From fbc430248bab32a442c65b64ba83be1b2961ebff Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tim=20D=C3=BCsterhus?= Date: Thu, 7 Jan 2021 15:26:07 +0100 Subject: [PATCH] Improve type safety of Plural.ts --- .../files/js/WoltLabSuite/Core/I18n/Plural.js | 315 ++++++----- .../files/ts/WoltLabSuite/Core/I18n/Plural.ts | 516 +++++++++--------- 2 files changed, 422 insertions(+), 409 deletions(-) diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/I18n/Plural.js b/wcfsetup/install/files/js/WoltLabSuite/Core/I18n/Plural.js index 042ba9f01a..f52ac4e716 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/I18n/Plural.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/I18n/Plural.js @@ -9,123 +9,50 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, exports, tslib_1, StringUtil) { "use strict"; StringUtil = tslib_1.__importStar(StringUtil); - const PLURAL_FEW = "few"; - const PLURAL_MANY = "many"; - const PLURAL_ONE = "one"; - const PLURAL_OTHER = "other"; - const PLURAL_TWO = "two"; - const PLURAL_ZERO = "zero"; - const Plural = { - /** - * Returns the plural category for the given value. - */ - getCategory(value, languageCode) { - if (!languageCode) { - languageCode = document.documentElement.lang; - } - // Fallback: handle unknown languages as English - if (typeof Plural[languageCode] !== "function") { - languageCode = "en"; - } - const category = Plural[languageCode](value); - if (category) { - return category; - } - return PLURAL_OTHER; - }, - /** - * Returns the value for a `plural` element used in the template. - * - * @see wcf\system\template\plugin\PluralFunctionTemplatePlugin::execute() - */ - getCategoryFromTemplateParameters(parameters) { - if (!parameters["value"]) { - throw new Error("Missing parameter value"); - } - if (!parameters["other"]) { - throw new Error("Missing parameter other"); - } - let value = parameters["value"]; - if (Array.isArray(value)) { - value = value.length; - } - // handle numeric attributes - const numericAttribute = Object.keys(parameters).find((key) => { - return key.toString() === (~~key).toString() && key == value; - }); - if (numericAttribute) { - return numericAttribute; - } - let category = Plural.getCategory(value); - if (!parameters[category]) { - category = PLURAL_OTHER; - } - const string = parameters[category]; - if (string.indexOf("#") !== -1) { - return string.replace("#", StringUtil.formatNumeric(value)); - } - return string; - }, - /** - * `f` is the fractional number as a whole number (1.234 yields 234) - */ - getF(n) { - const tmp = n.toString(); - const pos = tmp.indexOf("."); - if (pos === -1) { - return 0; - } - return parseInt(tmp.substr(pos + 1), 10); - }, - /** - * `v` represents the number of digits of the fractional part (1.234 yields 3) - */ - getV(n) { - return n.toString().replace(/^[^.]*\.?/, "").length; - }, + const Languages = { // Afrikaans af(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Amharic am(n) { const i = Math.floor(Math.abs(n)); if (n == 1 || i === 0) { - return PLURAL_ONE; + return "one" /* One */; } }, // Arabic ar(n) { if (n == 0) { - return PLURAL_ZERO; + return "zero" /* Zero */; } if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } if (n == 2) { - return PLURAL_TWO; + return "two" /* Two */; } const mod100 = n % 100; if (mod100 >= 3 && mod100 <= 10) { - return PLURAL_FEW; + return "few" /* Few */; } if (mod100 >= 11 && mod100 <= 99) { - return PLURAL_MANY; + return "many" /* Many */; } }, // Assamese as(n) { const i = Math.floor(Math.abs(n)); if (n == 1 || i === 0) { - return PLURAL_ONE; + return "one" /* One */; } }, // Azerbaijani az(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Belarusian @@ -133,26 +60,26 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo const mod10 = n % 10; const mod100 = n % 100; if (mod10 == 1 && mod100 != 11) { - return PLURAL_ONE; + return "one" /* One */; } if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) { - return PLURAL_FEW; + return "few" /* Few */; } if (mod10 == 0 || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 11 && mod100 <= 14)) { - return PLURAL_MANY; + return "many" /* Many */; } }, // Bulgarian bg(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Bengali bn(n) { const i = Math.floor(Math.abs(n)); if (n == 1 || i === 0) { - return PLURAL_ONE; + return "one" /* One */; } }, // Tibetan @@ -168,54 +95,54 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo const fMod10 = f % 10; const fMod100 = f % 100; if ((v == 0 && mod10 == 1 && mod100 != 11) || (fMod10 == 1 && fMod100 != 11)) { - return PLURAL_ONE; + return "one" /* One */; } if ((v == 0 && mod10 >= 2 && mod10 <= 4 && mod100 >= 12 && mod100 <= 14) || (fMod10 >= 2 && fMod10 <= 4 && fMod100 >= 12 && fMod100 <= 14)) { - return PLURAL_FEW; + return "few" /* Few */; } }, // Czech cs(n) { const v = Plural.getV(n); if (n == 1 && v === 0) { - return PLURAL_ONE; + return "one" /* One */; } if (n >= 2 && n <= 4 && v === 0) { - return PLURAL_FEW; + return "few" /* Few */; } if (v === 0) { - return PLURAL_MANY; + return "many" /* Many */; } }, // Welsh cy(n) { if (n == 0) { - return PLURAL_ZERO; + return "zero" /* Zero */; } if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } if (n == 2) { - return PLURAL_TWO; + return "two" /* Two */; } if (n == 3) { - return PLURAL_FEW; + return "few" /* Few */; } if (n == 6) { - return PLURAL_MANY; + return "many" /* Many */; } }, // Danish da(n) { if (n > 0 && n < 2) { - return PLURAL_ONE; + return "one" /* One */; } }, // Greek el(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Catalan (ca) @@ -230,71 +157,71 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo // Urdu (ur) en(n) { if (n == 1 && Plural.getV(n) === 0) { - return PLURAL_ONE; + return "one" /* One */; } }, // Spanish es(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Basque eu(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Persian fa(n) { if (n >= 0 && n <= 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // French fr(n) { if (n >= 0 && n < 2) { - return PLURAL_ONE; + return "one" /* One */; } }, // Irish ga(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } if (n == 2) { - return PLURAL_TWO; + return "two" /* Two */; } if (n == 3 || n == 4 || n == 5 || n == 6) { - return PLURAL_FEW; + return "few" /* Few */; } if (n == 7 || n == 8 || n == 9 || n == 10) { - return PLURAL_MANY; + return "many" /* Many */; } }, // Gujarati gu(n) { if (n >= 0 && n <= 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Hebrew he(n) { const v = Plural.getV(n); if (n == 1 && v === 0) { - return PLURAL_ONE; + return "one" /* One */; } if (n == 2 && v === 0) { - return PLURAL_TWO; + return "two" /* Two */; } if (n > 10 && v === 0 && n % 10 == 0) { - return PLURAL_MANY; + return "many" /* Many */; } }, // Hindi hi(n) { if (n >= 0 && n <= 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Croatian @@ -305,13 +232,13 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo // Hungarian hu(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Armenian hy(n) { if (n >= 0 && n < 2) { - return PLURAL_ONE; + return "one" /* One */; } }, // Indonesian @@ -322,7 +249,7 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo is(n) { const f = Plural.getF(n); if ((f === 0 && n % 10 === 1 && !(n % 100 === 11)) || !(f === 0)) { - return PLURAL_ONE; + return "one" /* One */; } }, // Japanese @@ -336,13 +263,13 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo // Georgian ka(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Kazakh kk(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Khmer @@ -352,7 +279,7 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo // Kannada kn(n) { if (n >= 0 && n <= 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Korean @@ -362,19 +289,19 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo // Kurdish ku(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Kyrgyz ky(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Luxembourgish lb(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Lao @@ -386,13 +313,13 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo const mod10 = n % 10; const mod100 = n % 100; if (mod10 == 1 && !(mod100 >= 11 && mod100 <= 19)) { - return PLURAL_ONE; + return "one" /* One */; } if (mod10 >= 2 && mod10 <= 9 && !(mod100 >= 11 && mod100 <= 19)) { - return PLURAL_FEW; + return "few" /* Few */; } if (Plural.getF(n) != 0) { - return PLURAL_MANY; + return "many" /* Many */; } }, // Latvian @@ -404,10 +331,10 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo const fMod10 = f % 10; const fMod100 = f % 100; if (mod10 == 0 || (mod100 >= 11 && mod100 <= 19) || (v == 2 && fMod100 >= 11 && fMod100 <= 19)) { - return PLURAL_ZERO; + return "zero" /* Zero */; } if ((mod10 == 1 && mod100 != 11) || (v == 2 && fMod10 == 1 && fMod100 != 11) || (v != 2 && fMod10 == 1)) { - return PLURAL_ONE; + return "one" /* One */; } }, // Macedonian @@ -417,19 +344,19 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo // Malayalam ml(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Mongolian mn(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Marathi mr(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Malay @@ -440,13 +367,13 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo mt(n) { const mod100 = n % 100; if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } if (n == 0 || (mod100 >= 2 && mod100 <= 10)) { - return PLURAL_FEW; + return "few" /* Few */; } if (mod100 >= 11 && mod100 <= 19) { - return PLURAL_MANY; + return "many" /* Many */; } }, // Burmese @@ -456,25 +383,25 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo // Norwegian no(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Nepali ne(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Odia or(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Punjabi pa(n) { if (n == 1 || n == 0) { - return PLURAL_ONE; + return "one" /* One */; } }, // Polish @@ -483,26 +410,26 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo const mod10 = n % 10; const mod100 = n % 100; if (n == 1 && v == 0) { - return PLURAL_ONE; + return "one" /* One */; } if (v == 0 && mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) { - return PLURAL_FEW; + return "few" /* Few */; } if (v == 0 && ((n != 1 && mod10 >= 0 && mod10 <= 1) || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 12 && mod100 <= 14))) { - return PLURAL_MANY; + return "many" /* Many */; } }, // Pashto ps(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Portuguese pt(n) { if (n >= 0 && n < 2) { - return PLURAL_ONE; + return "one" /* One */; } }, // Romanian @@ -510,10 +437,10 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo const v = Plural.getV(n); const mod100 = n % 100; if (n == 1 && v === 0) { - return PLURAL_ONE; + return "one" /* One */; } if (v != 0 || n == 0 || (mod100 >= 2 && mod100 <= 19)) { - return PLURAL_FEW; + return "few" /* Few */; } }, // Russian @@ -522,26 +449,26 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo const mod100 = n % 100; if (Plural.getV(n) == 0) { if (mod10 == 1 && mod100 != 11) { - return PLURAL_ONE; + return "one" /* One */; } if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) { - return PLURAL_FEW; + return "few" /* Few */; } if (mod10 == 0 || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 11 && mod100 <= 14)) { - return PLURAL_MANY; + return "many" /* Many */; } } }, // Sindhi sd(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Sinhala si(n) { if (n == 0 || n == 1 || (Math.floor(n) == 0 && Plural.getF(n) == 1)) { - return PLURAL_ONE; + return "one" /* One */; } }, // Slovak @@ -554,19 +481,19 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo const v = Plural.getV(n); const mod100 = n % 100; if (v == 0 && mod100 == 1) { - return PLURAL_ONE; + return "one" /* One */; } if (v == 0 && mod100 == 2) { - return PLURAL_TWO; + return "two" /* Two */; } if ((v == 0 && (mod100 == 3 || mod100 == 4)) || v != 0) { - return PLURAL_FEW; + return "few" /* Few */; } }, // Albanian sq(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Serbian @@ -577,13 +504,13 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo // Tamil ta(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Telugu te(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Tajik @@ -597,19 +524,19 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo // Turkmen tk(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Turkish tr(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Uyghur ug(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Ukrainian @@ -620,7 +547,7 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo // Uzbek uz(n) { if (n == 1) { - return PLURAL_ONE; + return "one" /* One */; } }, // Vietnamese @@ -632,5 +559,73 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo return undefined; }, }; + const Plural = Object.assign({ + /** + * Returns the plural category for the given value. + */ + getCategory(value, languageCode) { + if (!languageCode) { + languageCode = document.documentElement.lang; + } + // Fallback: handle unknown languages as English + if (typeof Plural[languageCode] !== "function") { + languageCode = "en"; + } + const category = Plural[languageCode](value); + if (category) { + return category; + } + return "other" /* Other */; + }, + /** + * Returns the value for a `plural` element used in the template. + * + * @see wcf\system\template\plugin\PluralFunctionTemplatePlugin::execute() + */ + getCategoryFromTemplateParameters(parameters) { + if (!parameters["value"]) { + throw new Error("Missing parameter value"); + } + if (!parameters["other"]) { + throw new Error("Missing parameter other"); + } + let value = parameters["value"]; + if (Array.isArray(value)) { + value = value.length; + } + // handle numeric attributes + const numericAttribute = Object.keys(parameters).find((key) => { + return key.toString() === (~~key).toString() && key.toString() === value.toString(); + }); + if (numericAttribute) { + return numericAttribute; + } + let category = Plural.getCategory(value); + if (!parameters[category]) { + category = "other" /* Other */; + } + const string = parameters[category]; + if (string.indexOf("#") !== -1) { + return string.replace("#", StringUtil.formatNumeric(value)); + } + return string; + }, + /** + * `f` is the fractional number as a whole number (1.234 yields 234) + */ + getF(n) { + const tmp = n.toString(); + const pos = tmp.indexOf("."); + if (pos === -1) { + return 0; + } + return parseInt(tmp.substr(pos + 1), 10); + }, + /** + * `v` represents the number of digits of the fractional part (1.234 yields 3) + */ + getV(n) { + return n.toString().replace(/^[^.]*\.?/, "").length; + } }, Languages); return Plural; }); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/I18n/Plural.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/I18n/Plural.ts index b79bd944ab..2f104d889b 100644 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/I18n/Plural.ts +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/I18n/Plural.ts @@ -9,184 +9,105 @@ import * as StringUtil from "../StringUtil"; -const PLURAL_FEW = "few"; -const PLURAL_MANY = "many"; -const PLURAL_ONE = "one"; -const PLURAL_OTHER = "other"; -const PLURAL_TWO = "two"; -const PLURAL_ZERO = "zero"; - -const Plural = { - /** - * Returns the plural category for the given value. - */ - getCategory(value: number, languageCode?: string): string { - if (!languageCode) { - languageCode = document.documentElement.lang; - } - - // Fallback: handle unknown languages as English - if (typeof Plural[languageCode] !== "function") { - languageCode = "en"; - } - - const category = Plural[languageCode](value); - if (category) { - return category; - } - - return PLURAL_OTHER; - }, - - /** - * Returns the value for a `plural` element used in the template. - * - * @see wcf\system\template\plugin\PluralFunctionTemplatePlugin::execute() - */ - getCategoryFromTemplateParameters(parameters: object): string { - if (!parameters["value"]) { - throw new Error("Missing parameter value"); - } - if (!parameters["other"]) { - throw new Error("Missing parameter other"); - } - - let value = parameters["value"]; - if (Array.isArray(value)) { - value = value.length; - } - - // handle numeric attributes - const numericAttribute = Object.keys(parameters).find((key) => { - return key.toString() === (~~key).toString() && key == value; - }); - - if (numericAttribute) { - return numericAttribute; - } - - let category = Plural.getCategory(value); - if (!parameters[category]) { - category = PLURAL_OTHER; - } - - const string = parameters[category]; - if (string.indexOf("#") !== -1) { - return string.replace("#", StringUtil.formatNumeric(value)); - } - - return string; - }, - - /** - * `f` is the fractional number as a whole number (1.234 yields 234) - */ - getF(n: number): number { - const tmp = n.toString(); - const pos = tmp.indexOf("."); - if (pos === -1) { - return 0; - } - - return parseInt(tmp.substr(pos + 1), 10); - }, - - /** - * `v` represents the number of digits of the fractional part (1.234 yields 3) - */ - getV(n: number): number { - return n.toString().replace(/^[^.]*\.?/, "").length; - }, - +const enum Category { + Few = "few", + Many = "many", + One = "one", + Other = "other", + Two = "two", + Zero = "zero", +} + +const Languages = { // Afrikaans - af(n: number): string | undefined { + af(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Amharic - am(n: number): string | undefined { + am(n: number): Category | undefined { const i = Math.floor(Math.abs(n)); if (n == 1 || i === 0) { - return PLURAL_ONE; + return Category.One; } }, // Arabic - ar(n: number): string | undefined { + ar(n: number): Category | undefined { if (n == 0) { - return PLURAL_ZERO; + return Category.Zero; } if (n == 1) { - return PLURAL_ONE; + return Category.One; } if (n == 2) { - return PLURAL_TWO; + return Category.Two; } const mod100 = n % 100; if (mod100 >= 3 && mod100 <= 10) { - return PLURAL_FEW; + return Category.Few; } if (mod100 >= 11 && mod100 <= 99) { - return PLURAL_MANY; + return Category.Many; } }, // Assamese - as(n: number): string | undefined { + as(n: number): Category | undefined { const i = Math.floor(Math.abs(n)); if (n == 1 || i === 0) { - return PLURAL_ONE; + return Category.One; } }, // Azerbaijani - az(n: number): string | undefined { + az(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Belarusian - be(n: number): string | undefined { + be(n: number): Category | undefined { const mod10 = n % 10; const mod100 = n % 100; if (mod10 == 1 && mod100 != 11) { - return PLURAL_ONE; + return Category.One; } if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) { - return PLURAL_FEW; + return Category.Few; } if (mod10 == 0 || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 11 && mod100 <= 14)) { - return PLURAL_MANY; + return Category.Many; } }, // Bulgarian - bg(n: number): string | undefined { + bg(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Bengali - bn(n: number): string | undefined { + bn(n: number): Category | undefined { const i = Math.floor(Math.abs(n)); if (n == 1 || i === 0) { - return PLURAL_ONE; + return Category.One; } }, // Tibetan - bo(_n: number): string | undefined { + bo(_n: number): Category | undefined { return undefined; }, // Bosnian - bs(n: number): string | undefined { + bs(n: number): Category | undefined { const v = Plural.getV(n); const f = Plural.getF(n); const mod10 = n % 10; @@ -195,61 +116,61 @@ const Plural = { const fMod100 = f % 100; if ((v == 0 && mod10 == 1 && mod100 != 11) || (fMod10 == 1 && fMod100 != 11)) { - return PLURAL_ONE; + return Category.One; } if ( (v == 0 && mod10 >= 2 && mod10 <= 4 && mod100 >= 12 && mod100 <= 14) || (fMod10 >= 2 && fMod10 <= 4 && fMod100 >= 12 && fMod100 <= 14) ) { - return PLURAL_FEW; + return Category.Few; } }, // Czech - cs(n: number): string | undefined { + cs(n: number): Category | undefined { const v = Plural.getV(n); if (n == 1 && v === 0) { - return PLURAL_ONE; + return Category.One; } if (n >= 2 && n <= 4 && v === 0) { - return PLURAL_FEW; + return Category.Few; } if (v === 0) { - return PLURAL_MANY; + return Category.Many; } }, // Welsh - cy(n: number): string | undefined { + cy(n: number): Category | undefined { if (n == 0) { - return PLURAL_ZERO; + return Category.Zero; } if (n == 1) { - return PLURAL_ONE; + return Category.One; } if (n == 2) { - return PLURAL_TWO; + return Category.Two; } if (n == 3) { - return PLURAL_FEW; + return Category.Few; } if (n == 6) { - return PLURAL_MANY; + return Category.Many; } }, // Danish - da(n: number): string | undefined { + da(n: number): Category | undefined { if (n > 0 && n < 2) { - return PLURAL_ONE; + return Category.One; } }, // Greek - el(n: number): string | undefined { + el(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, @@ -263,204 +184,204 @@ const Plural = { // Swedish (sv) // Swahili (sw) // Urdu (ur) - en(n: number): string | undefined { + en(n: number): Category | undefined { if (n == 1 && Plural.getV(n) === 0) { - return PLURAL_ONE; + return Category.One; } }, // Spanish - es(n: number): string | undefined { + es(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Basque - eu(n: number): string | undefined { + eu(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Persian - fa(n: number): string | undefined { + fa(n: number): Category | undefined { if (n >= 0 && n <= 1) { - return PLURAL_ONE; + return Category.One; } }, // French - fr(n: number): string | undefined { + fr(n: number): Category | undefined { if (n >= 0 && n < 2) { - return PLURAL_ONE; + return Category.One; } }, // Irish - ga(n: number): string | undefined { + ga(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } if (n == 2) { - return PLURAL_TWO; + return Category.Two; } if (n == 3 || n == 4 || n == 5 || n == 6) { - return PLURAL_FEW; + return Category.Few; } if (n == 7 || n == 8 || n == 9 || n == 10) { - return PLURAL_MANY; + return Category.Many; } }, // Gujarati - gu(n: number): string | undefined { + gu(n: number): Category | undefined { if (n >= 0 && n <= 1) { - return PLURAL_ONE; + return Category.One; } }, // Hebrew - he(n: number): string | undefined { + he(n: number): Category | undefined { const v = Plural.getV(n); if (n == 1 && v === 0) { - return PLURAL_ONE; + return Category.One; } if (n == 2 && v === 0) { - return PLURAL_TWO; + return Category.Two; } if (n > 10 && v === 0 && n % 10 == 0) { - return PLURAL_MANY; + return Category.Many; } }, // Hindi - hi(n: number): string | undefined { + hi(n: number): Category | undefined { if (n >= 0 && n <= 1) { - return PLURAL_ONE; + return Category.One; } }, // Croatian - hr(n: number): string | undefined { + hr(n: number): Category | undefined { // same as Bosnian return Plural.bs(n); }, // Hungarian - hu(n: number): string | undefined { + hu(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Armenian - hy(n: number): string | undefined { + hy(n: number): Category | undefined { if (n >= 0 && n < 2) { - return PLURAL_ONE; + return Category.One; } }, // Indonesian - id(_n: number): string | undefined { + id(_n: number): Category | undefined { return undefined; }, // Icelandic - is(n: number): string | undefined { + is(n: number): Category | undefined { const f = Plural.getF(n); if ((f === 0 && n % 10 === 1 && !(n % 100 === 11)) || !(f === 0)) { - return PLURAL_ONE; + return Category.One; } }, // Japanese - ja(_n: number): string | undefined { + ja(_n: number): Category | undefined { return undefined; }, // Javanese - jv(_n: number): string | undefined { + jv(_n: number): Category | undefined { return undefined; }, // Georgian - ka(n: number): string | undefined { + ka(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Kazakh - kk(n: number): string | undefined { + kk(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Khmer - km(_n: number): string | undefined { + km(_n: number): Category | undefined { return undefined; }, // Kannada - kn(n: number): string | undefined { + kn(n: number): Category | undefined { if (n >= 0 && n <= 1) { - return PLURAL_ONE; + return Category.One; } }, // Korean - ko(_n: number): string | undefined { + ko(_n: number): Category | undefined { return undefined; }, // Kurdish - ku(n: number): string | undefined { + ku(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Kyrgyz - ky(n: number): string | undefined { + ky(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Luxembourgish - lb(n: number): string | undefined { + lb(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Lao - lo(_n: number): string | undefined { + lo(_n: number): Category | undefined { return undefined; }, // Lithuanian - lt(n: number): string | undefined { + lt(n: number): Category | undefined { const mod10 = n % 10; const mod100 = n % 100; if (mod10 == 1 && !(mod100 >= 11 && mod100 <= 19)) { - return PLURAL_ONE; + return Category.One; } if (mod10 >= 2 && mod10 <= 9 && !(mod100 >= 11 && mod100 <= 19)) { - return PLURAL_FEW; + return Category.Few; } if (Plural.getF(n) != 0) { - return PLURAL_MANY; + return Category.Many; } }, // Latvian - lv(n: number): string | undefined { + lv(n: number): Category | undefined { const mod10 = n % 10; const mod100 = n % 100; const v = Plural.getV(n); @@ -469,273 +390,370 @@ const Plural = { const fMod100 = f % 100; if (mod10 == 0 || (mod100 >= 11 && mod100 <= 19) || (v == 2 && fMod100 >= 11 && fMod100 <= 19)) { - return PLURAL_ZERO; + return Category.Zero; } if ((mod10 == 1 && mod100 != 11) || (v == 2 && fMod10 == 1 && fMod100 != 11) || (v != 2 && fMod10 == 1)) { - return PLURAL_ONE; + return Category.One; } }, // Macedonian - mk(n: number): string | undefined { + mk(n: number): Category | undefined { return Plural.bs(n); }, // Malayalam - ml(n: number): string | undefined { + ml(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Mongolian - mn(n: number): string | undefined { + mn(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Marathi - mr(n: number): string | undefined { + mr(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Malay - ms(_n: number): string | undefined { + ms(_n: number): Category | undefined { return undefined; }, // Maltese - mt(n: number): string | undefined { + mt(n: number): Category | undefined { const mod100 = n % 100; if (n == 1) { - return PLURAL_ONE; + return Category.One; } if (n == 0 || (mod100 >= 2 && mod100 <= 10)) { - return PLURAL_FEW; + return Category.Few; } if (mod100 >= 11 && mod100 <= 19) { - return PLURAL_MANY; + return Category.Many; } }, // Burmese - my(_n: number): string | undefined { + my(_n: number): Category | undefined { return undefined; }, // Norwegian - no(n: number): string | undefined { + no(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Nepali - ne(n: number): string | undefined { + ne(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Odia - or(n: number): string | undefined { + or(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Punjabi - pa(n: number): string | undefined { + pa(n: number): Category | undefined { if (n == 1 || n == 0) { - return PLURAL_ONE; + return Category.One; } }, // Polish - pl(n: number): string | undefined { + pl(n: number): Category | undefined { const v = Plural.getV(n); const mod10 = n % 10; const mod100 = n % 100; if (n == 1 && v == 0) { - return PLURAL_ONE; + return Category.One; } if (v == 0 && mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) { - return PLURAL_FEW; + return Category.Few; } if ( v == 0 && ((n != 1 && mod10 >= 0 && mod10 <= 1) || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 12 && mod100 <= 14)) ) { - return PLURAL_MANY; + return Category.Many; } }, // Pashto - ps(n: number): string | undefined { + ps(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Portuguese - pt(n: number): string | undefined { + pt(n: number): Category | undefined { if (n >= 0 && n < 2) { - return PLURAL_ONE; + return Category.One; } }, // Romanian - ro(n: number): string | undefined { + ro(n: number): Category | undefined { const v = Plural.getV(n); const mod100 = n % 100; if (n == 1 && v === 0) { - return PLURAL_ONE; + return Category.One; } if (v != 0 || n == 0 || (mod100 >= 2 && mod100 <= 19)) { - return PLURAL_FEW; + return Category.Few; } }, // Russian - ru(n: number): string | undefined { + ru(n: number): Category | undefined { const mod10 = n % 10; const mod100 = n % 100; if (Plural.getV(n) == 0) { if (mod10 == 1 && mod100 != 11) { - return PLURAL_ONE; + return Category.One; } if (mod10 >= 2 && mod10 <= 4 && !(mod100 >= 12 && mod100 <= 14)) { - return PLURAL_FEW; + return Category.Few; } if (mod10 == 0 || (mod10 >= 5 && mod10 <= 9) || (mod100 >= 11 && mod100 <= 14)) { - return PLURAL_MANY; + return Category.Many; } } }, // Sindhi - sd(n: number): string | undefined { + sd(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Sinhala - si(n: number): string | undefined { + si(n: number): Category | undefined { if (n == 0 || n == 1 || (Math.floor(n) == 0 && Plural.getF(n) == 1)) { - return PLURAL_ONE; + return Category.One; } }, // Slovak - sk(n: number): string | undefined { + sk(n: number): Category | undefined { // same as Czech return Plural.cs(n); }, // Slovenian - sl(n: number): string | undefined { + sl(n: number): Category | undefined { const v = Plural.getV(n); const mod100 = n % 100; if (v == 0 && mod100 == 1) { - return PLURAL_ONE; + return Category.One; } if (v == 0 && mod100 == 2) { - return PLURAL_TWO; + return Category.Two; } if ((v == 0 && (mod100 == 3 || mod100 == 4)) || v != 0) { - return PLURAL_FEW; + return Category.Few; } }, // Albanian - sq(n: number): string | undefined { + sq(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Serbian - sr(n: number): string | undefined { + sr(n: number): Category | undefined { // same as Bosnian return Plural.bs(n); }, // Tamil - ta(n: number): string | undefined { + ta(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Telugu - te(n: number): string | undefined { + te(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Tajik - tg(_n: number): string | undefined { + tg(_n: number): Category | undefined { return undefined; }, // Thai - th(_n: number): string | undefined { + th(_n: number): Category | undefined { return undefined; }, // Turkmen - tk(n: number): string | undefined { + tk(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Turkish - tr(n: number): string | undefined { + tr(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Uyghur - ug(n: number): string | undefined { + ug(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Ukrainian - uk(n: number): string | undefined { + uk(n: number): Category | undefined { // same as Russian return Plural.ru(n); }, // Uzbek - uz(n: number): string | undefined { + uz(n: number): Category | undefined { if (n == 1) { - return PLURAL_ONE; + return Category.One; } }, // Vietnamese - vi(_n: number): string | undefined { + vi(_n: number): Category | undefined { return undefined; }, // Chinese - zh(_n: number): string | undefined { + zh(_n: number): Category | undefined { return undefined; }, }; +type ValidLanguage = keyof typeof Languages; + +// Note: This cannot be an interface due to the computed property. +type Parameters = { + value: number; + other: string; +} & { + [category in Category]?: string; +} & { + [number: number]: string; + }; + +const Plural = { + /** + * Returns the plural category for the given value. + */ + getCategory(value: number, languageCode?: ValidLanguage): Category { + if (!languageCode) { + languageCode = document.documentElement.lang as ValidLanguage; + } + + // Fallback: handle unknown languages as English + if (typeof Plural[languageCode] !== "function") { + languageCode = "en"; + } + + const category = Plural[languageCode](value); + if (category) { + return category; + } + + return Category.Other; + }, + + /** + * Returns the value for a `plural` element used in the template. + * + * @see wcf\system\template\plugin\PluralFunctionTemplatePlugin::execute() + */ + getCategoryFromTemplateParameters(parameters: Parameters): string { + if (!parameters["value"]) { + throw new Error("Missing parameter value"); + } + if (!parameters["other"]) { + throw new Error("Missing parameter other"); + } + + let value = parameters["value"]; + if (Array.isArray(value)) { + value = value.length; + } + + // handle numeric attributes + const numericAttribute = Object.keys(parameters).find((key) => { + return key.toString() === (~~key).toString() && key.toString() === value.toString(); + }); + + if (numericAttribute) { + return numericAttribute; + } + + let category = Plural.getCategory(value); + if (!parameters[category]) { + category = Category.Other; + } + + const string = parameters[category]!; + if (string.indexOf("#") !== -1) { + return string.replace("#", StringUtil.formatNumeric(value)); + } + + return string; + }, + + /** + * `f` is the fractional number as a whole number (1.234 yields 234) + */ + getF(n: number): number { + const tmp = n.toString(); + const pos = tmp.indexOf("."); + if (pos === -1) { + return 0; + } + + return parseInt(tmp.substr(pos + 1), 10); + }, + + /** + * `v` represents the number of digits of the fractional part (1.234 yields 3) + */ + getV(n: number): number { + return n.toString().replace(/^[^.]*\.?/, "").length; + }, + + ...Languages, +}; + export = Plural; -- 2.20.1