Greasy Fork is available in English.

jquery.localizationTool.js

jquery.localizationTool

2020/05/23のページです。最新版はこちら

このスクリプトは単体で利用できません。右のようなメタデータを含むスクリプトから、ライブラリとして読み込まれます: // @require https://update.greatest.deepsurf.us/scripts/403927/808315/jquerylocalizationTooljs.js

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