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