jquery.localizationTool.js

jquery.localizationTool

Fra 23.05.2020. Se den seneste versjonen.

Dette scriptet burde ikke installeres direkte. Det er et bibliotek for andre script å inkludere med det nye metadirektivet // @require https://update.greatest.deepsurf.us/scripts/403927/808319/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. if(typeof gc_multiLanguage != 'undefined'){
  264. gc_multiLanguage.saveConfig(languageCode);
  265. console.log("存储! languageCode",languageCode);
  266. }
  267. methods._selectLanguage.call($this, languageCode);
  268. methods._mayTranslate.call($this, languageCode);
  269. },
  270. /**
  271. * Select the language before the current language in the list. 在列表中选择当前语言之前的语言。
  272. * @name _selectPreviousLanguage
  273. * @function
  274. * @access private
  275. */
  276. '_selectPreviousLanguage' : function () {
  277. var $this = this;
  278.  
  279. var currentLanguageCode = $this.data('selectedLanguageCode');
  280. var currentLanguageCodeOrdinal = methods._languageCodeToOrdinal.call($this, currentLanguageCode);
  281.  
  282. if (currentLanguageCodeOrdinal === 0) {
  283. return; // cannot go before the first language
  284. }
  285.  
  286. var nextLanguageCode = methods._ordinalToLanguageCode.call($this, currentLanguageCodeOrdinal-1);
  287.  
  288. // peform the selection
  289. $this.find('.ltool-is-selected').removeClass('ltool-is-selected');
  290. methods._selectLanguage.call($this, nextLanguageCode);
  291. methods._mayTranslate.call($this, nextLanguageCode);
  292. $this.find('.' + nextLanguageCode).addClass('ltool-is-selected');
  293.  
  294. return $this;
  295. },
  296. /**
  297. * Select the language after the current language in the list. 选择列表中当前语言之后的语言。
  298. * @name _selectPreviousLanguage
  299. * @function
  300. * @access private
  301. */
  302. '_selectNextLanguage' : function () {
  303. var $this = this,
  304. activeLanguageCodes = $this.data('activeLanguageCodeArray');
  305.  
  306. var currentLanguageCode = $this.data('selectedLanguageCode');
  307. var currentLanguageCodeOrdinal = methods._languageCodeToOrdinal.call($this, currentLanguageCode);
  308.  
  309. if (currentLanguageCodeOrdinal + 1 >= activeLanguageCodes.length) {
  310. return;
  311. }
  312.  
  313. var nextLanguageCode = methods._ordinalToLanguageCode.call($this, currentLanguageCodeOrdinal+1);
  314.  
  315. // peform the selection
  316. $this.find('.ltool-is-selected').removeClass('ltool-is-selected');
  317. methods._selectLanguage.call($this, nextLanguageCode);
  318. methods._mayTranslate.call($this, nextLanguageCode);
  319. $this.find('.' + nextLanguageCode).addClass('ltool-is-selected');
  320.  
  321. return $this;
  322. },
  323. /**
  324. * Handles keydown event
  325. * @name _onKeydown
  326. * @function
  327. * @param {event} e - the keydown event
  328. * @access private
  329. */
  330. '_onKeydown': function (e) {
  331. var $this = this;
  332.  
  333. switch (e.keyCode) {
  334. case 13: /* enter (open-close menu) */
  335. methods._onDropdownClicked.call($this);
  336. e.preventDefault();
  337. break;
  338. case 40: /* down (select next) */
  339. methods._selectNextLanguage.call($this);
  340. e.preventDefault();
  341. break;
  342. case 38: /* up (select previous) */
  343. methods._selectPreviousLanguage.call($this);
  344. e.preventDefault();
  345. break;
  346. case 27:
  347. methods._closeDropdown.call($this);
  348. e.preventDefault();
  349. break;
  350. }
  351.  
  352. return $this;
  353. },
  354. /**
  355. * Binds events to the localization tool widget.
  356. * @name _bindEvents
  357. * @function
  358. * @access private
  359. */
  360. '_bindEvents': function () {
  361. var $this = this;
  362.  
  363. $this
  364. .bind('mousedown.localizationTool', function (e) {
  365. _keyboardPressed = false;
  366. methods._onKeydown.call($this, e);
  367. })
  368. .bind('click.localizationTool', function (e) {
  369. methods._onDropdownClicked.call($this, e);
  370. })
  371. .bind('keydown.localizationTool', function (e){
  372. _keyboardPressed = true;
  373. methods._onKeydown.call($this, e);
  374. })
  375. .bind('mouseout.localizationTool', function (e) {
  376. methods._onMouseout.call($this, e);
  377. })
  378. .bind('focusout.localizationTool', function () {
  379. if (_keyboardPressed) {
  380. methods._closeDropdown.call($this);
  381. }
  382. });
  383.  
  384. $this.find('.ltool-language')
  385. .bind('click.localizationTool', function (/*e*/) {
  386. methods._onLanguageSelected.call($this, $(this));
  387. });
  388.  
  389.  
  390. return $this;
  391. },
  392. /**
  393. * Analizes the input strings object and decomposes its keys in
  394. * sections: text strings, id strings, class strings, element strings,
  395. * attribute strings.
  396. * @name _decomposeStringsForReferenceMapping
  397. * @function
  398. * @access private
  399. * @returns {object} the decomposition object.
  400. */
  401. '_decomposeStringsForReferenceMapping' : function () {
  402. var decompositionObj = {
  403. 'idStrings' : [],
  404. 'classStrings' : [],
  405. 'elementStrings' : [],
  406. 'textStrings' : [],
  407. 'attributeStrings' : []
  408. };
  409.  
  410. var $this = this,
  411. stringsObj = $this.data('settings').strings;
  412.  
  413. // regexp for attributes matching
  414. var attrRegexp = new RegExp('^[a-zA-Z-]+?::');
  415.  
  416. var stringKey;
  417. for (stringKey in stringsObj) {
  418. if (stringsObj.hasOwnProperty(stringKey)) {
  419. if (stringKey.match(attrRegexp)) { // NOTE: check first!
  420. decompositionObj.attributeStrings.push(stringKey);
  421. }
  422. else if (stringKey.indexOf('id:') === 0) {
  423. decompositionObj.idStrings.push(stringKey);
  424. }
  425. else if (stringKey.indexOf('class:') === 0) {
  426. decompositionObj.classStrings.push(stringKey);
  427. }
  428. else if (stringKey.indexOf('element:') === 0) {
  429. decompositionObj.elementStrings.push(stringKey);
  430. }
  431. else {
  432. decompositionObj.textStrings.push(stringKey);
  433. }
  434. }
  435. }
  436.  
  437. return decompositionObj;
  438. },
  439. /**
  440. * Goes through each text node and builds a string reference mapping.
  441. * It is a mapping (an object)
  442. * STRING_IDENTIFIER -> <IS_ATTRIBUTE?, ORIGINAL_HTML, [DOM_NODES]>
  443. * used later for the translation. See init method for a
  444. * reference. The resulting object is stored internally in
  445. * $this.data('refMappingObj') as refMapping.
  446. * @name _buildStringReferenceMapping
  447. * @function
  448. * @access private
  449. */
  450. '_buildStringReferenceMapping': function () {
  451.  
  452. var $this = this,
  453. refMapping = {},
  454. settings = $this.data('settings'),
  455. stringsObj = settings.strings;
  456.  
  457. // decompose the initial strings in various bits
  458. var decompositionObj = methods._decomposeStringsForReferenceMapping.call($this);
  459.  
  460. /*
  461. * First go through each id
  462. */
  463.  
  464. var idString, i;
  465. for (i=0; idString = decompositionObj.idStrings[i++];) {
  466.  
  467. var idStringName = idString.substring('id:'.length);
  468. var $idNode = $('#' + idStringName);
  469. var contents = $idNode.contents();
  470.  
  471. if (settings.ignoreUnmatchedSelectors === true && contents.length === 0) {
  472. continue;
  473. }
  474.  
  475. if (contents.length !== 1) {
  476. $.error(idString + ' must contain exactly one text node, found ' + contents.length + ' instead');
  477. }
  478. else if (contents[0].nodeType !== 3) {
  479. $.error(idString + ' does not contain a #text node (i.e., type 3)');
  480. }
  481. else {
  482. // add this to the refMapping
  483. refMapping[idString] = {
  484. isAttribute : false, // it's an id: selector
  485. originalText : $idNode.text(),
  486. domNodes : [ $idNode ]
  487. };
  488. }
  489. }
  490.  
  491. /*
  492. * Helper function to not write the same code over again...
  493. */
  494. var processMultipleElements = function (prefix, jqueryPrefix, checkForIds, checkForClasses) {
  495.  
  496. var string;
  497. var decompositionKeyPrefix = prefix.replace(':','');
  498. for (i=0; string = decompositionObj[decompositionKeyPrefix + 'Strings'][i++];) {
  499. var stringName = string.substring(prefix.length);
  500.  
  501. // keeps the text of the first dom node in the loop below
  502. var domNodeText;
  503. var domNodesArray = [];
  504. var allNodeTextsAreEqual = true;
  505. domNodeText = undefined; // note: assigns undefined
  506.  
  507. var k=0, node;
  508. NODE:
  509. for (; node = $(jqueryPrefix + stringName)[k++];) {
  510.  
  511. var $node = $(node);
  512.  
  513. if (checkForIds) {
  514. var nodeId = $node.attr('id');
  515. // skip any node that was previously translated via an id
  516. if (typeof nodeId === 'string' && stringsObj.hasOwnProperty('id:' + nodeId)) {
  517. continue NODE;
  518. }
  519. }
  520.  
  521. if (checkForClasses) {
  522. // skip any node that was previously translated via a class
  523. var nodeClasses = $node.attr('class');
  524.  
  525. if (typeof nodeClasses === 'string') {
  526.  
  527. var nodeClassArray = nodeClasses.split(' '),
  528. nodeClass,
  529. j = 0;
  530.  
  531. for(;nodeClass = nodeClassArray[j++];) {
  532. if (typeof nodeClass === 'string' && stringsObj.hasOwnProperty('class:' + nodeClass)) {
  533. continue NODE;
  534. }
  535. }
  536. }
  537. }
  538.  
  539. // make sure this node contains only one text content
  540. var nodeContents = $node.contents();
  541. if (nodeContents.length === 0 || nodeContents.length > 1) {
  542. $.error('A \'' + string + '\' node was found to contain ' + nodeContents.length + ' child nodes. This node must contain exactly one text node!');
  543.  
  544. continue;
  545. }
  546.  
  547. if (nodeContents[0].nodeType !== 3) {
  548. $.error('A \'' + string + '\' node does not contain a #text node (i.e., type 3)');
  549.  
  550. continue;
  551. }
  552.  
  553. // this node is pushable at this point...
  554. domNodesArray.push($node);
  555.  
  556. // also check the text is the same across the nodes considered
  557. if (typeof domNodeText === 'undefined') {
  558. // ... the first time we store the text of the node
  559. domNodeText = $node.text();
  560. }
  561. else if (domNodeText !== $node.text()) {
  562. // ... then we keep checking if the text node is the same
  563. allNodeTextsAreEqual = false;
  564. }
  565.  
  566. } // end for k loop
  567.  
  568. // make sure that the remaining classes contain the same text
  569. if (!allNodeTextsAreEqual) {
  570. $.error('Not all text content of elements with ' + string + ' were found to be \'' + domNodeText + '\'. So these elements will be ignored.');
  571. }
  572. else {
  573. // all good
  574. refMapping[string] = {
  575. isAttribute : false, // it's a class: or an element: selector
  576. originalText : domNodeText,
  577. domNodes : domNodesArray
  578. };
  579. }
  580. }
  581.  
  582. }; // end of processMultipleElements
  583.  
  584.  
  585. /*
  586. * Then go through classes
  587. */
  588. processMultipleElements('class:', '.', true, false);
  589.  
  590. /*
  591. * Then go through elements
  592. */
  593. processMultipleElements('element:', '', true, true);
  594.  
  595. /*
  596. * Time to process the attributes
  597. */
  598. var firstSelectorStringRegex = new RegExp('(class|id|element):[^:]');
  599. var attrString;
  600. for (i=0; attrString = decompositionObj.attributeStrings[i++];) {
  601.  
  602.  
  603. // let's extract the attribute name from the element selector
  604. var splitStringArray = attrString.split("::");
  605. var attributeString = splitStringArray.shift();
  606.  
  607.  
  608. // sanity check on the format
  609. if (splitStringArray.length === 0) {
  610. $.error('sorry, you need to specify class:, element: or id: selectors in ' + attrString);
  611. }
  612.  
  613. var selectorString = splitStringArray.join('::');
  614.  
  615. if (!splitStringArray[0].match(firstSelectorStringRegex)) {
  616. $.error(attrString + "Doesn't look right. Perhaps you've added extra semicolons?");
  617. }
  618.  
  619.  
  620. // turn selector into jQuery selector
  621. selectorString = selectorString.replace('id:', '#');
  622. selectorString = selectorString.replace('class:', '.');
  623. selectorString = selectorString.replace('element:', '');
  624.  
  625. // find DOM nodes
  626. var $domNodes = $(selectorString + '[' + attributeString + ']');
  627. if ($domNodes.length === 0) {
  628. $.error('The selector "' + attrString + '" does not point to an existing DOM element');
  629. }
  630.  
  631.  
  632. // avoid using Array.prototype.reduce as it's supported in IE9+
  633. var j = 0,
  634. allSameAttributeValue = true;
  635.  
  636. var attributeText = $($domNodes[0]).attr(attributeString);
  637.  
  638. var domNodesToAdd = [];
  639.  
  640. for (j=0; j<$domNodes.length; j++) {
  641. // check the placeholder text is all the same
  642. var $dom = $($domNodes[j]);
  643. if (attributeText !== $dom.attr(attributeString)) {
  644. allSameAttributeValue = false;
  645. }
  646.  
  647. // also add for later...
  648. domNodesToAdd.push($dom);
  649. }
  650. if (!allSameAttributeValue) {
  651. $.error('Not all the attribute values selected via ' + attrString + ' are the same');
  652. }
  653.  
  654. // now we have everything in place, we just add it to the rest!
  655. refMapping[attrString] = {
  656. isAttribute : true, // yes, we are dealing with an attribute here
  657. originalText : attributeText,
  658. domNodes : domNodesToAdd
  659. };
  660. }
  661.  
  662.  
  663. /*
  664. * Finally find the dom nodes associated to any text searched
  665. */
  666. var textString;
  667.  
  668. for (i=0; textString = decompositionObj.textStrings[i++];) {
  669. // nodes that will contain the text to translate
  670. var textNodesToAdd = [];
  671.  
  672. var allParentNodes = $(':contains(' + textString + ')');
  673. var k, parentNode;
  674. for (k=0; parentNode = allParentNodes[k++];) {
  675. var nodeContents = $(parentNode).contents();
  676. if (nodeContents.length === 1 &&
  677. nodeContents[0].nodeType === 3) {
  678.  
  679. textNodesToAdd.push($(parentNode));
  680. }
  681. }
  682. if (textNodesToAdd.length > 0) {
  683. // all good
  684. refMapping[textString] = {
  685. isAttribute: false, // no it's just another dom element
  686. originalText : textString,
  687. domNodes : textNodesToAdd
  688. };
  689. }
  690. }
  691.  
  692. $this.data('refMappingObj', refMapping);
  693.  
  694. return $this;
  695. },
  696. /**
  697. * Calls the user specified callback (if any), then translates the page.
  698. * If the user returned 'false' in his/her callback, the translation is
  699. * not performed.
  700. * @name _mayTranslate
  701. * @function
  702. * @access private
  703. * @param {string} [languageCode] - the language code to translate to
  704. */
  705. '_mayTranslate': function (languageCode) {
  706. var $this = this,
  707. settings = $this.data('settings');
  708.  
  709. if (false !== settings.onLanguageSelected(languageCode)) {
  710. methods._translate.call($this, languageCode);
  711. }
  712. },
  713. /**
  714. * Returns the code of the language currently selected
  715. * @name getSelectedLanguageCode
  716. * @function
  717. * @access public
  718. * @returns {string} [languageCode] - the language code currently selected
  719. */
  720. 'getSelectedLanguageCode' : function () {
  721. var $this = this;
  722. return $this.data('selectedLanguageCode');
  723. },
  724. /**
  725. * Translates the current page.
  726. * @name translate
  727. * @function
  728. * @access public
  729. * @param {string} [languageCode] - the language to translate to.
  730. */
  731. '_translate': function (languageCode) {
  732. var $this = this,
  733. settings = $this.data('settings'),
  734. stringsObj = settings.strings,
  735. refMappingObj = $this.data('refMappingObj');
  736.  
  737. var cssDirection = 'ltr';
  738. if (typeof languageCode !== 'undefined') {
  739. // check if the language code exists actually
  740. if (!settings.languages.hasOwnProperty(languageCode)) {
  741. $.error('The language code ' + languageCode + ' is not defined');
  742. return $this;
  743. }
  744.  
  745. // check if we are dealing with a right to left language
  746. if (settings.languages[languageCode].hasOwnProperty('cssDirection')) {
  747.  
  748. cssDirection = settings.languages[languageCode].cssDirection;
  749. }
  750. }
  751.  
  752. // translate everything according to the reference mapping
  753. var string;
  754. for (string in refMappingObj) {
  755. if (refMappingObj.hasOwnProperty(string)) {
  756.  
  757. // get the translation for this string...
  758. var translation;
  759. if (typeof languageCode === 'undefined' || languageCode === settings.defaultLanguage) {
  760. translation = refMappingObj[string].originalText;
  761. }
  762. else {
  763. translation = stringsObj[string][languageCode];
  764. }
  765. //console.log("翻译的文本:",translation);
  766. //console.log("语言代码:",languageCode);
  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);