i18n: use zh-Hant for Chinese (Traditional)

This commit is contained in:
German Khokhlov committed 2026-10-27 10:20:54 +04:00
1 parent ddae593bb7
commit 8f16cf263b
15 files changed
+139 -110

No files matched your search

+1 -1
View File
@@ -36,7 +36,7 @@ for (const [age, zh, en, tw] of cases) {
assert.equal(methods.getStats().loadingLabel, `LIVE · updated ${en}`);
setLocale('zh-CN');
assert.equal(methods.getStats().loadingLabel, `实时 · 更新于 ${zh}`);
setLocale('zh-TW');
setLocale('zh-Hant');
assert.equal(methods.getStats().loadingLabel, `即時 · 更新於 ${tw}`);
} finally {
setLocale(previousLocale);
+7 -5
View File
@@ -39,12 +39,14 @@ test('normalizeLocale folds regional variants and rejects unshipped tags', () =>
assert.equal(normalizeLocale('zh'), 'zh-CN');
assert.equal(normalizeLocale('zh-CN'), 'zh-CN');
assert.equal(normalizeLocale('zh-Hans-SG'), 'zh-CN');
assert.equal(normalizeLocale('zh-TW'), 'zh-TW');
assert.equal(normalizeLocale('zh-Hant-HK'), 'zh-TW');
assert.equal(normalizeLocale('zh-Hant'), 'zh-Hant');
assert.equal(normalizeLocale(' ZH_hant '), 'zh-Hant');
assert.equal(normalizeLocale('zh-TW'), 'zh-Hant');
assert.equal(normalizeLocale('zh-Hant-HK'), 'zh-Hant');
assert.equal(normalizeLocale('zh-Hans-TW'), 'zh-CN');
assert.equal(normalizeLocale('zh-Hant-CN'), 'zh-TW');
assert.equal(normalizeLocale('zh_HK'), 'zh-TW');
assert.equal(normalizeLocale('zh-MO'), 'zh-TW');
assert.equal(normalizeLocale('zh-Hant-CN'), 'zh-Hant');
assert.equal(normalizeLocale('zh_HK'), 'zh-Hant');
assert.equal(normalizeLocale('zh-MO'), 'zh-Hant');
assert.equal(normalizeLocale(undefined), null);
});
+2 -2
View File
@@ -19,9 +19,9 @@ import {
import es from './locales/es.json' with { type: 'json' };
import ru from './locales/ru.json' with { type: 'json' };
import zhCN from './locales/zh-CN.json' with { type: 'json' };
import zhTW from './locales/zh-TW.json' with { type: 'json' };
import zhHant from './locales/zh-Hant.json' with { type: 'json' };
const CATALOGS = Object.freeze({ es, ru, 'zh-CN': zhCN, 'zh-TW': zhTW });
const CATALOGS = Object.freeze({ es, ru, 'zh-CN': zhCN, 'zh-Hant': zhHant });
let currentLocale = DEFAULT_LOCALE;
+6 -5
View File
@@ -8,15 +8,15 @@ export const LOCALE_STORAGE_KEY = 'gev:locale:v1';
export const DEFAULT_LOCALE = 'en';
/** Shipped locales; order does not matter. Adding one: docs/TRANSLATORS.md. */
export const LOCALES = Object.freeze(['en', 'es', 'ru', 'zh-CN', 'zh-TW']);
export const LOCALES = Object.freeze(['en', 'es', 'ru', 'zh-CN', 'zh-Hant']);
/** Each locale named in its own language, for the selector. */
export const LOCALE_NAMES = Object.freeze({
en: 'English',
es: 'Español',
ru: 'Русский',
'zh-CN': '简体中文',
'zh-TW': '繁體中文',
'zh-CN': '中文(简体)',
'zh-Hant': '中文(繁體)',
});
/** Right-to-left locales. */
@@ -24,7 +24,8 @@ const RTL_LOCALES = Object.freeze([]);
/**
* Map any language tag to a shipped locale: 'ru-RU' and 'RU' → 'ru',
* 'zh', 'zh-CN' and 'zh-Hans-SG' → 'zh-CN'.
* 'zh', 'zh-CN' and 'zh-Hans-SG' → 'zh-CN';
* 'zh-Hant', 'zh-TW', 'zh-HK' and 'zh-MO' → 'zh-Hant'.
* @returns {string|null}
*/
export function normalizeLocale(candidate) {
@@ -33,7 +34,7 @@ export function normalizeLocale(candidate) {
if (primary === 'zh') {
if (rest.includes('hans')) return 'zh-CN';
return rest.some((part) => ['hant', 'tw', 'hk', 'mo'].includes(part))
? 'zh-TW'
? 'zh-Hant'
: 'zh-CN';
}
return LOCALES.includes(primary) ? primary : null;
File renamed without changes.
+1 -1
View File
@@ -193,7 +193,7 @@
"ΔN",
"▲ {frp} MW"
],
"zh-TW": [
"zh-Hant": [
"1090 MHz",
"978 MHz UAT",
"AIS: --",
@@ -1,7 +1,12 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { setLocale, t, tn, getLocale } from './index.js';
import { resolveLocale, LOCALE_STORAGE_KEY } from './locale.js';
import {
resolveLocale,
LOCALE_STORAGE_KEY,
writeStoredLocale,
applyDocumentLanguage,
} from './locale.js';
import { hudQuantity, LOCALIZED_LOCALITY } from '../hudLocalityFormat.js';
import { formatCockpitDistance } from '../ui/cockpitPresentation.js';
import { READOUT_WORDS } from '../layers/recentImagery/display.js';
@@ -14,7 +19,7 @@ test('Traditional Chinese resolves from URL, saved preference and browser langua
storage: { getItem: () => 'en' },
languages: ['en'],
}),
'zh-TW',
'zh-Hant',
);
assert.equal(
resolveLocale({
@@ -24,7 +29,7 @@ test('Traditional Chinese resolves from URL, saved preference and browser langua
},
languages: ['en'],
}),
'zh-TW',
'zh-Hant',
);
assert.equal(
resolveLocale({
@@ -32,14 +37,29 @@ test('Traditional Chinese resolves from URL, saved preference and browser langua
storage: { getItem: () => null },
languages: ['zh-Hant', 'en'],
}),
'zh-TW',
'zh-Hant',
);
});
test('Traditional Chinese persists and exposes the script locale for legacy choices', () => {
const stored = new Map();
assert.equal(
writeStoredLocale('zh-TW', {
setItem: (key, value) => stored.set(key, value),
}),
true,
);
assert.equal(stored.get(LOCALE_STORAGE_KEY), 'zh-Hant');
const doc = { documentElement: {} };
assert.equal(applyDocumentLanguage(doc, 'zh-TW'), true);
assert.equal(doc.documentElement.lang, 'zh-Hant');
assert.equal(doc.documentElement.dir, 'ltr');
});
test('Traditional Chinese readouts preserve unit case, coordinates and natural relative dates', () => {
const previous = getLocale();
try {
setLocale('zh-TW');
setLocale('zh-Hant');
assert.equal(hudQuantity(2069, 'km', 1), '2069.0 km');
assert.equal(formatCockpitDistance(14000), '14\u00a0km');
assert.equal(
+9 -3
View File
@@ -20,6 +20,11 @@ export const LOCALE_SEARCH_THRESHOLD = 8;
/** Type-ahead keystrokes closer together than this extend one prefix. */
export const TYPEAHEAD_RESET_MS = 500;
const CHINESE_LABELS = {
'zh-CN': 'Chinese (Simplified)',
'zh-Hant': 'Chinese (Traditional)',
};
/**
* Text folded for search: no diacritics, lower case. 'Español' → 'espanol',
* 'Ёжик' → 'ежик'.
@@ -464,12 +469,13 @@ export function mountLocalePicker({
item.dataset.locale = option.code;
const name = element(documentRef, 'span', 'locale-picker-native');
name.textContent = option.nativeName;
// Every row reads the same: the language named in itself, then its code.
// The interface-language name only helps the search find it.
// Chinese variants are distinguished by script rather than region.
const meta = element(documentRef, 'span', 'locale-picker-meta');
const codeLabel = element(documentRef, 'span', 'locale-picker-option-code');
codeLabel.setAttribute('translate', 'no');
codeLabel.textContent = option.code.toUpperCase();
codeLabel.textContent =
CHINESE_LABELS[option.code] || option.code.toUpperCase();
if (CHINESE_LABELS[option.code]) codeLabel.lang = 'en';
meta.append(codeLabel);
const text = element(documentRef, 'span', 'locale-picker-text');
text.append(name, meta);
+4 -4
View File
@@ -40,7 +40,7 @@ test('the current language comes first, the rest by their own names', () => {
nativeNames: LOCALE_NAMES,
}),
),
['ru', 'en', 'es', 'zh-CN', 'zh-TW'],
['ru', 'en', 'es', 'zh-CN', 'zh-Hant'],
);
assert.deepEqual(
codes(
@@ -50,7 +50,7 @@ test('the current language comes first, the rest by their own names', () => {
nativeNames: LOCALE_NAMES,
}),
),
['es', 'en', 'ru', 'zh-CN', 'zh-TW'],
['es', 'en', 'ru', 'zh-CN', 'zh-Hant'],
);
assert.deepEqual(
codes(
@@ -60,7 +60,7 @@ test('the current language comes first, the rest by their own names', () => {
nativeNames: LOCALE_NAMES,
}),
),
['en', 'es', 'ru', 'zh-CN', 'zh-TW'],
['en', 'es', 'ru', 'zh-CN', 'zh-Hant'],
);
const rows = buildLocaleOptions({
locales: MANY,
@@ -263,7 +263,7 @@ test('keyboard model: open on the current language, move, choose, close', () =>
assert.deepEqual(model.keydown({ key: 'ArrowDown' }), { handled: true });
assert.equal(model.activeOption.code, 'en');
model.keydown({ key: 'End' });
assert.equal(model.activeOption.code, 'zh-TW');
assert.equal(model.activeOption.code, 'zh-Hant');
model.keydown({ key: 'Home' });
assert.equal(model.activeOption.code, 'ru');
model.keydown({ key: 'ArrowUp' });