Files
orca/config/scripts/locale-plural-forms.mjs
T
OrcaWinandOrcaWin 704d3cdd6f perf(i18n): load built-in catalogs once, without copies or a runtime plural fill (#35615)
* perf(i18n): load built-in catalogs once, without copies or a runtime plural fill

Each catalog load parsed the JSON, rebuilt it to fill CLDR plural forms
from _other, then deep-copied and deep-merged it into the store (about
26 ms after parsing per 1.3 MB catalog), and concurrent loads of one
locale fetched it twice.

- sync:localization-catalog now commits the plural forms a locale's
  CLDR rules need beside each _other (fr/es/pt many, tr one, a few
  de/ru), and verify:localization-catalog fails when one is missing.
  Plural forms may include {{count}} where English omits it.
- Renderer and main load built-in catalogs as-is; plugin packs keep the
  runtime fill.
- Renderer catalog requests are shared per locale; a timed-out request
  is dropped so a retry fetches again. Bundles are added with skipCopy.

* fix(i18n): require {{count}} in plural forms when English shows it

* chore(i18n): sync plural forms for catalogs merged from main

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 19:58:12 -07:00

85 lines
3.1 KiB
JavaScript

import fs from 'node:fs/promises'
import path from 'node:path'
const PLURAL_KEY_RE = /^(.*)_(zero|one|two|few|many|other)$/
// Locales with more CLDR plural categories than English (ru few/many) source those forms from `_other`.
export function resolveLocaleSourceKey(enEntries, localeName, key) {
if (enEntries.has(key)) {
return key
}
const plural = PLURAL_KEY_RE.exec(key)
if (!plural || !localeName || !enEntries.has(`${plural[1]}_other`)) {
return undefined
}
return pluralCategories(localeName).includes(plural[2]) ? `${plural[1]}_other` : undefined
}
const COUNT_PLACEHOLDER_RE = /^\{\{\s*count\s*[,}]/
// i18next always passes count to plural forms, so a locale may add it where English omits it (ru 21 uses _one).
export function interpolationVariablesMatch(sourceKey, enVariables, localeVariables) {
if (enVariables.join('|') === localeVariables.join('|')) {
return true
}
const withoutCount = (variable) => !COUNT_PLACEHOLDER_RE.test(variable)
return (
PLURAL_KEY_RE.test(sourceKey) &&
!enVariables.some((variable) => COUNT_PLACEHOLDER_RE.test(variable)) &&
enVariables.join('|') === localeVariables.filter(withoutCount).join('|')
)
}
function pluralCategories(localeName) {
try {
return new Intl.PluralRules(localeName).resolvedOptions().pluralCategories
} catch {
return []
}
}
// Untranslated CLDR forms (fr/es/pt many, tr one) reuse the locale's `_other` instead of English; inserted beside it.
function withMissingPluralForms(catalog, categories, prefix = '', added = []) {
const filled = {}
for (const [key, value] of Object.entries(catalog)) {
const keyPath = prefix ? `${prefix}.${key}` : key
filled[key] =
typeof value === 'object' && value !== null && !Array.isArray(value)
? withMissingPluralForms(value, categories, keyPath, added).catalog
: value
if (typeof value !== 'string' || !key.endsWith('_other')) {
continue
}
const base = key.slice(0, -'other'.length)
for (const category of categories) {
if (!Object.hasOwn(catalog, `${base}${category}`)) {
filled[`${base}${category}`] = value
added.push(`${prefix ? `${prefix}.` : ''}${base}${category}`)
}
}
}
return { catalog: filled, added }
}
/** Verifies (or with fix, writes) the plural forms a desktop locale catalog needs; runtimes load catalogs as-is. */
export async function syncLocalePluralForms(catalogPath, catalog, fix) {
const fileName = path.basename(catalogPath)
const plural = withMissingPluralForms(catalog, pluralCategories(fileName.replace(/\.json$/, '')))
if (plural.added.length === 0) {
return { ok: true, catalog }
}
if (!fix) {
console.error(`${fileName} is missing CLDR plural forms that runtimes no longer fill:`)
console.error(
plural.added
.slice(0, 20)
.map((key) => `missing plural form: ${key}`)
.join('\n')
)
return { ok: false }
}
await fs.writeFile(catalogPath, `${JSON.stringify(plural.catalog, null, 2)}\n`, 'utf8')
console.log(`Added ${plural.added.length} plural form(s) to ${fileName} from \`_other\`.`)
return { ok: true, catalog: plural.catalog }
}