From fb9c1783046996fc0eb4111dea5f96ce07cbf393 Mon Sep 17 00:00:00 2001 From: Jonathan Gamble Date: Sun, 20 Oct 2024 10:00:32 -0500 Subject: [PATCH 1/2] move quantity javascript to client i18n --- app/views/base/page.scala | 1 - modules/i18n/src/main/JsQuantity.scala | 145 ------------------------- modules/web/src/main/ui/layout.scala | 6 +- ui/.build/src/i18n.ts | 144 ++++++++++++++++++++++-- ui/@types/lichess/i18n.d.ts | 4 +- ui/site/src/site.ts | 6 +- 6 files changed, 139 insertions(+), 167 deletions(-) delete mode 100644 modules/i18n/src/main/JsQuantity.scala diff --git a/app/views/base/page.scala b/app/views/base/page.scala index a5588f7ed2e6..6addb667dcef 100644 --- a/app/views/base/page.scala +++ b/app/views/base/page.scala @@ -9,7 +9,6 @@ import lila.api.SocketTest object page: val ui = lila.web.ui.layout(helpers, assetHelper)( - jsQuantity = lila.i18n.JsQuantity.apply, isRTL = lila.i18n.LangList.isRTL, popularAlternateLanguages = lila.i18n.LangList.popularAlternateLanguages, reportScoreThreshold = env.report.scoreThresholdsSetting.get, diff --git a/modules/i18n/src/main/JsQuantity.scala b/modules/i18n/src/main/JsQuantity.scala deleted file mode 100644 index 6f06d250c753..000000000000 --- a/modules/i18n/src/main/JsQuantity.scala +++ /dev/null @@ -1,145 +0,0 @@ -package lila.i18n - -import play.api.i18n.Lang - -object JsQuantity: - def apply(lang: Lang): String = - lang.language match - case "fr" | "ff" | "kab" | "co" | "ak" | "am" | "bh" | "fil" | "tl" | "guw" | "hi" | "ln" | "mg" | - "nso" | "ti" | "wa" => // french - """o=>o<=1?"one":"other"""" - case "cs" | "sk" => // czech - """o=>1==o?"one":o>=2&&o<=4?"few":"other"""" - case "hr" | "ru" | "sr" | "uk" | "be" | "bs" | "sh" | "ry" => // balkan - """o=>{const e=o%100,t=o%10;return 1==t&&11!=e?"one":t>=2&&t<=4&&!(e>=12&&e<=14)?"few":0==t||t>=5&&t<=9||e>=11&&e<=14?"many":"other"}""" - case "lv" => // latvian - """o=>0==o?"zero":o%10==1&&o%100!=11?"one":"other"""" - case "lt" => // lithuanian - """o=>{const e=o%100,t=o%10;return 1!=t||e>=11&&e<=19?t>=2&&t<=9&&!(e>=11&&e<=19)?"few":"other":"one"}""" - case "pl" => // polish - """o=>{const e=o%100,t=o%10;return 1==o?"one":t>=2&&t<=4&&!(e>=12&&e<=14)?"few":"other"}""" - case "ro" | "mo" => // romanian - """o=>{const e=o%100;return 1==o?"one":0==o||e>=1&&e<=19?"few":"other"}""" - case "sl" => // slovenian - """o=>{const e=o%100;return 1==e?"one":2==e?"two":e>=3&&e<=4?"few":"other"}""" - case "ar" => // arabic - """o=>{const e=o%100;return 0==o?"zero":1==o?"one":2==o?"two":e>=3&&e<=10?"few":e>=11&&e<=99?"many":"other"}""" - case "mk" => // macedonian - """o=>o%10==1&&11!=o?"one":"other"""" - case "cy" | "br" => // welsh - """o=>0==o?"zero":1==o?"one":2==o?"two":3==o?"few":6==o?"many":"other"""" - case "mt" => // maltese - """o=>{const e=o%100;return 1==o?"one":0==o||e>=2&&e<=10?"few":e>=11&&e<=19?"many":"other"}""" - case "ga" | "se" | "sma" | "smi" | "smj" | "smn" | "sms" => // two - """o=>1==o?"one":2==o?"two":"other"""" - case "az" | "bm" | "fa" | "ig" | "hu" | "ja" | "kde" | "kea" | "ko" | "my" | "ses" | "sg" | "to" | - "tr" | "vi" | "wo" | "yo" | "zh" | "bo" | "dz" | "id" | "jv" | "ka" | "km" | "kn" | "ms" | "th" | - "tp" | "io" | "ia" => // none - """o=>"other"""" - case _ => // other - """o=>1==o?"one":"other"""" - -/* - -// $ terser --mangle --compress --ecma 2018 --safari10 - -export const french = c => { - return c <= 1 ? 'one' : 'other'; -}; - -export const czech = c => { - if (c == 1) return 'one'; - else if (c >= 2 && c <= 4) return 'few'; - else return 'other'; -}; - -export const balkan = c => { - const rem100 = c % 100; - const rem10 = c % 10; - if (rem10 == 1 && rem100 != 11) return 'one'; - else if (rem10 >= 2 && rem10 <= 4 && !(rem100 >= 12 && rem100 <= 14)) return 'few'; - else if (rem10 == 0 || (rem10 >= 5 && rem10 <= 9) || (rem100 >= 11 && rem100 <= 14)) return 'many'; - else return 'other'; -}; - -export const latvian = c => { - if (c == 0) return 'zero'; - else if (c % 10 == 1 && c % 100 != 11) return 'one'; - else return 'other'; -}; - -export const lithuanian = c => { - const rem100 = c % 100; - const rem10 = c % 10; - if (rem10 == 1 && !(rem100 >= 11 && rem100 <= 19)) return 'one'; - else if (rem10 >= 2 && rem10 <= 9 && !(rem100 >= 11 && rem100 <= 19)) return 'few'; - else return 'other'; -}; - -export const polish = c => { - const rem100 = c % 100; - const rem10 = c % 10; - if (c == 1) return 'one'; - else if (rem10 >= 2 && rem10 <= 4 && !(rem100 >= 12 && rem100 <= 14)) return 'few'; - else return 'other'; -}; - -export const romanian = c => { - const rem100 = c % 100; - if (c == 1) return 'one'; - else if (c == 0 || (rem100 >= 1 && rem100 <= 19)) return 'few'; - else return 'other'; -}; - -export const slovenian = c => { - const rem100 = c % 100; - if (rem100 == 1) return 'one'; - else if (rem100 == 2) return 'two'; - else if (rem100 >= 3 && rem100 <= 4) return 'few'; - else return 'other'; -}; - -export const arabic = c => { - const rem100 = c % 100; - if (c == 0) return 'zero'; - else if (c == 1) return 'one'; - else if (c == 2) return 'two'; - else if (rem100 >= 3 && rem100 <= 10) return 'few'; - else if (rem100 >= 11 && rem100 <= 99) return 'many'; - else return 'other'; -}; - -export const macedonian = c => { - return c % 10 == 1 && c != 11 ? 'one' : 'other'; -}; - -export const welsh = c => { - if (c == 0) return 'zero'; - else if (c == 1) return 'one'; - else if (c == 2) return 'two'; - else if (c == 3) return 'few'; - else if (c == 6) return 'many'; - else return 'other'; -}; - -export const maltese = c => { - const rem100 = c % 100; - if (c == 1) return 'one'; - else if (c == 0 || (rem100 >= 2 && rem100 <= 10)) return 'few'; - else if (rem100 >= 11 && rem100 <= 19) return 'many'; - else return 'other'; -}; - -export const two = c => { - if (c == 1) return 'one'; - else if (c == 2) return 'two'; - else return 'other'; -}; - -export const none = _ => 'other'; - -export const other = c => { - return c == 1 ? 'one' : 'other'; -}; - - */ diff --git a/modules/web/src/main/ui/layout.scala b/modules/web/src/main/ui/layout.scala index 7fe3d7e96fa4..7c680dedc073 100644 --- a/modules/web/src/main/ui/layout.scala +++ b/modules/web/src/main/ui/layout.scala @@ -10,7 +10,6 @@ import lila.ui.* import ScalatagsTemplate.{ *, given } final class layout(helpers: Helpers, assetHelper: lila.web.ui.AssetFullHelper)( - jsQuantity: Lang => String, isRTL: Lang => Boolean, popularAlternateLanguages: List[Language], reportScoreThreshold: () => ScoreThresholds, @@ -345,9 +344,6 @@ final class layout(helpers: Helpers, assetHelper: lila.web.ui.AssetFullHelper)( private def jsCode(using t: Translate) = cache.computeIfAbsent( t.lang, - _ => - "if (!window.site) window.site={};" + - """window.site.load=new Promise(r=>document.addEventListener("DOMContentLoaded",r));""" + - s"window.site.quantity=${jsQuantity(t.lang)};" + _ => """window.site={load:new Promise(r=>document.addEventListener("DOMContentLoaded",r))};""" ) end inlineJs diff --git a/ui/.build/src/i18n.ts b/ui/.build/src/i18n.ts index ae70e5a15681..0bcd61c4e221 100644 --- a/ui/.build/src/i18n.ts +++ b/ui/.build/src/i18n.ts @@ -27,7 +27,8 @@ interface I18nPlural { } interface I18n { /** Global noarg key lookup (only if absolutely necessary). */ - (key: string): string;\n\n`; + (key: string): string; + quantity: (count: number) => 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';\n\n`; const jsPrelude = '"use strict";(()=>{' + @@ -44,17 +45,17 @@ const jsPrelude = return (r.asArray = (...n) => l(t, ...n)), r; } function o(t, n) { - return t[site.quantity(n)] || t.other || t.one || ''; + return t[i18n.quantity(n)] || t.other || t.one || ''; } function l(t, ...r) { let n = t.split(/(%(?:\\d\\$)?s)/); if (r.length) { let e = n.indexOf('%s'); - if (e !== -1) n[e] = r[0]; + if (e != -1) n[e] = r[0]; else for (let i = 0; i < r.length; i++) { let s = n.indexOf('%' + (i + 1) + '$s'); - s !== -1 && (n[s] = r[i]); + s != -1 && (n[s] = r[i]); } } return n; @@ -63,6 +64,132 @@ const jsPrelude = ) ).code; +const siteInit = ( + await transform( + `window.i18n = function(k) { + for (let v of Object.values(window.i18n)) { + if (v[k]) return v[k]; + return k; + } + }; + window.i18n.quantity = [ + { + l: ['fr', 'ff', 'kab', 'co', 'ak', 'am', 'bh', 'fil', 'tl', 'guw', 'hi', 'ln', 'mg', 'nso', 'ti', 'wa'], + q: e => (e <= 1 ? 'one' : 'other'), + }, + { l: ['cs', 'sk'], q: e => (e == 1 ? 'one' : e >= 2 && e <= 4 ? 'few' : 'other') }, + { + l: ['hr', 'ru', 'sr', 'uk', 'be', 'bs', 'sh', 'ry'], + q: e => { + const t = e % 100, + n = e % 10; + return n == 1 && t != 11 + ? 'one' + : n >= 2 && n <= 4 && !(t >= 12 && t <= 14) + ? 'few' + : n == 0 || (n >= 5 && n <= 9) || (t >= 11 && t <= 14) + ? 'many' + : 'other'; + }, + }, + { l: ['lv'], q: e => (e == 0 ? 'zero' : e % 10 == 1 && e % 100 != 11 ? 'one' : 'other') }, + { + l: ['lt'], + q: e => { + const t = e % 100, + n = e % 10; + return n != 1 || (t >= 11 && t <= 19) + ? n >= 2 && n <= 9 && !(t >= 11 && t <= 19) + ? 'few' + : 'other' + : 'one'; + }, + }, + { + l: ['pl'], + q: e => { + const t = e % 100, + n = e % 10; + return e == 1 ? 'one' : n >= 2 && n <= 4 && !(t >= 12 && t <= 14) ? 'few' : 'other'; + }, + }, + { + l: ['ro', 'mo'], + q: e => { + const t = e % 100; + return e == 1 ? 'one' : e == 0 || (t >= 1 && t <= 19) ? 'few' : 'other'; + }, + }, + { + l: ['sl'], + q: e => { + const t = e % 100; + return t == 1 ? 'one' : t == 2 ? 'two' : t >= 3 && t <= 4 ? 'few' : 'other'; + }, + }, + { + l: ['ar'], + q: e => { + const t = e % 100; + return e == 0 + ? 'zero' + : e == 1 + ? 'one' + : e == 2 + ? 'two' + : t >= 3 && t <= 10 + ? 'few' + : t >= 11 && t <= 99 + ? 'many' + : 'other'; + }, + }, + { l: ['mk'], q: e => (e % 10 == 1 && e != 11 ? 'one' : 'other') }, + { + l: ['cy', 'br'], + q: e => + e == 0 + ? 'zero' + : e == 1 + ? 'one' + : e == 2 + ? 'two' + : e == 3 + ? 'few' + : e == 6 + ? 'many' + : 'other', + }, + { + l: ['mt'], + q: e => { + const t = e % 100; + return e == 1 + ? 'one' + : e == 0 || (t >= 2 && t <= 10) + ? 'few' + : t >= 11 && t <= 19 + ? 'many' + : 'other'; + }, + }, + { + l: ['ga', 'se', 'sma', 'smi', 'smj', 'smn', 'sms'], + q: e => (e == 1 ? 'one' : e == 2 ? 'two' : 'other'), + }, + { + l: [ + 'az', 'bm', 'fa', 'ig', 'hu', 'ja', 'kde', 'kea', 'ko', 'my', 'ses', 'sg', 'to', 'tr', 'vi', + 'wo', 'yo', 'zh', 'bo', 'dz', 'id', 'jv', 'ka', 'km', 'kn', 'ms', 'th', 'tp', 'io', 'ia' + ], + q: e => 'other', + }, + ].find(({ l: e }) => e.includes(document.documentElement.lang.split('-')[0]))?.q || + (e => e == 1 ? 'one' : 'other');`, + { minify: true, loader: 'js' }, + ) +).code; + export function stopI18n(): void { clearTimeout(watchTimeout); watchTimeout = undefined; @@ -177,15 +304,10 @@ async function writeJavascript(cat: string, locale?: string, xstat: fs.Stats | f .then(parseXml) : []), ]); - const jsInit = - cat === 'site' - ? 'window.i18n=function(k){for(let v of Object.values(window.i18n))if(v[k])return v[k];return k};' - : ''; const code = jsPrelude + - jsInit + - `if(!window.i18n.${cat})window.i18n.${cat}={};` + - `let i=window.i18n.${cat};` + + (cat === 'site' ? siteInit : '') + + `let i=window.i18n.${cat}={};` + [...translations] .map( ([k, v]) => diff --git a/ui/@types/lichess/i18n.d.ts b/ui/@types/lichess/i18n.d.ts index 6ac9d4d23994..c65813be79d2 100644 --- a/ui/@types/lichess/i18n.d.ts +++ b/ui/@types/lichess/i18n.d.ts @@ -5,12 +5,12 @@ interface I18nFormat { } interface I18nPlural { (quantity: number, ...args: (string | number)[]): string; // pluralSame - raw: (quantity: number, ...args: (string | number)[]) => string; // plural - asArray: (quantity: number, ...args: T[]) => (T | string)[]; // vdomPlural + asArray: (quantity: number, ...args: T[]) => (T | string)[]; // vdomPlural / plural } interface I18n { /** Global noarg key lookup (only if absolutely necessary). */ (key: string): string; + quantity: (count: number) => 'zero' | 'one' | 'two' | 'few' | 'many' | 'other'; activity: { /** Activity */ diff --git a/ui/site/src/site.ts b/ui/site/src/site.ts index 7bd5074742a1..79dac80afa1b 100644 --- a/ui/site/src/site.ts +++ b/ui/site/src/site.ts @@ -11,10 +11,10 @@ import { pubsub } from 'common/pubsub'; const site = window.site; (site as any).pubsub = pubsub; // do not declare in index.d.ts. some extensions need this here -// site.load, site.quantity, site.siteI18n are initialized in layout.scala embedded script tags -// site.manifest is fetched immediately from the server +// site.load is initialized in layout.scala embedded script tags +// site.manifest is fetched // site.info, site.debug are populated by ui/build -// site.socket, site.quietMode, site.analysis are set elsewhere but available here +// site.socket, site.quietMode, site.analysis are set elsewhere site.sri = randomToken(); site.displayLocale = displayLocale; site.blindMode = document.body.classList.contains('blind-mode'); From 1e69349698ed5b888799d1c3be53dce63408713e Mon Sep 17 00:00:00 2001 From: Jonathan Gamble Date: Sun, 20 Oct 2024 17:01:45 -0500 Subject: [PATCH 2/2] put a single quantity function in site.locale.js --- ui/.build/src/i18n.ts | 304 ++++++++++++++++++------------------------ 1 file changed, 129 insertions(+), 175 deletions(-) diff --git a/ui/.build/src/i18n.ts b/ui/.build/src/i18n.ts index 0bcd61c4e221..f8fd0f3d860a 100644 --- a/ui/.build/src/i18n.ts +++ b/ui/.build/src/i18n.ts @@ -16,180 +16,6 @@ let watchTimeout: NodeJS.Timeout | undefined; const i18nWatch: fs.FSWatcher[] = []; const isFormat = /%(?:[\d]\$)?s/; -const tsPrelude = `// Generated -interface I18nFormat { - (...args: (string | number)[]): string; // formatted - asArray: (...args: T[]) => (T | string)[]; // vdom -} -interface I18nPlural { - (quantity: number, ...args: (string | number)[]): string; // pluralSame - asArray: (quantity: number, ...args: T[]) => (T | string)[]; // vdomPlural / plural -} -interface I18n { - /** Global noarg key lookup (only if absolutely necessary). */ - (key: string): string; - quantity: (count: number) => 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';\n\n`; - -const jsPrelude = - '"use strict";(()=>{' + - ( - await transform( - // s(...) is the standard format function, p(...) is the plural format function. - // both have an asArray method for vdom. - `function p(t) { - let r = (n, ...e) => l(o(t, n), n, ...e).join(''); - return (r.asArray = (n, ...e) => l(o(t, n), ...e)), r; - } - function s(t) { - let r = (...n) => l(t, ...n).join(''); - return (r.asArray = (...n) => l(t, ...n)), r; - } - function o(t, n) { - return t[i18n.quantity(n)] || t.other || t.one || ''; - } - function l(t, ...r) { - let n = t.split(/(%(?:\\d\\$)?s)/); - if (r.length) { - let e = n.indexOf('%s'); - if (e != -1) n[e] = r[0]; - else - for (let i = 0; i < r.length; i++) { - let s = n.indexOf('%' + (i + 1) + '$s'); - s != -1 && (n[s] = r[i]); - } - } - return n; - }`, - { minify: true, loader: 'js' }, - ) - ).code; - -const siteInit = ( - await transform( - `window.i18n = function(k) { - for (let v of Object.values(window.i18n)) { - if (v[k]) return v[k]; - return k; - } - }; - window.i18n.quantity = [ - { - l: ['fr', 'ff', 'kab', 'co', 'ak', 'am', 'bh', 'fil', 'tl', 'guw', 'hi', 'ln', 'mg', 'nso', 'ti', 'wa'], - q: e => (e <= 1 ? 'one' : 'other'), - }, - { l: ['cs', 'sk'], q: e => (e == 1 ? 'one' : e >= 2 && e <= 4 ? 'few' : 'other') }, - { - l: ['hr', 'ru', 'sr', 'uk', 'be', 'bs', 'sh', 'ry'], - q: e => { - const t = e % 100, - n = e % 10; - return n == 1 && t != 11 - ? 'one' - : n >= 2 && n <= 4 && !(t >= 12 && t <= 14) - ? 'few' - : n == 0 || (n >= 5 && n <= 9) || (t >= 11 && t <= 14) - ? 'many' - : 'other'; - }, - }, - { l: ['lv'], q: e => (e == 0 ? 'zero' : e % 10 == 1 && e % 100 != 11 ? 'one' : 'other') }, - { - l: ['lt'], - q: e => { - const t = e % 100, - n = e % 10; - return n != 1 || (t >= 11 && t <= 19) - ? n >= 2 && n <= 9 && !(t >= 11 && t <= 19) - ? 'few' - : 'other' - : 'one'; - }, - }, - { - l: ['pl'], - q: e => { - const t = e % 100, - n = e % 10; - return e == 1 ? 'one' : n >= 2 && n <= 4 && !(t >= 12 && t <= 14) ? 'few' : 'other'; - }, - }, - { - l: ['ro', 'mo'], - q: e => { - const t = e % 100; - return e == 1 ? 'one' : e == 0 || (t >= 1 && t <= 19) ? 'few' : 'other'; - }, - }, - { - l: ['sl'], - q: e => { - const t = e % 100; - return t == 1 ? 'one' : t == 2 ? 'two' : t >= 3 && t <= 4 ? 'few' : 'other'; - }, - }, - { - l: ['ar'], - q: e => { - const t = e % 100; - return e == 0 - ? 'zero' - : e == 1 - ? 'one' - : e == 2 - ? 'two' - : t >= 3 && t <= 10 - ? 'few' - : t >= 11 && t <= 99 - ? 'many' - : 'other'; - }, - }, - { l: ['mk'], q: e => (e % 10 == 1 && e != 11 ? 'one' : 'other') }, - { - l: ['cy', 'br'], - q: e => - e == 0 - ? 'zero' - : e == 1 - ? 'one' - : e == 2 - ? 'two' - : e == 3 - ? 'few' - : e == 6 - ? 'many' - : 'other', - }, - { - l: ['mt'], - q: e => { - const t = e % 100; - return e == 1 - ? 'one' - : e == 0 || (t >= 2 && t <= 10) - ? 'few' - : t >= 11 && t <= 19 - ? 'many' - : 'other'; - }, - }, - { - l: ['ga', 'se', 'sma', 'smi', 'smj', 'smn', 'sms'], - q: e => (e == 1 ? 'one' : e == 2 ? 'two' : 'other'), - }, - { - l: [ - 'az', 'bm', 'fa', 'ig', 'hu', 'ja', 'kde', 'kea', 'ko', 'my', 'ses', 'sg', 'to', 'tr', 'vi', - 'wo', 'yo', 'zh', 'bo', 'dz', 'id', 'jv', 'ka', 'km', 'kn', 'ms', 'th', 'tp', 'io', 'ia' - ], - q: e => 'other', - }, - ].find(({ l: e }) => e.includes(document.documentElement.lang.split('-')[0]))?.q || - (e => e == 1 ? 'one' : 'other');`, - { minify: true, loader: 'js' }, - ) -).code; - export function stopI18n(): void { clearTimeout(watchTimeout); watchTimeout = undefined; @@ -304,9 +130,17 @@ async function writeJavascript(cat: string, locale?: string, xstat: fs.Stats | f .then(parseXml) : []), ]); + const lang = locale?.split('-')[0]; + const jsInit = + cat !== 'site' + ? '' + : siteInit + + 'window.i18n.quantity=' + + (jsQuantity.find(({ l }) => l.includes(lang ?? ''))?.q ?? `o=>o==1?'one':'other'`) + + ';'; const code = jsPrelude + - (cat === 'site' ? siteInit : '') + + jsInit + `let i=window.i18n.${cat}={};` + [...translations] .map( @@ -366,3 +200,123 @@ function zip(arr1: T[], arr2: U[]): [T, U][] { } return result; } + +async function min(js: string): Promise { + return (await transform(js, { minify: true, loader: 'js' })).code; +} + +const tsPrelude = `// Generated +interface I18nFormat { + (...args: (string | number)[]): string; // formatted + asArray: (...args: T[]) => (T | string)[]; // vdom +} +interface I18nPlural { + (quantity: number, ...args: (string | number)[]): string; // pluralSame + asArray: (quantity: number, ...args: T[]) => (T | string)[]; // vdomPlural / plural +} +interface I18n { + /** Global noarg key lookup (only if absolutely necessary). */ + (key: string): string; + quantity: (count: number) => 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';\n\n`; + +const jsPrelude = + '"use strict";(()=>{' + + (await min( + // s(...) is the standard format function, p(...) is the plural format function. + // both have an asArray method for vdom. + `function p(t) { + let r = (n, ...e) => l(o(t, n), n, ...e).join(''); + return (r.asArray = (n, ...e) => l(o(t, n), ...e)), r; + } + function s(t) { + let r = (...n) => l(t, ...n).join(''); + return (r.asArray = (...n) => l(t, ...n)), r; + } + function o(t, n) { + return t[i18n.quantity(n)] || t.other || t.one || ''; + } + function l(t, ...r) { + let n = t.split(/(%(?:\\d\\$)?s)/); + if (r.length) { + let e = n.indexOf('%s'); + if (e != -1) n[e] = r[0]; + else + for (let i = 0; i < r.length; i++) { + let s = n.indexOf('%' + (i + 1) + '$s'); + s != -1 && (n[s] = r[i]); + } + } + return n; + }`, + )); + +const siteInit = await min( + `window.i18n = function(k) { + for (let v of Object.values(window.i18n)) { + if (v[k]) return v[k]; + return k; + } + }`, +); + +const jsQuantity = [ + { + l: ['fr', 'ff', 'kab', 'co', 'ak', 'am', 'bh', 'fil', 'tl', 'guw', 'hi', 'ln', 'mg', 'nso', 'ti', 'wa'], + q: `o=>o<=1?"one":"other"`, // french + }, + { + l: ['cs', 'sk'], + q: `o=>1==o?"one":o>=2&&o<=4?"few":"other"`, // czech + }, + { + l: ['hr', 'ru', 'sr', 'uk', 'be', 'bs', 'sh', 'ry'], // balkan + q: `o=>{const e=o%100,t=o%10;return 1==t&&11!=e?"one":t>=2&&t<=4&&!(e>=12&&e<=14)?"few":0==t||t>=5&&t<=9||e>=11&&e<=14?"many":"other"}`, + }, + { + l: ['lv'], // latvian + q: `o=>0==o?"zero":o%10==1&&o%100!=11?"one":"other"`, + }, + { + l: ['lt'], // lithuanian + q: `o=>{const e=o%100,t=o%10;return 1!=t||e>=11&&e<=19?t>=2&&t<=9&&!(e>=11&&e<=19)?"few":"other":"one"}`, + }, + { + l: ['pl'], // polish + q: `o=>{const e=o%100,t=o%10;return 1==o?"one":t>=2&&t<=4&&!(e>=12&&e<=14)?"few":"other"}`, + }, + { + l: ['ro', 'mo'], // romanian + q: `o=>{const e=o%100;return 1==o?"one":0==o||e>=1&&e<=19?"few":"other"}`, + }, + { + l: ['sl'], // slovenian + q: `o=>{const e=o%100;return 1==e?"one":2==e?"two":e>=3&&e<=4?"few":"other"}`, + }, + { + l: ['ar'], // arabic + q: `o=>{const e=o%100;return 0==o?"zero":1==o?"one":2==o?"two":e>=3&&e<=10?"few":e>=11&&e<=99?"many":"other"}`, + }, + { + l: ['mk'], // macedonian + q: `o=>o%10==1&&11!=o?"one":"other"`, + }, + { + l: ['cy', 'br'], // welsh + q: `o=>0==o?"zero":1==o?"one":2==o?"two":3==o?"few":6==o?"many":"other"`, + }, + { + l: ['mt'], // maltese + q: `o=>{const e=o%100;return 1==o?"one":0==o||e>=2&&e<=10?"few":e>=11&&e<=19?"many":"other"}`, + }, + { + l: ['ga', 'se', 'sma', 'smi', 'smj', 'smn', 'sms'], + q: `o=>1==o?"one":2==o?"two":"other"`, + }, + { + l: [ + ...['az', 'bm', 'fa', 'ig', 'hu', 'ja', 'kde', 'kea', 'ko', 'my', 'ses', 'sg', 'to', 'tr', 'vi', 'wo'], + ...['yo', 'zh', 'bo', 'dz', 'id', 'jv', 'ka', 'km', 'kn', 'ms', 'th', 'tp', 'io', 'ia'], + ], + q: `o=>"other"`, + }, +];