jquery.localizationTool.js

jquery.localizationTool

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