jquery.localizationTool.js

jquery.localizationTool

Verze ze dne 23. 05. 2020. Zobrazit nejnovější verzi.

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greatest.deepsurf.us/scripts/403927/808313/jquerylocalizationTooljs.js

  1. /**
  2. * @fileOverview Contains the code for jQuery.localizationTool
  3. *
  4. * @author Savio Dimatteo <darksmo@gmail.com>
  5. */
  6.  
  7. (function($) {
  8. var _keyboardPressed = false;
  9.  
  10. var methods = {
  11. /**
  12. * Returns the ordinal number corresponding to the given language code,
  13. * or throws in case the given language code is not defined.
  14. * NOTE: this method operates on the active languages, therefore
  15. * $this.data('activeLanguageCodeArray') must be available when the
  16. * method is called.
  17. *
  18. * @name _languageCodeToOrdinal
  19. * @function
  20. * @access private
  21. * @param {string} lanuageCode - the language code to convert to ordinal
  22. * @returns {number} ordinal - the converted ordinal
  23. */
  24. '_languageCodeToOrdinal' : function (languageCode) {
  25. var $this = this,
  26. activeLanguageCodes = $this.data('activeLanguageCodeArray');
  27.  
  28. var ordinal = $.inArray(languageCode, activeLanguageCodes);
  29.  
  30. if (ordinal === -1) {
  31. $.error('Cannot convert ' + languageCode + ' into an ordinal number');
  32. }
  33.  
  34. return ordinal;
  35. },
  36. /**
  37. * Returns the language code corresponding to the given ordinal number.
  38. * It throws in case the given ordinal number does not correspond to any
  39. * language code.
  40. * NOTE: this method operates on the active languages, therefore
  41. * $this.data('activeLanguageCodeArray') must be available when the
  42. * method is called.
  43. *
  44. * @name _ordinalToLanguageCode
  45. * @function
  46. * @access private
  47. * @param {number} ordinal - the ordinal number to convert into a language code
  48. * @returns {string} languageCode - the converted language code
  49. */
  50. '_ordinalToLanguageCode' : function (ordinal) {
  51. var $this = this,
  52. activeLanguageCodes = $this.data('activeLanguageCodeArray');
  53.  
  54. if (activeLanguageCodes.length <= ordinal || ordinal < 0) {
  55. $.error('Cannot convert ' + ordinal + ' into a language code.');
  56. }
  57.  
  58. return activeLanguageCodes[ordinal];
  59. },
  60. /**
  61. * Returns the html representation for the given language code.
  62. * @name _languageCodeToHtml
  63. * @function
  64. * @param {string} languageCode - the language code as defined in the settings object
  65. */
  66. '_languageCodeToHtml': function (languageCode) {
  67. var $this = this,
  68. settings = $this.data('settings'),
  69. languagesObj = settings.languages,
  70. languageDefinitionObj = languagesObj[languageCode];
  71.  
  72. var htmlClass = '';
  73. if (languageDefinitionObj.flag.hasOwnProperty('class')) {
  74. htmlClass = ' ' + languageDefinitionObj.flag['class'];
  75. }
  76.  
  77. var htmlImage = '';
  78. if (languageDefinitionObj.flag.hasOwnProperty('url')) {
  79. htmlImage = '<img src="' + languageDefinitionObj.flag.url + '" />';
  80. }
  81.  
  82. var languageName = languageDefinitionObj.language;
  83. var haveCountry = languageDefinitionObj.hasOwnProperty('country');
  84.  
  85. /*
  86. * Build up the html
  87. */
  88. var html = [];
  89.  
  90. html.push('<li class="ltool-language ', languageCode, '">');
  91.  
  92. if (settings.showFlag) {
  93. html.push(
  94. '<div class="ltool-language-flag', htmlClass, '"></div>'
  95. );
  96. html.push(
  97. htmlImage
  98. );
  99. }
  100.  
  101. var interpolatedTemplate = methods._interpolateTemplate.call($this,
  102. haveCountry ? languageDefinitionObj.country : undefined,
  103. languageName
  104. );
  105. html.push(interpolatedTemplate);
  106. html.push('</li>');
  107.  
  108. return html.join('');
  109. },
  110. /**
  111. * Interpolates the given country name and language name to the
  112. * labelTemplate specified in the settings.
  113. *
  114. * @param {string} countryName
  115. * the country name
  116. * @param {string} languageName
  117. * the language name
  118. *
  119. * @returns {string}
  120. * the interpolated template
  121. */
  122. _interpolateTemplate: function (countryName, languageName) {
  123. var $this = this,
  124. settings = $this.data('settings'),
  125. template = settings.labelTemplate,
  126. countryReplacement = '',
  127. languageReplacement = '',
  128. haveCountry = typeof countryName === 'string';
  129.  
  130. if (settings.showCountry && haveCountry) {
  131. countryReplacement = [
  132. '<span class="ltool-language-country">',
  133. '$1' + countryName.replace(/[$]/g, '&#36;') + '$2',
  134. '</span>'
  135. ].join('');
  136. }
  137. if (settings.showLanguage) {
  138. var hasCountryClass = haveCountry ? 'ltool-has-country ' : "";
  139. languageReplacement = [
  140. '<span class="', hasCountryClass, 'ltool-language-name">',
  141. '$1' + languageName.replace(/[$]/g, '&#36;') + '$2' ,
  142. '</span>'
  143. ].join('');
  144. }
  145.  
  146. return '<span class="ltool-language-countryname">' +
  147. template
  148. .replace(/{{([^{]*)language([^}]*)}}/g, languageReplacement)
  149. .replace(/{{([^{]*)country([^}]*)}}/g, countryReplacement) +
  150. '</span>';
  151. },
  152. /**
  153. * Displays the given language in the dropdown menu.
  154. * @name _selectLanguage
  155. * @function
  156. * @access private
  157. * @param {string} languageCode - the language code
  158. */
  159. '_selectLanguage': function (languageCode) {
  160. var $this = this;
  161.  
  162. $this.find('.ltool-dropdown-label').html(
  163. $('.ltool-language.' + languageCode).html()
  164. );
  165.  
  166. $this.data('selectedLanguageCode', languageCode);
  167. },
  168. /**
  169. * Initializes the localization tool widget.
  170. * @name _initializeWidget
  171. * @function
  172. * @access private
  173. * @param {array} languageCodeArray - the language code array of the languages to be displayed
  174. */
  175. '_initializeWidget': function (languageCodeArray) {
  176.  
  177. var $this = this,
  178. settings = $this.data('settings'),
  179. languagesObj = settings.languages;
  180. var markupArray = [];
  181.  
  182. markupArray.push('<span tabindex="0" class="ltool-dropdown-label">Change Language</span><div class="ltool-dropdown-label-arrow"></div>');
  183. markupArray.push('<ul class="ltool-dropdown-items">');
  184. var languageCode, i;
  185. for (i=0;languageCode=languageCodeArray[i++];) {
  186.  
  187. if ( languagesObj.hasOwnProperty(languageCode)) {
  188. markupArray.push(
  189. methods._languageCodeToHtml.call($this, languageCode)
  190. );
  191. }
  192. else {
  193. $.error('The language \'' + languageCode + '\' must be defined');
  194. }
  195. }
  196. markupArray.push('</ul>');
  197.  
  198. $(markupArray.join('')).appendTo($this);
  199.  
  200. return $this;
  201. },
  202. /**
  203. * Handles dropdown click event.
  204. * @name _onDropdownClicked
  205. * @function
  206. * @access private
  207. */
  208. '_onDropdownClicked' : function (/*e*/) {
  209. var $this = this;
  210.  
  211. var selectedLanguageCode = $this.data('selectedLanguageCode');
  212.  
  213. $this.find('.ltool-language').removeClass('ltool-is-selected');
  214. $this.find('.' + selectedLanguageCode).addClass('ltool-is-selected');
  215.  
  216. $this.toggleClass('ltool-is-visible');
  217.  
  218. return $this;
  219. },
  220. '_closeDropdown' : function () {
  221. var $this = this;
  222.  
  223. $this.removeClass('ltool-is-visible');
  224. },
  225. /**
  226. * Handles mouseout on dropdown items.
  227. * @name _onMouseout
  228. * @function
  229. * @access private
  230. */
  231. '_onMouseout': function (e) {
  232. var $this = this;
  233.  
  234. if ($this.find(e.relatedTarget).length > 0) {
  235. // get rid of the current selected item!
  236. $this.find('.ltool-is-selected')
  237. .removeClass('ltool-is-selected');
  238.  
  239. // we will be over an element of ours
  240. e.preventDefault();
  241. return $this;
  242. }
  243.  
  244. /* We will be over another element that doesn't belong to us */
  245. $this.removeClass('ltool-is-visible');
  246. },
  247. /**
  248. * Handles user clicks on a certain dropdown item.
  249. * @name _onLanguageSelected
  250. * @function
  251. * @param {$element} $item - the jquery item clicked
  252. * @access private
  253. */
  254. '_onLanguageSelected': function ($item) {
  255. var $this = this;
  256.  
  257. // extract language code from the $item
  258. var languageCode = $item.attr('class')
  259. .replace('ltool-language', '')
  260. .replace('ltool-is-selected', '')
  261. .replace(/ /g, '');
  262.  
  263. methods._selectLanguage.call($this, languageCode);
  264. methods._mayTranslate.call($this, languageCode);
  265. },
  266. /**
  267. * Select the language before the current language in the list.
  268. * @name _selectPreviousLanguage
  269. * @function
  270. * @access private
  271. */
  272. '_selectPreviousLanguage' : function () {
  273. var $this = this;
  274.  
  275. var currentLanguageCode = $this.data('selectedLanguageCode');
  276. var currentLanguageCodeOrdinal = methods._languageCodeToOrdinal.call($this, currentLanguageCode);
  277.  
  278. if (currentLanguageCodeOrdinal === 0) {
  279. return; // cannot go before the first language
  280. }
  281.  
  282. var nextLanguageCode = methods._ordinalToLanguageCode.call($this, currentLanguageCodeOrdinal-1);
  283.  
  284. // peform the selection
  285. $this.find('.ltool-is-selected').removeClass('ltool-is-selected');
  286. methods._selectLanguage.call($this, nextLanguageCode);
  287. methods._mayTranslate.call($this, nextLanguageCode);
  288. $this.find('.' + nextLanguageCode).addClass('ltool-is-selected');
  289.  
  290. return $this;
  291. },
  292. /**
  293. * Select the language after the current language in the list.
  294. * @name _selectPreviousLanguage
  295. * @function
  296. * @access private
  297. */
  298. '_selectNextLanguage' : function () {
  299. var $this = this,
  300. activeLanguageCodes = $this.data('activeLanguageCodeArray');
  301.  
  302. var currentLanguageCode = $this.data('selectedLanguageCode');
  303. var currentLanguageCodeOrdinal = methods._languageCodeToOrdinal.call($this, currentLanguageCode);
  304.  
  305. if (currentLanguageCodeOrdinal + 1 >= activeLanguageCodes.length) {
  306. return;
  307. }
  308.  
  309. var nextLanguageCode = methods._ordinalToLanguageCode.call($this, currentLanguageCodeOrdinal+1);
  310.  
  311. // peform the selection
  312. $this.find('.ltool-is-selected').removeClass('ltool-is-selected');
  313. methods._selectLanguage.call($this, nextLanguageCode);
  314. methods._mayTranslate.call($this, nextLanguageCode);
  315. $this.find('.' + nextLanguageCode).addClass('ltool-is-selected');
  316.  
  317. return $this;
  318. },
  319. /**
  320. * Handles keydown event
  321. * @name _onKeydown
  322. * @function
  323. * @param {event} e - the keydown event
  324. * @access private
  325. */
  326. '_onKeydown': function (e) {
  327. var $this = this;
  328.  
  329. switch (e.keyCode) {
  330. case 13: /* enter (open-close menu) */
  331. methods._onDropdownClicked.call($this);
  332. e.preventDefault();
  333. break;
  334. case 40: /* down (select next) */
  335. methods._selectNextLanguage.call($this);
  336. e.preventDefault();
  337. break;
  338. case 38: /* up (select previous) */
  339. methods._selectPreviousLanguage.call($this);
  340. e.preventDefault();
  341. break;
  342. case 27:
  343. methods._closeDropdown.call($this);
  344. e.preventDefault();
  345. break;
  346. }
  347.  
  348. return $this;
  349. },
  350. /**
  351. * Binds events to the localization tool widget.
  352. * @name _bindEvents
  353. * @function
  354. * @access private
  355. */
  356. '_bindEvents': function () {
  357. var $this = this;
  358.  
  359. $this
  360. .bind('mousedown.localizationTool', function (e) {
  361. _keyboardPressed = false;
  362. methods._onKeydown.call($this, e);
  363. })
  364. .bind('click.localizationTool', function (e) {
  365. methods._onDropdownClicked.call($this, e);
  366. })
  367. .bind('keydown.localizationTool', function (e){
  368. _keyboardPressed = true;
  369. methods._onKeydown.call($this, e);
  370. })
  371. .bind('mouseout.localizationTool', function (e) {
  372. methods._onMouseout.call($this, e);
  373. })
  374. .bind('focusout.localizationTool', function () {
  375. if (_keyboardPressed) {
  376. methods._closeDropdown.call($this);
  377. }
  378. });
  379.  
  380. $this.find('.ltool-language')
  381. .bind('click.localizationTool', function (/*e*/) {
  382. methods._onLanguageSelected.call($this, $(this));
  383. });
  384.  
  385.  
  386. return $this;
  387. },
  388. /**
  389. * Analizes the input strings object and decomposes its keys in
  390. * sections: text strings, id strings, class strings, element strings,
  391. * attribute strings.
  392. * @name _decomposeStringsForReferenceMapping
  393. * @function
  394. * @access private
  395. * @returns {object} the decomposition object.
  396. */
  397. '_decomposeStringsForReferenceMapping' : function () {
  398. var decompositionObj = {
  399. 'idStrings' : [],
  400. 'classStrings' : [],
  401. 'elementStrings' : [],
  402. 'textStrings' : [],
  403. 'attributeStrings' : []
  404. };
  405.  
  406. var $this = this,
  407. stringsObj = $this.data('settings').strings;
  408.  
  409. // regexp for attributes matching
  410. var attrRegexp = new RegExp('^[a-zA-Z-]+?::');
  411.  
  412. var stringKey;
  413. for (stringKey in stringsObj) {
  414. if (stringsObj.hasOwnProperty(stringKey)) {
  415. if (stringKey.match(attrRegexp)) { // NOTE: check first!
  416. decompositionObj.attributeStrings.push(stringKey);
  417. }
  418. else if (stringKey.indexOf('id:') === 0) {
  419. decompositionObj.idStrings.push(stringKey);
  420. }
  421. else if (stringKey.indexOf('class:') === 0) {
  422. decompositionObj.classStrings.push(stringKey);
  423. }
  424. else if (stringKey.indexOf('element:') === 0) {
  425. decompositionObj.elementStrings.push(stringKey);
  426. }
  427. else {
  428. decompositionObj.textStrings.push(stringKey);
  429. }
  430. }
  431. }
  432.  
  433. return decompositionObj;
  434. },
  435. /**
  436. * Goes through each text node and builds a string reference mapping.
  437. * It is a mapping (an object)
  438. * STRING_IDENTIFIER -> <IS_ATTRIBUTE?, ORIGINAL_HTML, [DOM_NODES]>
  439. * used later for the translation. See init method for a
  440. * reference. The resulting object is stored internally in
  441. * $this.data('refMappingObj') as refMapping.
  442. * @name _buildStringReferenceMapping
  443. * @function
  444. * @access private
  445. */
  446. '_buildStringReferenceMapping': function () {
  447.  
  448. var $this = this,
  449. refMapping = {},
  450. settings = $this.data('settings'),
  451. stringsObj = settings.strings;
  452.  
  453. // decompose the initial strings in various bits
  454. var decompositionObj = methods._decomposeStringsForReferenceMapping.call($this);
  455.  
  456. /*
  457. * First go through each id
  458. */
  459.  
  460. var idString, i;
  461. for (i=0; idString = decompositionObj.idStrings[i++];) {
  462.  
  463. var idStringName = idString.substring('id:'.length);
  464. var $idNode = $('#' + idStringName);
  465. var contents = $idNode.contents();
  466.  
  467. if (settings.ignoreUnmatchedSelectors === true && contents.length === 0) {
  468. continue;
  469. }
  470.  
  471. if (contents.length !== 1) {
  472. $.error(idString + ' must contain exactly one text node, found ' + contents.length + ' instead');
  473. }
  474. else if (contents[0].nodeType !== 3) {
  475. $.error(idString + ' does not contain a #text node (i.e., type 3)');
  476. }
  477. else {
  478. // add this to the refMapping
  479. refMapping[idString] = {
  480. isAttribute : false, // it's an id: selector
  481. originalText : $idNode.text(),
  482. domNodes : [ $idNode ]
  483. };
  484. }
  485. }
  486.  
  487. /*
  488. * Helper function to not write the same code over again...
  489. */
  490. var processMultipleElements = function (prefix, jqueryPrefix, checkForIds, checkForClasses) {
  491.  
  492. var string;
  493. var decompositionKeyPrefix = prefix.replace(':','');
  494. for (i=0; string = decompositionObj[decompositionKeyPrefix + 'Strings'][i++];) {
  495. var stringName = string.substring(prefix.length);
  496.  
  497. // keeps the text of the first dom node in the loop below
  498. var domNodeText;
  499. var domNodesArray = [];
  500. var allNodeTextsAreEqual = true;
  501. domNodeText = undefined; // note: assigns undefined
  502.  
  503. var k=0, node;
  504. NODE:
  505. for (; node = $(jqueryPrefix + stringName)[k++];) {
  506.  
  507. var $node = $(node);
  508.  
  509. if (checkForIds) {
  510. var nodeId = $node.attr('id');
  511. // skip any node that was previously translated via an id
  512. if (typeof nodeId === 'string' && stringsObj.hasOwnProperty('id:' + nodeId)) {
  513. continue NODE;
  514. }
  515. }
  516.  
  517. if (checkForClasses) {
  518. // skip any node that was previously translated via a class
  519. var nodeClasses = $node.attr('class');
  520.  
  521. if (typeof nodeClasses === 'string') {
  522.  
  523. var nodeClassArray = nodeClasses.split(' '),
  524. nodeClass,
  525. j = 0;
  526.  
  527. for(;nodeClass = nodeClassArray[j++];) {
  528. if (typeof nodeClass === 'string' && stringsObj.hasOwnProperty('class:' + nodeClass)) {
  529. continue NODE;
  530. }
  531. }
  532. }
  533. }
  534.  
  535. // make sure this node contains only one text content
  536. var nodeContents = $node.contents();
  537. if (nodeContents.length === 0 || nodeContents.length > 1) {
  538. $.error('A \'' + string + '\' node was found to contain ' + nodeContents.length + ' child nodes. This node must contain exactly one text node!');
  539.  
  540. continue;
  541. }
  542.  
  543. if (nodeContents[0].nodeType !== 3) {
  544. $.error('A \'' + string + '\' node does not contain a #text node (i.e., type 3)');
  545.  
  546. continue;
  547. }
  548.  
  549. // this node is pushable at this point...
  550. domNodesArray.push($node);
  551.  
  552. // also check the text is the same across the nodes considered
  553. if (typeof domNodeText === 'undefined') {
  554. // ... the first time we store the text of the node
  555. domNodeText = $node.text();
  556. }
  557. else if (domNodeText !== $node.text()) {
  558. // ... then we keep checking if the text node is the same
  559. allNodeTextsAreEqual = false;
  560. }
  561.  
  562. } // end for k loop
  563.  
  564. // make sure that the remaining classes contain the same text
  565. if (!allNodeTextsAreEqual) {
  566. $.error('Not all text content of elements with ' + string + ' were found to be \'' + domNodeText + '\'. So these elements will be ignored.');
  567. }
  568. else {
  569. // all good
  570. refMapping[string] = {
  571. isAttribute : false, // it's a class: or an element: selector
  572. originalText : domNodeText,
  573. domNodes : domNodesArray
  574. };
  575. }
  576. }
  577.  
  578. }; // end of processMultipleElements
  579.  
  580.  
  581. /*
  582. * Then go through classes
  583. */
  584. processMultipleElements('class:', '.', true, false);
  585.  
  586. /*
  587. * Then go through elements
  588. */
  589. processMultipleElements('element:', '', true, true);
  590.  
  591. /*
  592. * Time to process the attributes
  593. */
  594. var firstSelectorStringRegex = new RegExp('(class|id|element):[^:]');
  595. var attrString;
  596. for (i=0; attrString = decompositionObj.attributeStrings[i++];) {
  597.  
  598.  
  599. // let's extract the attribute name from the element selector
  600. var splitStringArray = attrString.split("::");
  601. var attributeString = splitStringArray.shift();
  602.  
  603.  
  604. // sanity check on the format
  605. if (splitStringArray.length === 0) {
  606. $.error('sorry, you need to specify class:, element: or id: selectors in ' + attrString);
  607. }
  608.  
  609. var selectorString = splitStringArray.join('::');
  610.  
  611. if (!splitStringArray[0].match(firstSelectorStringRegex)) {
  612. $.error(attrString + "Doesn't look right. Perhaps you've added extra semicolons?");
  613. }
  614.  
  615.  
  616. // turn selector into jQuery selector
  617. selectorString = selectorString.replace('id:', '#');
  618. selectorString = selectorString.replace('class:', '.');
  619. selectorString = selectorString.replace('element:', '');
  620.  
  621. // find DOM nodes
  622. var $domNodes = $(selectorString + '[' + attributeString + ']');
  623. if ($domNodes.length === 0) {
  624. $.error('The selector "' + attrString + '" does not point to an existing DOM element');
  625. }
  626.  
  627.  
  628. // avoid using Array.prototype.reduce as it's supported in IE9+
  629. var j = 0,
  630. allSameAttributeValue = true;
  631.  
  632. var attributeText = $($domNodes[0]).attr(attributeString);
  633.  
  634. var domNodesToAdd = [];
  635.  
  636. for (j=0; j<$domNodes.length; j++) {
  637. // check the placeholder text is all the same
  638. var $dom = $($domNodes[j]);
  639. if (attributeText !== $dom.attr(attributeString)) {
  640. allSameAttributeValue = false;
  641. }
  642.  
  643. // also add for later...
  644. domNodesToAdd.push($dom);
  645. }
  646. if (!allSameAttributeValue) {
  647. $.error('Not all the attribute values selected via ' + attrString + ' are the same');
  648. }
  649.  
  650. // now we have everything in place, we just add it to the rest!
  651. refMapping[attrString] = {
  652. isAttribute : true, // yes, we are dealing with an attribute here
  653. originalText : attributeText,
  654. domNodes : domNodesToAdd
  655. };
  656. }
  657.  
  658.  
  659. /*
  660. * Finally find the dom nodes associated to any text searched
  661. */
  662. var textString;
  663.  
  664. for (i=0; textString = decompositionObj.textStrings[i++];) {
  665. // nodes that will contain the text to translate
  666. var textNodesToAdd = [];
  667.  
  668. var allParentNodes = $(':contains(' + textString + ')');
  669. var k, parentNode;
  670. for (k=0; parentNode = allParentNodes[k++];) {
  671. var nodeContents = $(parentNode).contents();
  672. if (nodeContents.length === 1 &&
  673. nodeContents[0].nodeType === 3) {
  674.  
  675. textNodesToAdd.push($(parentNode));
  676. }
  677. }
  678. if (textNodesToAdd.length > 0) {
  679. // all good
  680. refMapping[textString] = {
  681. isAttribute: false, // no it's just another dom element
  682. originalText : textString,
  683. domNodes : textNodesToAdd
  684. };
  685. }
  686. }
  687.  
  688. $this.data('refMappingObj', refMapping);
  689.  
  690. return $this;
  691. },
  692. /**
  693. * Calls the user specified callback (if any), then translates the page.
  694. * If the user returned 'false' in his/her callback, the translation is
  695. * not performed.
  696. * @name _mayTranslate
  697. * @function
  698. * @access private
  699. * @param {string} [languageCode] - the language code to translate to
  700. */
  701. '_mayTranslate': function (languageCode) {
  702. var $this = this,
  703. settings = $this.data('settings');
  704.  
  705. if (false !== settings.onLanguageSelected(languageCode)) {
  706. methods._translate.call($this, languageCode);
  707. }
  708. },
  709. /**
  710. * Returns the code of the language currently selected
  711. * @name getSelectedLanguageCode
  712. * @function
  713. * @access public
  714. * @returns {string} [languageCode] - the language code currently selected
  715. */
  716. 'getSelectedLanguageCode' : function () {
  717. var $this = this;
  718. return $this.data('selectedLanguageCode');
  719. },
  720. /**
  721. * Translates the current page.
  722. * @name translate
  723. * @function
  724. * @access public
  725. * @param {string} [languageCode] - the language to translate to.
  726. */
  727. '_translate': function (languageCode) {
  728. var $this = this,
  729. settings = $this.data('settings'),
  730. stringsObj = settings.strings,
  731. refMappingObj = $this.data('refMappingObj');
  732.  
  733. var cssDirection = 'ltr';
  734. if (typeof languageCode !== 'undefined') {
  735. // check if the language code exists actually
  736. if (!settings.languages.hasOwnProperty(languageCode)) {
  737. $.error('The language code ' + languageCode + ' is not defined');
  738. return $this;
  739. }
  740.  
  741. // check if we are dealing with a right to left language
  742. if (settings.languages[languageCode].hasOwnProperty('cssDirection')) {
  743.  
  744. cssDirection = settings.languages[languageCode].cssDirection;
  745. }
  746. }
  747.  
  748. // translate everything according to the reference mapping
  749. var string;
  750. for (string in refMappingObj) {
  751. if (refMappingObj.hasOwnProperty(string)) {
  752.  
  753. // get the translation for this string...
  754. var translation;
  755. if (typeof languageCode === 'undefined' || languageCode === settings.defaultLanguage) {
  756. translation = refMappingObj[string].originalText;
  757. }
  758. else {
  759. translation = stringsObj[string][languageCode];
  760. }
  761. //存储当前选择的语言
  762. console.log("translation:",translation);
  763. console.log("languageCode:",languageCode);
  764. if(gc_multiLanguage){
  765. gc_multiLanguage.saveConfig(translation);
  766. }
  767. var domNodes = refMappingObj[string].domNodes;
  768.  
  769. var $domNode, i;
  770.  
  771. // attribute case
  772. if (refMappingObj[string].isAttribute === true) {
  773. var attributeName = string.split("::", 1)[0];
  774.  
  775. for (i=0; $domNode = domNodes[i++];) {
  776. $domNode.attr(attributeName, translation);
  777. $domNode.css('direction', cssDirection);
  778. }
  779. }
  780. else {
  781. // all other cases
  782. for (i=0; $domNode = domNodes[i++];) {
  783. $domNode.html(translation);
  784. $domNode.css('direction', cssDirection);
  785. }
  786. }
  787. }
  788. }
  789.  
  790. return $this;
  791. },
  792. /**
  793. * Translates according to the widget configuration programmatically.
  794. * This is meant to be called by the user. The difference with the
  795. * private counterpart _translate method is that the language is
  796. * selected in the widget.
  797. */
  798. 'translate' : function (languageCode) {
  799. var $this = this;
  800.  
  801. methods._translate.call($this, languageCode);
  802.  
  803. // must also select the language when translating via the public method
  804. methods._selectLanguage.call($this, languageCode);
  805.  
  806. return $this;
  807. },
  808. /**
  809. * Destroys the dropdown widget.
  810. *
  811. * @name destroy
  812. * @function
  813. * @access public
  814. **/
  815. 'destroy' : function () {
  816. var $this = this;
  817.  
  818. // remove all data set with .data()
  819. $this.removeData();
  820.  
  821. // unbind events
  822. $this.unbind('click.localizationTool', function (e) {
  823. methods._onDropdownClicked.call($this, e);
  824. });
  825. $this.find('.ltool-language')
  826. .unbind('click.localizationTool', function (/*e*/) {
  827. methods._onLanguageSelected.call($this, $(this));
  828. });
  829.  
  830. $this
  831. .unbind('mouseout.localizationTool', function (e) {
  832. methods._onMouseout.call($this, e);
  833. });
  834.  
  835. // remove markup
  836. $this.empty();
  837.  
  838. return $this;
  839. },
  840. /**
  841. * Sorts the given array of countryLanguageCodes by country name.
  842. * If a language has no name goes to the bottom of the list.
  843. *
  844. * @name _sortCountryLanguagesByCountryName
  845. * @function
  846. * @access private
  847. * @param {object} languagesDefinition - the array countryLanguageCodes defined during initialization.
  848. * @param {array} arrayOfCountryLanguageCodes - the input array countryLanguageCodes.
  849. * @returns {array} sortedArrayOfCountryLanguageCodes - the sorted array countryLanguageCodes.
  850. */
  851. '_sortCountryLanguagesByCountryName': function (languagesDefinition, arrayOfCountryLanguageCodes) {
  852. return arrayOfCountryLanguageCodes.sort(function (a, b) {
  853. if (languagesDefinition[a].hasOwnProperty('country') && languagesDefinition[b].hasOwnProperty('country')) {
  854. return languagesDefinition[a].country.localeCompare(
  855. languagesDefinition[b].country
  856. );
  857. }
  858. else if (languagesDefinition[a].hasOwnProperty('country')) {
  859. return languagesDefinition[a].country.localeCompare(
  860. languagesDefinition[b].language
  861. );
  862. }
  863. // else if (languagesDefinition[b].hasOwnProperty('country')) {
  864. return languagesDefinition[a].language.localeCompare(
  865. languagesDefinition[b].country
  866. );
  867. // }
  868. });
  869. },
  870. /**
  871. * Goes through each string defined and extracts the common subset of
  872. * languages that actually used. The default language is added to this
  873. * subset a priori. The resulting list is sorted by country name.
  874. *
  875. * @name _findSubsetOfUsedLanguages
  876. * @function
  877. * @access private
  878. * @param {object} stringsObj - the strings to translate
  879. * @returns {array} usedLanguageCodes - an array of country codes sorted based on country names.
  880. */
  881. '_findSubsetOfUsedLanguages' : function (stringsObj) {
  882. var $this = this;
  883. var string;
  884. var settings = $this.data('settings');
  885.  
  886. // build an histogram of all the used languages in strings
  887. var usedLanguagesHistogram = {};
  888. var howManyDifferentStrings = 0;
  889.  
  890. for (string in stringsObj) {
  891. if (stringsObj.hasOwnProperty(string)) {
  892.  
  893. var languages = stringsObj[string],
  894. language;
  895.  
  896. for (language in languages) {
  897. if (languages.hasOwnProperty(language)) {
  898. if (!usedLanguagesHistogram.hasOwnProperty(language)) {
  899. usedLanguagesHistogram[language] = 0;
  900. }
  901. }
  902. usedLanguagesHistogram[language]++;
  903. }
  904.  
  905. howManyDifferentStrings++;
  906. }
  907. }
  908.  
  909. // find languages that are guaranteed to appear in all strings
  910. var guaranteedLanguages = [],
  911. languageCode;
  912.  
  913. for (languageCode in usedLanguagesHistogram) {
  914. if (usedLanguagesHistogram.hasOwnProperty(languageCode) &&
  915. usedLanguagesHistogram[languageCode] === howManyDifferentStrings
  916. ) {
  917.  
  918. guaranteedLanguages.push(languageCode);
  919. }
  920. }
  921.  
  922. // delete the default language if it's in the guaranteed languages
  923. var defaultIdx = $.inArray(settings.defaultLanguage, guaranteedLanguages);
  924. if (defaultIdx > -1) {
  925. // delete the default language from the array
  926. guaranteedLanguages.splice(defaultIdx, 1);
  927. }
  928.  
  929. // add the default language in front
  930. guaranteedLanguages.unshift(settings.defaultLanguage);
  931.  
  932. return methods._sortCountryLanguagesByCountryName.call(
  933. this,
  934. settings.languages,
  935. guaranteedLanguages
  936. );
  937. },
  938. /**
  939. * Initialises the localization tool plugin.
  940. * @name init
  941. * @function
  942. * @param {object} [options] - the user options
  943. * @access public
  944. * @returns jqueryObject
  945. */
  946. 'init' : function(options) {
  947. // NOTE: "country" is optional
  948. var knownLanguages = {
  949. 'en_GB' : {
  950. 'country' : 'United Kingdom',
  951. 'language': 'English',
  952. 'countryTranslated' : 'United Kingdom',
  953. 'languageTranslated': 'English',
  954. 'flag': {
  955. 'class' : 'flag flag-gb'
  956. }
  957. },
  958. 'de_DE' : {
  959. 'country' : 'Germany',
  960. 'language' : 'German',
  961. 'countryTranslated' : 'Deutschland',
  962. 'languageTranslated' : 'Deutsch',
  963. 'flag' : {
  964. 'class' : 'flag flag-de'
  965. }
  966. },
  967. 'es_ES' : {
  968. 'country' : 'Spain',
  969. 'language' : 'Spanish',
  970. 'countryTranslated': 'España',
  971. 'languageTranslated' : 'Español',
  972. 'flag' : {
  973. 'class' : 'flag flag-es'
  974. }
  975. },
  976. 'fr_FR' : {
  977. 'country' : 'France',
  978. 'language' : 'French',
  979. 'countryTranslated' : 'France',
  980. 'languageTranslated' : 'Français',
  981. 'flag' : {
  982. 'class' : 'flag flag-fr'
  983. }
  984. },
  985. 'ko_KR' : {
  986. 'country' : 'Korea, Republic of.',
  987. 'language' : 'Korean',
  988. 'countryTranslated' : '대한민국',
  989. 'languageTranslated' : '한국어',
  990. 'flag' : {
  991. 'class' : 'flag flag-kr'
  992. }
  993. },
  994. 'pt_BR' : {
  995. 'country' : 'Brazil',
  996. 'language' : 'Portuguese',
  997. 'countryTranslated': 'Brasil',
  998. 'languageTranslated' : 'Português',
  999. 'flag' : {
  1000. 'class' : 'flag flag-br'
  1001. }
  1002. },
  1003. 'en_AU' : {
  1004. 'country' : 'Australia',
  1005. 'language' : 'English',
  1006. 'countryTranslated' : 'Australia',
  1007. 'languageTranslated' : 'English',
  1008. 'flag' : {
  1009. 'class' : 'flag flag-au'
  1010. }
  1011. },
  1012. 'en_IN' : {
  1013. 'country' : 'India',
  1014. 'language' : 'English',
  1015. 'countryTranslated': 'India',
  1016. 'languageTranslated': 'English',
  1017. 'flag': {
  1018. 'class' : 'flag flag-in'
  1019. }
  1020. },
  1021. 'it_IT' : {
  1022. 'country' : 'Italy',
  1023. 'language': 'Italian',
  1024. 'countryTranslated': 'Italia',
  1025. 'languageTranslated': 'Italiano',
  1026. 'flag' : {
  1027. 'class' : 'flag flag-it'
  1028. }
  1029. },
  1030. 'jp_JP' : {
  1031. 'country' : 'Japan',
  1032. 'language': 'Japanese',
  1033. 'countryTranslated': '日本',
  1034. 'languageTranslated': '日本語',
  1035. 'flag' : {
  1036. 'class' : 'flag flag-jp'
  1037. }
  1038. },
  1039. 'ar_TN' : {
  1040. 'country' : 'Tunisia',
  1041. 'language' : 'Arabic',
  1042. 'countryTranslated': 'تونس',
  1043. 'languageTranslated': 'عربي',
  1044. 'cssDirection': 'rtl',
  1045. 'flag' : {
  1046. 'class' : 'flag flag-tn'
  1047. }
  1048. },
  1049. 'en_IE' : {
  1050. 'country': 'Ireland',
  1051. 'language': 'English',
  1052. 'countryTranslated': 'Ireland',
  1053. 'languageTranslated' : 'English',
  1054. 'flag' : {
  1055. 'class' : 'flag flag-ie'
  1056. }
  1057. },
  1058. 'nl_NL': {
  1059. 'country' : 'Netherlands',
  1060. 'language': 'Dutch',
  1061. 'countryTranslated' : 'Nederland',
  1062. 'languageTranslated' : 'Nederlands',
  1063. 'flag' : {
  1064. 'class' : 'flag flag-nl'
  1065. }
  1066. },
  1067. 'zh_CN': {
  1068. 'country' : 'China',
  1069. 'language' : 'Simplified Chinese',
  1070. 'countryTranslated': '中国',
  1071. 'languageTranslated': '简体中文',
  1072. 'flag' : {
  1073. 'class' : 'flag flag-cn'
  1074. }
  1075. },
  1076. 'zh_TW': {
  1077. 'country' : 'Taiwan',
  1078. 'language' : 'Traditional Chinese',
  1079. 'countryTranslated': '臺灣',
  1080. 'languageTranslated': '繁體中文',
  1081. 'flag' : {
  1082. 'class' : 'flag flag-tw'
  1083. }
  1084. },
  1085. 'fi_FI': {
  1086. 'country' : 'Finland',
  1087. 'language' : 'Finnish',
  1088. 'countryTranslated' : 'Suomi',
  1089. 'languageTranslated' : 'Suomi',
  1090. 'flag' : {
  1091. 'class' : 'flag flag-fi'
  1092. }
  1093. },
  1094. 'pt_PT' : {
  1095. 'country' : 'Portugal',
  1096. 'language' : 'Portuguese',
  1097. 'countryTranslated': 'Portugal',
  1098. 'languageTranslated' : 'Português',
  1099. 'flag' : {
  1100. 'class' : 'flag flag-pt'
  1101. }
  1102. },
  1103. 'pl_PL': {
  1104. 'country' : 'Poland',
  1105. 'language': 'Polish',
  1106. 'countryTranslated' : 'Polska',
  1107. 'languageTranslated': 'Polski',
  1108. 'flag' : {
  1109. 'class' : 'flag flag-pl'
  1110. }
  1111. },
  1112. 'ru_RU': {
  1113. 'country' : 'Russia',
  1114. 'language' : 'Russian',
  1115. 'languageTranslated': 'Русский',
  1116. 'countryTranslated' : 'Россия',
  1117. 'flag': {
  1118. 'class': 'flag flag-ru'
  1119. }
  1120. },
  1121. 'hi_IN': {
  1122. 'country' : 'India',
  1123. 'language': 'Hindi',
  1124. 'countryTranslated': 'भारत',
  1125. 'languageTranslated': 'हिन्द',
  1126. 'flag': {
  1127. 'class': 'flag flag-in'
  1128. }
  1129. },
  1130. 'ta_IN': {
  1131. 'country' : 'India',
  1132. 'language': 'Tamil',
  1133. 'countryTranslated': 'இந்தியா',
  1134. 'languageTranslated': 'தமிழ்',
  1135. 'flag': {
  1136. 'class': 'flag flag-in'
  1137. }
  1138. },
  1139. 'tr_TR': {
  1140. 'country' : 'Turkey',
  1141. 'language' : 'Turkish',
  1142. 'countryTranslated': 'Türkiye',
  1143. 'languageTranslated': 'Türkçe',
  1144. 'flag': {
  1145. 'class': 'flag flag-tr'
  1146. }
  1147. },
  1148. 'he_IL': {
  1149. 'country' : 'Israel',
  1150. 'language' : 'Hebrew',
  1151. 'countryTranslated' : 'מדינת ישראל',
  1152. 'languageTranslated': 'עברית',
  1153. 'cssDirection': 'rtl',
  1154. 'flag': {
  1155. 'class': 'flag flag-il'
  1156. }
  1157. },
  1158. 'da_DK' : {
  1159. 'country' : 'Denmark',
  1160. 'language' : 'Danish',
  1161. 'countryTranslated': 'Danmark',
  1162. 'languageTranslated': 'Dansk',
  1163. 'flag' : {
  1164. 'class': 'flag flag-dk'
  1165. }
  1166. },
  1167. 'ro_RO': {
  1168. 'country' : 'Romania',
  1169. 'language' : 'Romanian',
  1170. 'countryTranslated': 'România',
  1171. 'languageTranslated': 'Român',
  1172. 'flag' : {
  1173. 'class': 'flag flag-ro'
  1174. }
  1175. },
  1176. 'eo' : {
  1177. // NOTE: no country
  1178. 'language' : 'Esperanto',
  1179. 'languageTranslated' : 'Esperanto',
  1180. 'flag' : {
  1181. 'class': 'flag flag-esperanto'
  1182. }
  1183. }
  1184. };
  1185.  
  1186. var settings = $.extend({
  1187. 'defaultLanguage' : 'en_GB',
  1188. /* do not throw error if a selector doesn't match */
  1189. 'ignoreUnmatchedSelectors': false,
  1190. /* show the flag on the widget */
  1191. 'showFlag' : true,
  1192. /* show the language on the widget */
  1193. 'showLanguage': true,
  1194. /* show the country on the widget */
  1195. 'showCountry': true,
  1196. /* format of the language/country label */
  1197. 'labelTemplate': '{{country}} {{(language)}}',
  1198. 'languages' : {
  1199. /*
  1200. * The format here is <country code>_<language code>.
  1201. * - list of country codes: http://www.gnu.org/software/gettext/manual/html_node/Country-Codes.html
  1202. * - list of language codes: http://www.gnu.org/software/gettext/manual/html_node/Usual-Language-Codes.html#Usual-Language-Codes
  1203. */
  1204. },
  1205. /*
  1206. * Strings are provided by the user of the plugin. Each entry
  1207. * in the dictionary has the form:
  1208. *
  1209. * [STRING_IDENTIFIER] : {
  1210. * [LANGUAGE] : [TRANSLATION]
  1211. * }
  1212. *
  1213. * STRING_IDENTIFIER:
  1214. * id:<html-id-name> OR
  1215. * class:<html-class-name> OR
  1216. * element:<html-element-name> OR
  1217. * <string>
  1218. *
  1219. * LANGUAGE: one of the languages defined above (e.g., it_IT)
  1220. *
  1221. * TRANSLATION: <string>
  1222. *
  1223. */
  1224. 'strings' : {},
  1225. /*
  1226. * A callback called whenever the user selects the language
  1227. * from the dropdown menu. If false is returned, the
  1228. * translation will not be performed (but just the language
  1229. * will be selected from the widget).
  1230. *
  1231. * The countryLanguageCode is a string representing the
  1232. * selected language identifier like 'en_GB'
  1233. */
  1234. 'onLanguageSelected' : function (/*countryLanguageCode*/) { return true; }
  1235. }, options);
  1236.  
  1237. // add more languages
  1238. settings.languages = $.extend(knownLanguages, settings.languages);
  1239.  
  1240. // check that the default language is defined
  1241. if (!settings.languages.hasOwnProperty(settings.defaultLanguage)) {
  1242. $.error('FATAL: the default language ' + settings.defaultLanguage + ' is not defined in the \'languages\' parameter!');
  1243. }
  1244.  
  1245. return this.each(function() {
  1246. // save settings
  1247. var $this = $(this);
  1248.  
  1249. $this.data('settings', settings);
  1250.  
  1251. // language codes common to all translations
  1252. var activeLanguageCodeArray = methods._findSubsetOfUsedLanguages.call(
  1253. $this, settings.strings
  1254. );
  1255. $this.data('activeLanguageCodeArray', activeLanguageCodeArray);
  1256.  
  1257. methods._initializeWidget.call($this, activeLanguageCodeArray);
  1258.  
  1259. methods._selectLanguage.call($this, settings.defaultLanguage);
  1260.  
  1261. methods._bindEvents.call($this);
  1262.  
  1263. methods._buildStringReferenceMapping.call($this);
  1264. });
  1265. }
  1266. };
  1267.  
  1268. var __name__ = 'localizationTool';
  1269.  
  1270. /**
  1271. * jQuery Localization Tool - a jQuery widget to translate web pages
  1272. *
  1273. * @memberOf jQuery.fn
  1274. */
  1275. $.fn[__name__] = function(method) {
  1276. /*
  1277. * Just a router for method calls
  1278. */
  1279. if (methods[method]) {
  1280. if (this.data('initialized') === true) {
  1281. // call a method
  1282. return methods[method].apply(this,
  1283. Array.prototype.slice.call(arguments, 1)
  1284. );
  1285. }
  1286. else {
  1287. throw new Error('method ' + method + ' called on an uninitialized instance of ' + __name__);
  1288. }
  1289. }
  1290. else if (typeof method === 'object' || !method) {
  1291. // call init, user passed the settings as parameters
  1292. this.data('initialized', true);
  1293. return methods.init.apply(this, arguments);
  1294. }
  1295. else {
  1296. $.error('Cannot call method ' + method);
  1297. }
  1298. };
  1299. })(jQuery);