diff --git a/i18n/en.json b/i18n/en.json new file mode 100644 index 0000000..def2089 --- /dev/null +++ b/i18n/en.json @@ -0,0 +1,3 @@ +{ + "i-message": "message" +} \ No newline at end of file diff --git a/i18n/zh.json b/i18n/zh.json new file mode 100644 index 0000000..1adb5fc --- /dev/null +++ b/i18n/zh.json @@ -0,0 +1,3 @@ +{ + "i-message": "信息" +} \ No newline at end of file diff --git a/index.html b/index.html index 3dea83c..85e9b7c 100644 --- a/index.html +++ b/index.html @@ -31,6 +31,12 @@ + + + + + + @@ -80,6 +86,7 @@ Source Code Blog Install + @@ -504,10 +511,10 @@
load('path/to/all_localizations.json');
+ *
+ * To load a localization file for a locale:
+ *
+ * load('path/to/de-messages.json', 'de' );
+ *
+ *
+ * To load a localization file from a directory:
+ *
+ * load('path/to/i18n/directory', 'de' );
+ *
+ * The above method has the advantage of fallback resolution.
+ * ie, it will automatically load the fallback locales for de.
+ * For most usecases, this is the recommended method.
+ * It is optional to have trailing slash at end.
+ *
+ * A data object containing message key- message translation mappings
+ * can also be passed. Example:
+ *
+ * load( { 'hello' : 'Hello' }, optionalLocale );
+ *
+ *
+ * A source map containing key-value pair of languagename and locations
+ * can also be passed. Example:
+ *
+ * load( {
+ * bn: 'i18n/bn.json',
+ * he: 'i18n/he.json',
+ * en: 'i18n/en.json'
+ * } )
+ *
+ *
+ * If the data argument is null/undefined/false,
+ * all cached messages for the i18n instance will get reset.
+ *
+ * @param {string|Object} source
+ * @param {string} locale Language tag
+ * @return {jQuery.Promise}
+ */
+ load: function ( source, locale ) {
+ var fallbackLocales, locIndex, fallbackLocale, sourceMap = {};
+ if ( !source && !locale ) {
+ source = 'i18n/' + $.i18n().locale + '.json';
+ locale = $.i18n().locale;
+ }
+ if ( typeof source === 'string' &&
+ // source extension should be json, but can have query params after that.
+ source.split( '?' )[ 0 ].split( '.' ).pop() !== 'json'
+ ) {
+ // Load specified locale then check for fallbacks when directory is
+ // specified in load()
+ sourceMap[ locale ] = source + '/' + locale + '.json';
+ fallbackLocales = ( $.i18n.fallbacks[ locale ] || [] )
+ .concat( this.options.fallbackLocale );
+ for ( locIndex = 0; locIndex < fallbackLocales.length; locIndex++ ) {
+ fallbackLocale = fallbackLocales[ locIndex ];
+ sourceMap[ fallbackLocale ] = source + '/' + fallbackLocale + '.json';
+ }
+ return this.load( sourceMap );
+ } else {
+ return this.messageStore.load( source, locale );
+ }
+
+ },
+
+ /**
+ * Does parameter and magic word substitution.
+ *
+ * @param {string} key Message key
+ * @param {Array} parameters Message parameters
+ * @return {string}
+ */
+ parse: function ( key, parameters ) {
+ var message = this.localize( key );
+ // FIXME: This changes the state of the I18N object,
+ // should probably not change the 'this.parser' but just
+ // pass it to the parser.
+ this.parser.language = $.i18n.languages[ $.i18n().locale ] || $.i18n.languages[ 'default' ];
+ if ( message === '' ) {
+ message = key;
+ }
+ return this.parser.parse( message, parameters );
+ }
+ };
+
+ /**
+ * Process a message from the $.I18N instance
+ * for the current document, stored in jQuery.data(document).
+ *
+ * @param {string} key Key of the message.
+ * @param {string} param1 [param...] Variadic list of parameters for {key}.
+ * @return {string|$.I18N} Parsed message, or if no key was given
+ * the instance of $.I18N is returned.
+ */
+ $.i18n = function ( key, param1 ) {
+ var parameters,
+ i18n = $.data( document, 'i18n' ),
+ options = typeof key === 'object' && key;
+
+ // If the locale option for this call is different then the setup so far,
+ // update it automatically. This doesn't just change the context for this
+ // call but for all future call as well.
+ // If there is no i18n setup yet, don't do this. It will be taken care of
+ // by the `new I18N` construction below.
+ // NOTE: It should only change language for this one call.
+ // Then cache instances of I18N somewhere.
+ if ( options && options.locale && i18n && i18n.locale !== options.locale ) {
+ i18n.locale = options.locale;
+ }
+
+ if ( !i18n ) {
+ i18n = new I18N( options );
+ $.data( document, 'i18n', i18n );
+ }
+
+ if ( typeof key === 'string' ) {
+ if ( param1 !== undefined ) {
+ parameters = slice.call( arguments, 1 );
+ } else {
+ parameters = [];
+ }
+
+ return i18n.parse( key, parameters );
+ } else {
+ // FIXME: remove this feature/bug.
+ return i18n;
+ }
+ };
+
+ $.fn.i18n = function () {
+ var i18n = $.data( document, 'i18n' );
+
+ if ( !i18n ) {
+ i18n = new I18N();
+ $.data( document, 'i18n', i18n );
+ }
+
+ return this.each( function () {
+ var $this = $( this ),
+ messageKey = $this.data( 'i18n' ),
+ lBracket, rBracket, type, key;
+
+ if ( messageKey ) {
+ lBracket = messageKey.indexOf( '[' );
+ rBracket = messageKey.indexOf( ']' );
+ if ( lBracket !== -1 && rBracket !== -1 && lBracket < rBracket ) {
+ type = messageKey.slice( lBracket + 1, rBracket );
+ key = messageKey.slice( rBracket + 1 );
+ if ( type === 'html' ) {
+ $this.html( i18n.parse( key ) );
+ } else {
+ $this.attr( type, i18n.parse( key ) );
+ }
+ } else {
+ $this.text( i18n.parse( messageKey ) );
+ }
+ } else {
+ $this.find( '[data-i18n]' ).i18n();
+ }
+ } );
+ };
+
+ function getDefaultLocale() {
+ var locale = $( 'html' ).attr( 'lang' );
+ if ( !locale ) {
+ locale = navigator.language || navigator.userLanguage || '';
+ }
+ return locale;
+ }
+
+ $.i18n.languages = {};
+ $.i18n.messageStore = $.i18n.messageStore || {};
+ $.i18n.parser = {
+ // The default parser only handles variable substitution
+ parse: function ( message, parameters ) {
+ return message.replace( /\$(\d+)/g, function ( str, match ) {
+ var index = parseInt( match, 10 ) - 1;
+ return parameters[ index ] !== undefined ? parameters[ index ] : '$' + match;
+ } );
+ },
+ emitter: {}
+ };
+ $.i18n.fallbacks = {};
+ $.i18n.debug = false;
+ $.i18n.log = function ( /* arguments */ ) {
+ if ( window.console && $.i18n.debug ) {
+ window.console.log.apply( window.console, arguments );
+ }
+ };
+ /* Static members */
+ I18N.defaults = {
+ locale: getDefaultLocale(),
+ fallbackLocale: 'en',
+ parser: $.i18n.parser,
+ messageStore: $.i18n.messageStore
+ };
+
+ // Expose constructor
+ $.i18n.constructor = I18N;
+}( jQuery ) );
diff --git a/scripts/jquery.i18n.language.js b/scripts/jquery.i18n.language.js
new file mode 100644
index 0000000..daabfaf
--- /dev/null
+++ b/scripts/jquery.i18n.language.js
@@ -0,0 +1,499 @@
+/* global pluralRuleParser */
+( function ( $ ) {
+ 'use strict';
+
+ // jscs:disable
+ var language = {
+ // CLDR plural rules generated using
+ // libs/CLDRPluralRuleParser/tools/PluralXML2JSON.html
+ pluralRules: {
+ ak: {
+ one: 'n = 0..1'
+ },
+ am: {
+ one: 'i = 0 or n = 1'
+ },
+ ar: {
+ zero: 'n = 0',
+ one: 'n = 1',
+ two: 'n = 2',
+ few: 'n % 100 = 3..10',
+ many: 'n % 100 = 11..99'
+ },
+ ars: {
+ zero: 'n = 0',
+ one: 'n = 1',
+ two: 'n = 2',
+ few: 'n % 100 = 3..10',
+ many: 'n % 100 = 11..99'
+ },
+ as: {
+ one: 'i = 0 or n = 1'
+ },
+ be: {
+ one: 'n % 10 = 1 and n % 100 != 11',
+ few: 'n % 10 = 2..4 and n % 100 != 12..14',
+ many: 'n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14'
+ },
+ bh: {
+ one: 'n = 0..1'
+ },
+ bn: {
+ one: 'i = 0 or n = 1'
+ },
+ br: {
+ one: 'n % 10 = 1 and n % 100 != 11,71,91',
+ two: 'n % 10 = 2 and n % 100 != 12,72,92',
+ few: 'n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99',
+ many: 'n != 0 and n % 1000000 = 0'
+ },
+ bs: {
+ one: 'v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11',
+ few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14'
+ },
+ cs: {
+ one: 'i = 1 and v = 0',
+ few: 'i = 2..4 and v = 0',
+ many: 'v != 0'
+ },
+ cy: {
+ zero: 'n = 0',
+ one: 'n = 1',
+ two: 'n = 2',
+ few: 'n = 3',
+ many: 'n = 6'
+ },
+ da: {
+ one: 'n = 1 or t != 0 and i = 0,1'
+ },
+ dsb: {
+ one: 'v = 0 and i % 100 = 1 or f % 100 = 1',
+ two: 'v = 0 and i % 100 = 2 or f % 100 = 2',
+ few: 'v = 0 and i % 100 = 3..4 or f % 100 = 3..4'
+ },
+ fa: {
+ one: 'i = 0 or n = 1'
+ },
+ ff: {
+ one: 'i = 0,1'
+ },
+ fil: {
+ one: 'v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9'
+ },
+ fr: {
+ one: 'i = 0,1'
+ },
+ ga: {
+ one: 'n = 1',
+ two: 'n = 2',
+ few: 'n = 3..6',
+ many: 'n = 7..10'
+ },
+ gd: {
+ one: 'n = 1,11',
+ two: 'n = 2,12',
+ few: 'n = 3..10,13..19'
+ },
+ gu: {
+ one: 'i = 0 or n = 1'
+ },
+ guw: {
+ one: 'n = 0..1'
+ },
+ gv: {
+ one: 'v = 0 and i % 10 = 1',
+ two: 'v = 0 and i % 10 = 2',
+ few: 'v = 0 and i % 100 = 0,20,40,60,80',
+ many: 'v != 0'
+ },
+ he: {
+ one: 'i = 1 and v = 0',
+ two: 'i = 2 and v = 0',
+ many: 'v = 0 and n != 0..10 and n % 10 = 0'
+ },
+ hi: {
+ one: 'i = 0 or n = 1'
+ },
+ hr: {
+ one: 'v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11',
+ few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14'
+ },
+ hsb: {
+ one: 'v = 0 and i % 100 = 1 or f % 100 = 1',
+ two: 'v = 0 and i % 100 = 2 or f % 100 = 2',
+ few: 'v = 0 and i % 100 = 3..4 or f % 100 = 3..4'
+ },
+ hy: {
+ one: 'i = 0,1'
+ },
+ is: {
+ one: 't = 0 and i % 10 = 1 and i % 100 != 11 or t != 0'
+ },
+ iu: {
+ one: 'n = 1',
+ two: 'n = 2'
+ },
+ iw: {
+ one: 'i = 1 and v = 0',
+ two: 'i = 2 and v = 0',
+ many: 'v = 0 and n != 0..10 and n % 10 = 0'
+ },
+ kab: {
+ one: 'i = 0,1'
+ },
+ kn: {
+ one: 'i = 0 or n = 1'
+ },
+ kw: {
+ one: 'n = 1',
+ two: 'n = 2'
+ },
+ lag: {
+ zero: 'n = 0',
+ one: 'i = 0,1 and n != 0'
+ },
+ ln: {
+ one: 'n = 0..1'
+ },
+ lt: {
+ one: 'n % 10 = 1 and n % 100 != 11..19',
+ few: 'n % 10 = 2..9 and n % 100 != 11..19',
+ many: 'f != 0'
+ },
+ lv: {
+ zero: 'n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19',
+ one: 'n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1'
+ },
+ mg: {
+ one: 'n = 0..1'
+ },
+ mk: {
+ one: 'v = 0 and i % 10 = 1 or f % 10 = 1'
+ },
+ mo: {
+ one: 'i = 1 and v = 0',
+ few: 'v != 0 or n = 0 or n != 1 and n % 100 = 1..19'
+ },
+ mr: {
+ one: 'i = 0 or n = 1'
+ },
+ mt: {
+ one: 'n = 1',
+ few: 'n = 0 or n % 100 = 2..10',
+ many: 'n % 100 = 11..19'
+ },
+ naq: {
+ one: 'n = 1',
+ two: 'n = 2'
+ },
+ nso: {
+ one: 'n = 0..1'
+ },
+ pa: {
+ one: 'n = 0..1'
+ },
+ pl: {
+ one: 'i = 1 and v = 0',
+ few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14',
+ many: 'v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14'
+ },
+ prg: {
+ zero: 'n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19',
+ one: 'n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1'
+ },
+ pt: {
+ one: 'i = 0..1'
+ },
+ ro: {
+ one: 'i = 1 and v = 0',
+ few: 'v != 0 or n = 0 or n != 1 and n % 100 = 1..19'
+ },
+ ru: {
+ one: 'v = 0 and i % 10 = 1 and i % 100 != 11',
+ few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14',
+ many: 'v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14'
+ },
+ se: {
+ one: 'n = 1',
+ two: 'n = 2'
+ },
+ sh: {
+ one: 'v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11',
+ few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14'
+ },
+ shi: {
+ one: 'i = 0 or n = 1',
+ few: 'n = 2..10'
+ },
+ si: {
+ one: 'n = 0,1 or i = 0 and f = 1'
+ },
+ sk: {
+ one: 'i = 1 and v = 0',
+ few: 'i = 2..4 and v = 0',
+ many: 'v != 0'
+ },
+ sl: {
+ one: 'v = 0 and i % 100 = 1',
+ two: 'v = 0 and i % 100 = 2',
+ few: 'v = 0 and i % 100 = 3..4 or v != 0'
+ },
+ sma: {
+ one: 'n = 1',
+ two: 'n = 2'
+ },
+ smi: {
+ one: 'n = 1',
+ two: 'n = 2'
+ },
+ smj: {
+ one: 'n = 1',
+ two: 'n = 2'
+ },
+ smn: {
+ one: 'n = 1',
+ two: 'n = 2'
+ },
+ sms: {
+ one: 'n = 1',
+ two: 'n = 2'
+ },
+ sr: {
+ one: 'v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11',
+ few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14'
+ },
+ ti: {
+ one: 'n = 0..1'
+ },
+ tl: {
+ one: 'v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9'
+ },
+ tzm: {
+ one: 'n = 0..1 or n = 11..99'
+ },
+ uk: {
+ one: 'v = 0 and i % 10 = 1 and i % 100 != 11',
+ few: 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14',
+ many: 'v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14'
+ },
+ wa: {
+ one: 'n = 0..1'
+ },
+ zu: {
+ one: 'i = 0 or n = 1'
+ }
+ },
+ // jscs:enable
+
+ /**
+ * Plural form transformations, needed for some languages.
+ *
+ * @param {integer} count
+ * Non-localized quantifier
+ * @param {Array} forms
+ * List of plural forms
+ * @return {string} Correct form for quantifier in this language
+ */
+ convertPlural: function ( count, forms ) {
+ var pluralRules,
+ pluralFormIndex,
+ index,
+ explicitPluralPattern = new RegExp( '\\d+=', 'i' ),
+ formCount,
+ form;
+
+ if ( !forms || forms.length === 0 ) {
+ return '';
+ }
+
+ // Handle for Explicit 0= & 1= values
+ for ( index = 0; index < forms.length; index++ ) {
+ form = forms[ index ];
+ if ( explicitPluralPattern.test( form ) ) {
+ formCount = parseInt( form.slice( 0, form.indexOf( '=' ) ), 10 );
+ if ( formCount === count ) {
+ return ( form.slice( form.indexOf( '=' ) + 1 ) );
+ }
+ forms[ index ] = undefined;
+ }
+ }
+
+ forms = $.map( forms, function ( form ) {
+ if ( form !== undefined ) {
+ return form;
+ }
+ } );
+
+ pluralRules = this.pluralRules[ $.i18n().locale ];
+
+ if ( !pluralRules ) {
+ // default fallback.
+ return ( count === 1 ) ? forms[ 0 ] : forms[ 1 ];
+ }
+
+ pluralFormIndex = this.getPluralForm( count, pluralRules );
+ pluralFormIndex = Math.min( pluralFormIndex, forms.length - 1 );
+
+ return forms[ pluralFormIndex ];
+ },
+
+ /**
+ * For the number, get the plural for index
+ *
+ * @param {integer} number
+ * @param {Object} pluralRules
+ * @return {integer} plural form index
+ */
+ getPluralForm: function ( number, pluralRules ) {
+ var i,
+ pluralForms = [ 'zero', 'one', 'two', 'few', 'many', 'other' ],
+ pluralFormIndex = 0;
+
+ for ( i = 0; i < pluralForms.length; i++ ) {
+ if ( pluralRules[ pluralForms[ i ] ] ) {
+ if ( pluralRuleParser( pluralRules[ pluralForms[ i ] ], number ) ) {
+ return pluralFormIndex;
+ }
+
+ pluralFormIndex++;
+ }
+ }
+
+ return pluralFormIndex;
+ },
+
+ /**
+ * Converts a number using digitTransformTable.
+ *
+ * @param {number} num Value to be converted
+ * @param {boolean} integer Convert the return value to an integer
+ * @return {string} The number converted into a String.
+ */
+ convertNumber: function ( num, integer ) {
+ var tmp, item, i,
+ transformTable, numberString, convertedNumber;
+
+ // Set the target Transform table:
+ transformTable = this.digitTransformTable( $.i18n().locale );
+ numberString = String( num );
+ convertedNumber = '';
+
+ if ( !transformTable ) {
+ return num;
+ }
+
+ // Check if the restore to Latin number flag is set:
+ if ( integer ) {
+ if ( parseFloat( num, 10 ) === num ) {
+ return num;
+ }
+
+ tmp = [];
+
+ for ( item in transformTable ) {
+ tmp[ transformTable[ item ] ] = item;
+ }
+
+ transformTable = tmp;
+ }
+
+ for ( i = 0; i < numberString.length; i++ ) {
+ if ( transformTable[ numberString[ i ] ] ) {
+ convertedNumber += transformTable[ numberString[ i ] ];
+ } else {
+ convertedNumber += numberString[ i ];
+ }
+ }
+
+ return integer ? parseFloat( convertedNumber, 10 ) : convertedNumber;
+ },
+
+ /**
+ * Grammatical transformations, needed for inflected languages.
+ * Invoked by putting {{grammar:form|word}} in a message.
+ * Override this method for languages that need special grammar rules
+ * applied dynamically.
+ *
+ * @param {string} word
+ * @param {string} form
+ * @return {string}
+ */
+ // eslint-disable-next-line no-unused-vars
+ convertGrammar: function ( word, form ) {
+ return word;
+ },
+
+ /**
+ * Provides an alternative text depending on specified gender. Usage
+ * {{gender:[gender|user object]|masculine|feminine|neutral}}. If second
+ * or third parameter are not specified, masculine is used.
+ *
+ * These details may be overriden per language.
+ *
+ * @param {string} gender
+ * male, female, or anything else for neutral.
+ * @param {Array} forms
+ * List of gender forms
+ *
+ * @return {string}
+ */
+ gender: function ( gender, forms ) {
+ if ( !forms || forms.length === 0 ) {
+ return '';
+ }
+
+ while ( forms.length < 2 ) {
+ forms.push( forms[ forms.length - 1 ] );
+ }
+
+ if ( gender === 'male' ) {
+ return forms[ 0 ];
+ }
+
+ if ( gender === 'female' ) {
+ return forms[ 1 ];
+ }
+
+ return ( forms.length === 3 ) ? forms[ 2 ] : forms[ 0 ];
+ },
+
+ /**
+ * Get the digit transform table for the given language
+ * See http://cldr.unicode.org/translation/numbering-systems
+ *
+ * @param {string} language
+ * @return {Array|boolean} List of digits in the passed language or false
+ * representation, or boolean false if there is no information.
+ */
+ digitTransformTable: function ( language ) {
+ var tables = {
+ ar: '٠١٢٣٤٥٦٧٨٩',
+ fa: '۰۱۲۳۴۵۶۷۸۹',
+ ml: '൦൧൨൩൪൫൬൭൮൯',
+ kn: '೦೧೨೩೪೫೬೭೮೯',
+ lo: '໐໑໒໓໔໕໖໗໘໙',
+ or: '୦୧୨୩୪୫୬୭୮୯',
+ kh: '០១២៣៤៥៦៧៨៩',
+ nqo: '߀߁߂߃߄߅߆߇߈߉', // Note that the digits go right to left
+ pa: '੦੧੨੩੪੫੬੭੮੯',
+ gu: '૦૧૨૩૪૫૬૭૮૯',
+ hi: '०१२३४५६७८९',
+ my: '၀၁၂၃၄၅၆၇၈၉',
+ ta: '௦௧௨௩௪௫௬௭௮௯',
+ te: '౦౧౨౩౪౫౬౭౮౯',
+ th: '๐๑๒๓๔๕๖๗๘๙', // FIXME use iso 639 codes
+ bo: '༠༡༢༣༤༥༦༧༨༩' // FIXME use iso 639 codes
+ };
+
+ if ( !tables[ language ] ) {
+ return false;
+ }
+
+ return tables[ language ].split( '' );
+ }
+ };
+
+ $.extend( $.i18n.languages, {
+ default: language
+ } );
+}( jQuery ) );
diff --git a/scripts/jquery.i18n.messagestore.js b/scripts/jquery.i18n.messagestore.js
new file mode 100644
index 0000000..ba81ce5
--- /dev/null
+++ b/scripts/jquery.i18n.messagestore.js
@@ -0,0 +1,123 @@
+/*!
+ * jQuery Internationalization library - Message Store
+ *
+ * Copyright (C) 2012 Santhosh Thottingal
+ *
+ * jquery.i18n is dual licensed GPLv2 or later and MIT. You don't have to do anything special to
+ * choose one license or the other and you don't have to notify anyone which license you are using.
+ * You are free to use UniversalLanguageSelector in commercial projects as long as the copyright
+ * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details.
+ *
+ * @licence GNU General Public Licence 2.0 or later
+ * @licence MIT License
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ var MessageStore = function () {
+ this.messages = {};
+ this.sources = {};
+ };
+
+ function jsonMessageLoader( url ) {
+ var deferred = $.Deferred();
+
+ $.getJSON( url )
+ .done( deferred.resolve )
+ .fail( function ( jqxhr, settings, exception ) {
+ $.i18n.log( 'Error in loading messages from ' + url + ' Exception: ' + exception );
+ // Ignore 404 exception, because we are handling fallabacks explicitly
+ deferred.resolve();
+ } );
+
+ return deferred.promise();
+ }
+
+ /**
+ * See https://github.com/wikimedia/jquery.i18n/wiki/Specification#wiki-Message_File_Loading
+ */
+ MessageStore.prototype = {
+
+ /**
+ * General message loading API This can take a URL string for
+ * the json formatted messages.
+ * load('path/to/all_localizations.json');
+ *
+ * This can also load a localization file for a locale
+ * load( 'path/to/de-messages.json', 'de' );
+ *
+ * A data object containing message key- message translation mappings
+ * can also be passed Eg:
+ *
+ * load( { 'hello' : 'Hello' }, optionalLocale );
+ *
If the data argument is
+ * null/undefined/false,
+ * all cached messages for the i18n instance will get reset.
+ *
+ * @param {string|Object} source
+ * @param {string} locale Language tag
+ * @return {jQuery.Promise}
+ */
+ load: function ( source, locale ) {
+ var key = null,
+ deferreds = [],
+ messageStore = this;
+
+ if ( typeof source === 'string' ) {
+ // This is a URL to the messages file.
+ $.i18n.log( 'Loading messages from: ' + source );
+ return jsonMessageLoader( source )
+ .then( function ( localization ) {
+ return messageStore.load( localization, locale );
+ } );
+ }
+
+ if ( locale ) {
+ // source is an key-value pair of messages for given locale
+ messageStore.set( locale, source );
+
+ return $.Deferred().resolve();
+ } else {
+ // source is a key-value pair of locales and their source
+ for ( key in source ) {
+ if ( Object.prototype.hasOwnProperty.call( source, key ) ) {
+ locale = key;
+ // No {locale} given, assume data is a group of languages,
+ // call this function again for each language.
+ deferreds.push( messageStore.load( source[ key ], locale ) );
+ }
+ }
+ return $.when.apply( $, deferreds );
+ }
+
+ },
+
+ /**
+ * Set messages to the given locale.
+ * If locale exists, add messages to the locale.
+ *
+ * @param {string} locale
+ * @param {Object} messages
+ */
+ set: function ( locale, messages ) {
+ if ( !this.messages[ locale ] ) {
+ this.messages[ locale ] = messages;
+ } else {
+ this.messages[ locale ] = $.extend( this.messages[ locale ], messages );
+ }
+ },
+
+ /**
+ *
+ * @param {string} locale
+ * @param {string} messageKey
+ * @return {boolean}
+ */
+ get: function ( locale, messageKey ) {
+ return this.messages[ locale ] && this.messages[ locale ][ messageKey ];
+ }
+ };
+
+ $.extend( $.i18n.messageStore, new MessageStore() );
+}( jQuery ) );
diff --git a/scripts/jquery.i18n.parser.js b/scripts/jquery.i18n.parser.js
new file mode 100644
index 0000000..8c47e55
--- /dev/null
+++ b/scripts/jquery.i18n.parser.js
@@ -0,0 +1,310 @@
+/*!
+ * jQuery Internationalization library
+ *
+ * Copyright (C) 2011-2013 Santhosh Thottingal, Neil Kandalgaonkar
+ *
+ * jquery.i18n is dual licensed GPLv2 or later and MIT. You don't have to do
+ * anything special to choose one license or the other and you don't have to
+ * notify anyone which license you are using. You are free to use
+ * UniversalLanguageSelector in commercial projects as long as the copyright
+ * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details.
+ *
+ * @licence GNU General Public Licence 2.0 or later
+ * @licence MIT License
+ */
+
+( function ( $ ) {
+ 'use strict';
+
+ var MessageParser = function ( options ) {
+ this.options = $.extend( {}, $.i18n.parser.defaults, options );
+ this.language = $.i18n.languages[ String.locale ] || $.i18n.languages[ 'default' ];
+ this.emitter = $.i18n.parser.emitter;
+ };
+
+ MessageParser.prototype = {
+
+ constructor: MessageParser,
+
+ simpleParse: function ( message, parameters ) {
+ return message.replace( /\$(\d+)/g, function ( str, match ) {
+ var index = parseInt( match, 10 ) - 1;
+
+ return parameters[ index ] !== undefined ? parameters[ index ] : '$' + match;
+ } );
+ },
+
+ parse: function ( message, replacements ) {
+ if ( message.indexOf( '{{' ) < 0 ) {
+ return this.simpleParse( message, replacements );
+ }
+
+ this.emitter.language = $.i18n.languages[ $.i18n().locale ] ||
+ $.i18n.languages[ 'default' ];
+
+ return this.emitter.emit( this.ast( message ), replacements );
+ },
+
+ ast: function ( message ) {
+ var pipe, colon, backslash, anyCharacter, dollar, digits, regularLiteral,
+ regularLiteralWithoutBar, regularLiteralWithoutSpace, escapedOrLiteralWithoutBar,
+ escapedOrRegularLiteral, templateContents, templateName, openTemplate,
+ closeTemplate, expression, paramExpression, result,
+ pos = 0;
+
+ // Try parsers until one works, if none work return null
+ function choice( parserSyntax ) {
+ return function () {
+ var i, result;
+
+ for ( i = 0; i < parserSyntax.length; i++ ) {
+ result = parserSyntax[ i ]();
+
+ if ( result !== null ) {
+ return result;
+ }
+ }
+
+ return null;
+ };
+ }
+
+ // Try several parserSyntax-es in a row.
+ // All must succeed; otherwise, return null.
+ // This is the only eager one.
+ function sequence( parserSyntax ) {
+ var i, res,
+ originalPos = pos,
+ result = [];
+
+ for ( i = 0; i < parserSyntax.length; i++ ) {
+ res = parserSyntax[ i ]();
+
+ if ( res === null ) {
+ pos = originalPos;
+
+ return null;
+ }
+
+ result.push( res );
+ }
+
+ return result;
+ }
+
+ // Run the same parser over and over until it fails.
+ // Must succeed a minimum of n times; otherwise, return null.
+ function nOrMore( n, p ) {
+ return function () {
+ var originalPos = pos,
+ result = [],
+ parsed = p();
+
+ while ( parsed !== null ) {
+ result.push( parsed );
+ parsed = p();
+ }
+
+ if ( result.length < n ) {
+ pos = originalPos;
+
+ return null;
+ }
+
+ return result;
+ };
+ }
+
+ // Helpers -- just make parserSyntax out of simpler JS builtin types
+
+ function makeStringParser( s ) {
+ var len = s.length;
+
+ return function () {
+ var result = null;
+
+ if ( message.slice( pos, pos + len ) === s ) {
+ result = s;
+ pos += len;
+ }
+
+ return result;
+ };
+ }
+
+ function makeRegexParser( regex ) {
+ return function () {
+ var matches = message.slice( pos ).match( regex );
+
+ if ( matches === null ) {
+ return null;
+ }
+
+ pos += matches[ 0 ].length;
+
+ return matches[ 0 ];
+ };
+ }
+
+ pipe = makeStringParser( '|' );
+ colon = makeStringParser( ':' );
+ backslash = makeStringParser( '\\' );
+ anyCharacter = makeRegexParser( /^./ );
+ dollar = makeStringParser( '$' );
+ digits = makeRegexParser( /^\d+/ );
+ regularLiteral = makeRegexParser( /^[^{}[\]$\\]/ );
+ regularLiteralWithoutBar = makeRegexParser( /^[^{}[\]$\\|]/ );
+ regularLiteralWithoutSpace = makeRegexParser( /^[^{}[\]$\s]/ );
+
+ // There is a general pattern:
+ // parse a thing;
+ // if it worked, apply transform,
+ // otherwise return null.
+ // But using this as a combinator seems to cause problems
+ // when combined with nOrMore().
+ // May be some scoping issue.
+ function transform( p, fn ) {
+ return function () {
+ var result = p();
+
+ return result === null ? null : fn( result );
+ };
+ }
+
+ // Used to define "literals" within template parameters. The pipe
+ // character is the parameter delimeter, so by default
+ // it is not a literal in the parameter
+ function literalWithoutBar() {
+ var result = nOrMore( 1, escapedOrLiteralWithoutBar )();
+
+ return result === null ? null : result.join( '' );
+ }
+
+ function literal() {
+ var result = nOrMore( 1, escapedOrRegularLiteral )();
+
+ return result === null ? null : result.join( '' );
+ }
+
+ function escapedLiteral() {
+ var result = sequence( [ backslash, anyCharacter ] );
+
+ return result === null ? null : result[ 1 ];
+ }
+
+ choice( [ escapedLiteral, regularLiteralWithoutSpace ] );
+ escapedOrLiteralWithoutBar = choice( [ escapedLiteral, regularLiteralWithoutBar ] );
+ escapedOrRegularLiteral = choice( [ escapedLiteral, regularLiteral ] );
+
+ function replacement() {
+ var result = sequence( [ dollar, digits ] );
+
+ if ( result === null ) {
+ return null;
+ }
+
+ return [ 'REPLACE', parseInt( result[ 1 ], 10 ) - 1 ];
+ }
+
+ templateName = transform(
+ // see $wgLegalTitleChars
+ // not allowing : due to the need to catch "PLURAL:$1"
+ makeRegexParser( /^[ !"$&'()*,./0-9;=?@A-Z^_`a-z~\x80-\xFF+-]+/ ),
+
+ function ( result ) {
+ return result.toString();
+ }
+ );
+
+ function templateParam() {
+ var expr,
+ result = sequence( [ pipe, nOrMore( 0, paramExpression ) ] );
+
+ if ( result === null ) {
+ return null;
+ }
+
+ expr = result[ 1 ];
+
+ // use a "CONCAT" operator if there are multiple nodes,
+ // otherwise return the first node, raw.
+ return expr.length > 1 ? [ 'CONCAT' ].concat( expr ) : expr[ 0 ];
+ }
+
+ function templateWithReplacement() {
+ var result = sequence( [ templateName, colon, replacement ] );
+
+ return result === null ? null : [ result[ 0 ], result[ 2 ] ];
+ }
+
+ function templateWithOutReplacement() {
+ var result = sequence( [ templateName, colon, paramExpression ] );
+
+ return result === null ? null : [ result[ 0 ], result[ 2 ] ];
+ }
+
+ templateContents = choice( [
+ function () {
+ var res = sequence( [
+ // templates can have placeholders for dynamic
+ // replacement eg: {{PLURAL:$1|one car|$1 cars}}
+ // or no placeholders eg:
+ // {{GRAMMAR:genitive|{{SITENAME}}}
+ choice( [ templateWithReplacement, templateWithOutReplacement ] ),
+ nOrMore( 0, templateParam )
+ ] );
+
+ return res === null ? null : res[ 0 ].concat( res[ 1 ] );
+ },
+ function () {
+ var res = sequence( [ templateName, nOrMore( 0, templateParam ) ] );
+
+ if ( res === null ) {
+ return null;
+ }
+
+ return [ res[ 0 ] ].concat( res[ 1 ] );
+ }
+ ] );
+
+ openTemplate = makeStringParser( '{{' );
+ closeTemplate = makeStringParser( '}}' );
+
+ function template() {
+ var result = sequence( [ openTemplate, templateContents, closeTemplate ] );
+
+ return result === null ? null : result[ 1 ];
+ }
+
+ expression = choice( [ template, replacement, literal ] );
+ paramExpression = choice( [ template, replacement, literalWithoutBar ] );
+
+ function start() {
+ var result = nOrMore( 0, expression )();
+
+ if ( result === null ) {
+ return null;
+ }
+
+ return [ 'CONCAT' ].concat( result );
+ }
+
+ result = start();
+
+ /*
+ * For success, the pos must have gotten to the end of the input
+ * and returned a non-null.
+ * n.b. This is part of language infrastructure, so we do not throw an
+ * internationalizable message.
+ */
+ if ( result === null || pos !== message.length ) {
+ throw new Error( 'Parse error at position ' + pos.toString() + ' in input: ' + message );
+ }
+
+ return result;
+ }
+
+ };
+
+ $.extend( $.i18n.parser, new MessageParser() );
+}( jQuery ) );
diff --git a/scripts/main.js b/scripts/main.js
index d676dd6..0074c88 100644
--- a/scripts/main.js
+++ b/scripts/main.js
@@ -1,4 +1,15 @@
jQuery(document).ready(function($) {
+ var i18n = $.i18n()
+ i18n.load({
+ en: 'i18n/en.json',
+ zh: 'i18n/zh.json'
+ })
+ var localStorageLang = window.localStorage.getItem('mask_language')
+ var lang = localStorageLang ?
+ localStorageLang : ['zh', 'en'].includes(navigator.language) ?
+ navigator.language : 'en'
+ setLocale(lang)
+
$('a[href$="/install"]').attr("href", function() {
const agent = navigator.userAgent;
if (/Chrome/i.test(agent)) {
@@ -67,4 +78,20 @@ jQuery(document).ready(function($) {
setCardGroup($(this).index());
});
});
+
+ $('#i18n-en').on("click", function() {
+ setLocale('en', true)
+ })
+
+ $('#i18n-zh').on("click", function() {
+ setLocale('zh', true)
+ })
+
+ function setLocale (lang, cache = false) {
+ setTimeout(() => {
+ i18n.locale = lang
+ if (cache) window.localStorage.setItem('mask_language', lang)
+ $('body').i18n()
+ }, 1000)
+ }
});