diff --git a/jquery.i18n.d.ts b/jquery.i18n.d.ts new file mode 100644 index 0000000..96e98c3 --- /dev/null +++ b/jquery.i18n.d.ts @@ -0,0 +1,399 @@ +/*! + * 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 + */ + +interface JQuery { + /** + * Translate the jquery element based on the data-i18n key + */ + i18n: () => void; +} + +interface JQueryStatic { + i18n: I18NStatic; +} + +interface I18NConstructor { + defaults: I18NOptions; + + new( options: I18NOptions ): I18N; +} + +interface I18NStatic { + locale: string; + fallbacks: { [locale: string]: string[] }; + languages: { [locale: string]: Language }; + messageStore: MessageStore; + parser: MessageParser; + constructor: I18NConstructor; + debug: boolean; + + /** + * Return the current instance of $.I18N + */ + (): I18N; + + /** + * 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[]} parameters Variadic list of parameters for {key}. + * @return {string} Parsed message + */ + ( key: string, ...parameters: string[] ): string + + log( ...args: any[] ): void; +} + +interface I18N { + options: I18NOptions; + parser: MessageParser; + locale: string; + messageStore: MessageStore; + languages: { [locale: string]: Language }; + + /** + * Localize a given messageKey to a locale. + * @param {string} messageKey + * @returns {string} Localized message + */ + localize( messageKey: string ): string; + + /** + * If the data argument is null/undefined/false, + * all cached messages for the i18n instance will get reset. + */ + load(): void; + + /** + * General message loading API + * + * This can take a URL string for + * the json formatted messages. Example: + * ```js + * load('path/to/all_localizations.json'); + * ``` + * + * To load a localization file for a locale: + * ```js + * load('path/to/de-messages.json', 'de' ); + * ``` + * + * To load a localization file from a directory: + * ```js + * 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 use-cases, this is the recommended method. + * It is optional to have trailing slash at end. + * @param source file or directory path + * @param locale optional language tag + */ + load( source: string, locale?: string ): JQuery.Promise; + + /** + * A source map containing key-value pair of language name and locations + * can also be passed. Example: + * ```js + * load( { + * bn: 'i18n/bn.json', + * he: 'i18n/he.json', + * en: 'i18n/en.json' + * } ) + * ``` + * @param source map of language names and location paths + */ + load( source: Record ): JQuery.Promise; + + /** + * A data object containing message key-message translation mappings + * can also be passed. Example: + * + * load( { 'hello' : 'Hello' }, optionalLocale ); + * + * @param data map of message keys and messages + * @param locale optional language tag + */ + load( data: Record, locale?: string ): JQuery.Promise; + + /** + * Does parameter and magic word substitution. + * @param {string} key Message key + * @param {Array} parameters Message parameters + */ + parse( key: string, parameters?: string[] ): string; + + /** + * Destroy the i18n instance. + */ + destroy(): void; +} + +interface I18NOptions { + locale: string; + fallbackLocale: string; + parser: MessageParser; + messageStore: MessageStore; +} + +type ASTNode = string | number | JQuery | ASTNode[]; + +interface MessageParser { + constructor: { + new( options: I18NOptions ): MessageParser; + }; + emitter: MessageParserEmitter; + language: Language; + + simpleParse( message: string, parameters?: string[] ): string; + + parse( message: string, replacements?: string[] ): string; + + ast( message: string ): ASTNode[]; +} + +interface MessageParserEmitter { + constructor: { + new(): MessageParserEmitter; + } + + /** + * (We put this method definition here, and not in prototype, to make + * sure it's not overwritten by any magic.) Walk entire node structure, + * applying replacements and template functions when appropriate + * + * @param {ASTNode} node abstract syntax tree (top node or subnode) + * @param {Array} replacements for $1, $2, ... $n + * @return {ASTNode} single-string node or array of nodes suitable for + * jQuery appending. + */ + emit( node: ASTNode, replacements: string[] ): ASTNode; + + /** + * Parsing has been applied depth-first we can assume that all nodes + * here are single nodes Must return a single node to parents -- a + * jQuery with synthetic span However, unwrap any other synthetic spans + * in our children and pass them upwards + * + * @param {Array} nodes Mixed, some single nodes, some arrays of nodes. + * @return {string} + */ + concat( nodes: ASTNode[] ): string; + + /** + * Return escaped replacement of correct index, or string if + * unavailable. Note that we expect the parsed parameter to be + * zero-based. i.e. $1 should have become [ 0 ]. if the specified + * parameter is not found return the same string (e.g. "$99" -> + * parameter 98 -> not found -> return "$99" ) TODO throw error if + * nodes.length > 1 ? + * + * @param {Array} nodes One element, integer, n >= 0 + * @param {Array} replacements for $1, $2, ... $n + * @return {string} replacement + */ + replace( nodes: [number], replacements: string[] ): string; + + /** + * Transform parsed structure into pluralization n.b. The first node may + * be a non-integer (for instance, a string representing an Arabic + * number). So convert it back with the current language's + * convertNumber. + * + * @return {string} selected pluralized form according to current + * language. + */ + plural( nodes: [string | number, ...string[]] ): string; + + /** + * Transform parsed structure into gender Usage + * {{gender:gender|masculine|feminine|neutral}}. + * + * @return {string} selected gender form according to current language + */ + gender( nodes: [string, string, string, string] ): string + + /** + * Transform parsed structure into grammar conversion. Invoked by + * putting {{grammar:form|word}} in a message + * + * @param {string} nodes.form grammar case, e.g.: genitive + * @param {string} nodes.word word to transform + * @return {string} selected grammatical form according to current + * language. + */ + grammar( nodes: [form: string, word: string] ): string; + + /** + * Wraps argument with unicode control characters for directionality safety + * + * This solves the problem where directionality-neutral characters at the edge of + * the argument string get interpreted with the wrong directionality from the + * enclosing context, giving renderings that look corrupted like "(Ben_(WMF". + * + * The wrapping is LRE...PDF or RLE...PDF, depending on the detected + * directionality of the argument string, using the BIDI algorithm's own "First + * strong directional codepoint" rule. Essentially, this works round the fact that + * there is no embedding equivalent of U+2068 FSI (isolation with heuristic + * direction inference). The latter is cleaner but still not widely supported. + * + * @param {string[]} nodes The text nodes from which to take the first item. + * @return {string} Wrapped String of content as needed. + */ + bidi( nodes: string[] ): string +} + +interface Language { + /** + * CLDR plural rules generated using + * libs/CLDRPluralRuleParser/tools/PluralXML2JSON.html + */ + pluralRules: { [locale: string]: string }; + + /** + * Plural form transformations, needed for some languages. + * + * @param {number} count Non-localized quantifier + * @param {Array} forms List of plural forms + * @return {string} Correct form for quantifier in this language + */ + convertPlural( count: number, forms: string[] ): string; + + /** + * For the number, get the plural for index + * + * @return {number} plural form index + */ + getPluralForm( num: number, pluralRules: { [form: string]: string } ): number; + + /** + * 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 tring. + */ + convertNumber( num: number, integer?: boolean ): string; + + /** + * 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. + */ + convertGrammar( word: string, form: string ): string; + + /** + * 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( gender: string, forms: string[] ): string; + + /** + * Get the digit transform table for the given language + * See http://cldr.unicode.org/translation/numbering-systems + * + * @return {Array|boolean} List of digits in the passed language or false + * representation, or boolean false if there is no information. + */ + digitTransformTable( language: string ): string[] | false; +} + +interface MessageStore { + messages: { [locale: string]: { [messageKey: string]: string } }; + sources: any; + + /** + * If the data argument is null/undefined/false, + * all cached messages for the i18n instance will get reset. + */ + load(): void; + + /** + * General message loading API + * + * This can take a URL string for + * the json formatted messages. Example: + * ```js + * load('path/to/all_localizations.json'); + * ``` + * + * To load a localization file for a locale: + * ```js + * load('path/to/de-messages.json', 'de' ); + * ``` + * + * To load a localization file from a directory: + * ```js + * 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 use-cases, this is the recommended method. + * It is optional to have trailing slash at end. + * @param source file or directory path + * @param locale optional language tag + */ + load( source: string, locale?: string ): JQuery.Promise; + + /** + * A source map containing key-value pair of language name and locations + * can also be passed. Example: + * ```js + * load( { + * bn: 'i18n/bn.json', + * he: 'i18n/he.json', + * en: 'i18n/en.json' + * } ) + * ``` + * @param source map of language names and location paths + */ + load( source: Record ): JQuery.Promise; + + /** + * A data object containing message key-message translation mappings + * can also be passed. Example: + * + * load( { 'hello' : 'Hello' }, optionalLocale ); + * + * @param data map of message keys and messages + * @param locale optional language tag + */ + load( data: Record, locale?: string ): JQuery.Promise; + + /** + * Set messages to the given locale. + * If locale exists, add messages to the locale. + * + * @param {string} locale + * @param {Object} messages + */ + set( locale: string, messages: { [messageKey: string]: string } ): void; + + /** + * Checks if the given locale exists with a given messageKey + * @param {string} locale + * @param {string} messageKey + * @return {boolean} + */ + get( locale: string, messageKey: string ): string | boolean; +} diff --git a/package-lock.json b/package-lock.json index f07b9df..ef49188 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,9 +6,10 @@ "packages": { "": { "name": "@wikimedia/jquery.i18n", - "version": "1.0.8", + "version": "1.0.9", "license": "(MIT OR GPL-2.0-or-later)", "devDependencies": { + "@types/jquery": "^3.5.22", "eslint-config-wikimedia": "0.15.3", "eslint-plugin-qunit": "4.2.0", "grunt": "1.5.3", @@ -52,6 +53,21 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "node_modules/@types/jquery": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.22.tgz", + "integrity": "sha512-ISQFeUK5GwRftLK4PVvKTWEVCxZ2BpaqBz0TWkIq5w4vGojxZP9+XkqgcPjxoqmPeew+HLyWthCBvK7GdF5NYA==", + "dev": true, + "dependencies": { + "@types/sizzle": "*" + } + }, + "node_modules/@types/sizzle": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.4.tgz", + "integrity": "sha512-jA2llq2zNkg8HrALI7DtWzhALcVH0l7i89yhY3iBdOz6cBPeACoFq+fkQrjHA39t1hnSFOboZ7A/AY5MMZSlag==", + "dev": true + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -504,13 +520,20 @@ } }, "node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/deep-is": { @@ -818,12 +841,12 @@ } }, "node_modules/eslint-scope": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", - "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { - "esrecurse": "^4.1.0", + "esrecurse": "^4.3.0", "estraverse": "^4.1.1" }, "engines": { @@ -900,17 +923,26 @@ } }, "node_modules/esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "dependencies": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" }, "engines": { "node": ">=4.0" } }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", @@ -2351,9 +2383,9 @@ } }, "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "dependencies": { "is-extglob": "^2.1.1" @@ -4193,6 +4225,21 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/jquery": { + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.22.tgz", + "integrity": "sha512-ISQFeUK5GwRftLK4PVvKTWEVCxZ2BpaqBz0TWkIq5w4vGojxZP9+XkqgcPjxoqmPeew+HLyWthCBvK7GdF5NYA==", + "dev": true, + "requires": { + "@types/sizzle": "*" + } + }, + "@types/sizzle": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.4.tgz", + "integrity": "sha512-jA2llq2zNkg8HrALI7DtWzhALcVH0l7i89yhY3iBdOz6cBPeACoFq+fkQrjHA39t1hnSFOboZ7A/AY5MMZSlag==", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -4561,12 +4608,12 @@ "dev": true }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "deep-is": { @@ -4816,12 +4863,12 @@ } }, "eslint-scope": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", - "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { - "esrecurse": "^4.1.0", + "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, @@ -4875,12 +4922,20 @@ } }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } } }, "estraverse": { @@ -6008,9 +6063,9 @@ "dev": true }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" diff --git a/package.json b/package.json index ef98d06..f2481f7 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "grunt-contrib-qunit": "3.1.0", "grunt-contrib-watch": "1.1.0", "grunt-eslint": "23.0.0", + "@types/jquery": "^3.5.22", "jquery": "3.5.1", "qunit": "2.19.3" }, @@ -38,5 +39,6 @@ "license": "(MIT OR GPL-2.0-or-later)", "scripts": { "test": "grunt test --verbose" - } + }, + "types": "jquery.i18n.d.ts" }