Skip to content

Internationalisation

English and Chinese are peers here. Neither is a translation of the other, and CI enforces it.

The guard

src/i18n/i18n.test.ts fails the build when:

  1. A key exists in one locale and not the other.
  2. Any value is empty.
  3. A zh-CN value is identical to its English counterpart.
  4. A zh-CN value contains Latin letters and no Han characters.

Rule 4 exists because rule 3 is not enough. profile.technical once held the English word "technical" against English "Technical contact" — different, and still plainly untranslated.

Brands (Ant Design, React), acronyms (CPU, API) and filenames (theme.ts, hero-dark.png) are explicitly allowed. Everything else must contain Han characters.

Typography is not shared

Latin and 汉字 do not share a metric. Han glyphs are dense and full-width with no ascender/descender rhythm, so Latin line-heights crush them and Latin tracking pulls them apart. tokens.css keeps two parallel scales:

css
:root {
  --text-base: 14px;
  --leading-normal: 1.55;
  --tracking-tight: -0.011em;
}

:root:lang(zh), :root:lang(ja), :root:lang(ko) {
  --text-base: 15px;      /* Han reads smaller at the same px size */
  --leading-normal: 1.75;
  --tracking-tight: 0;    /* negative tracking is actively wrong here */
  --weight-bold: 600;     /* avoid synthesised fake-bold */
}

The lang attribute on <html> is doing real work — it is the switch. buildTheme() mirrors the same split into antd's ConfigProvider.

Loading

Bundles are fetched on demand, so a reader in Shanghai does not download English copy they will never see:

ts
const LOADERS = {
  'en-US': () => import('./locales/en-US/common.json'),
  'zh-CN': () => import('./locales/zh-CN/common.json'),
};

Use changeLocale(), not i18n.changeLanguage() — the former loads the bundle before switching. Calling changeLanguage directly swaps the active language ahead of its strings and paints raw keys for a frame.

Adding a locale

  1. Add the folder under src/i18n/locales/.
  2. Add it to SUPPORTED_LOCALES and LOADERS.
  3. If it is CJK, add it to CJK_LOCALES and to the :lang() selector in tokens.css. Missing the second step is why a new Japanese locale looks subtly wrong.
  4. Map it to an antd locale in ThemeProvider.

Released under the MIT License.