Files
zeekay 595bb3b930 79 locales: right-to-left by script, regional variants in resolve
Add 43 locales to the registry, their loaders and catalogs: Persian,
Dari, Hebrew, Urdu, Pashto, Central Kurdish, Sindhi, Uyghur, Punjabi in
both scripts, four regional Arabics, Hong Kong Chinese, Mexican and
Argentine Spanish, European Portuguese, Canadian French, and 24 more.
34 wait for a native reader in review.json; ti, qu, gn, ay, bo and dv
are not shipped.

Every locale in Arabic or Hebrew script is rtl, and the registry test
derives the script from the tag. rtl.css selects on :dir(rtl), so it
holds inside a right-to-left region of a left-to-right page.

resolve no longer leans on registry order: after the exact tag and the
script, a request takes its own region, then the variant that serves it
(CLDR parents for es-419 and pt-PT, Macau to zh-HK, the Arabic dialect
areas), then the variant with no region, then the language's own region.
pnb reaches pa-PK.
2026-10-22 05:30:36 -07:00

78 lines
3.5 KiB
TypeScript

import assert from 'node:assert/strict'
import { test } from 'node:test'
import { DEFAULT_LOCALE, SUPPORTED_LOCALES, direction, locales, preferenceKey, translate } from '../src/index.ts'
test('79 locales, each a canonical tag with a name in its own script', () => {
assert.equal(locales.length, 79)
assert.equal(new Set(SUPPORTED_LOCALES).size, 79)
assert.deepEqual(SUPPORTED_LOCALES, locales.map((l) => l.code))
for (const l of locales) {
assert.equal(Intl.getCanonicalLocales(l.code)[0], l.code)
assert.ok(l.name && l.englishName, l.code)
}
assert.ok(SUPPORTED_LOCALES.includes(DEFAULT_LOCALE))
})
test('regional variants ship beside their language', () => {
for (const code of ['zh-Hans', 'zh-Hant', 'zh-HK', 'es-ES', 'es-419', 'es-MX', 'es-AR', 'pt-BR', 'pt-PT', 'fr-FR', 'fr-CA', 'ar', 'ar-EG', 'ar-SA', 'ar-MA', 'ar-LB', 'fa', 'fa-AF', 'pa', 'pa-PK']) {
assert.ok(SUPPORTED_LOCALES.includes(code as never), code)
}
})
test('the picker lists languages by English name', () => {
const names = locales.map((l) => l.englishName)
assert.deepEqual(names, [...names].sort((a, b) => a.localeCompare(b, 'en')))
})
/** Scripts written right to left, by their ISO 15924 code. */
const RTL = new Set(['Adlm', 'Arab', 'Hebr', 'Mand', 'Nkoo', 'Rohg', 'Samr', 'Syrc', 'Thaa'])
test('a locale is right-to-left exactly when its script is', () => {
// The script comes from the tag (`ur` → Arab, `pa-PK` → Arab, `ku` → Latn), not
// from a list kept beside the registry, so a new locale cannot ship the wrong way.
for (const l of locales) {
const script = new Intl.Locale(l.code).maximize().script ?? ''
assert.equal(l.dir, RTL.has(script) ? 'rtl' : 'ltr', `${l.code} (${script})`)
assert.equal(direction(l.code), l.dir, l.code)
}
assert.deepEqual(
locales.filter((l) => l.dir === 'rtl').map((l) => l.code).sort(),
['ar', 'ar-EG', 'ar-LB', 'ar-MA', 'ar-SA', 'ckb', 'fa', 'fa-AF', 'he', 'pa-PK', 'ps', 'sd', 'ug', 'ur'],
)
})
test('a request resolves to the direction of the locale it reaches', () => {
for (const tag of ['ar-AE', 'fa-IR', 'prs', 'iw', 'he-IL', 'ur-IN', 'ckb-IR', 'pa-Arab', 'pnb']) {
assert.equal(direction(tag), 'rtl', tag)
}
for (const tag of ['ku', 'kmr', 'pa-IN', 'en-GB', 'zh-HK']) {
assert.equal(direction(tag), 'ltr', tag)
}
assert.equal(direction(null), 'ltr')
})
test('a missing message is its English, with arguments filled', () => {
assert.equal(translate({ 'Hello {name}': 'مرحبا {name}' }, 'Hello {name}', { name: 'Ada' }), 'مرحبا Ada')
assert.equal(translate({}, 'Missing {count}'), 'Missing {count}')
assert.equal(translate({}, 'Missing {count}', { count: 0 }), 'Missing 0')
})
test('a source string is copy, not a prototype key', () => {
assert.equal(translate({}, 'constructor'), 'constructor')
assert.equal(translate({}, '__proto__'), '__proto__')
})
test('the source keeps its edge whitespace whatever the catalog holds', () => {
// Fragments are joined around links; a catalog that trimmed them would glue words.
assert.equal(translate({ ' and': 'und' }, ' and'), ' und')
assert.equal(translate({ 'Terms ': ' Bedingungen ' }, 'Terms '), 'Bedingungen ')
})
test('each account, and the signed-out visitor, keeps its own choice', () => {
assert.notEqual(preferenceKey('alice'), preferenceKey('bob'))
assert.notEqual(preferenceKey('alice'), preferenceKey())
assert.equal(preferenceKey(null), preferenceKey(undefined))
assert.equal(preferenceKey('a/b'), 'hanzo.locale.v1:a%2Fb')
})