feat(i18n): add Japanese locale catalogs and negotiation (#3841)

Co-authored-by: Y.anbe <yoshitaka.anbe@aot.it.com>
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
This commit is contained in:
Y.anbe
2026-09-22 07:18:37 +09:00
committed by GitHub
parent ecbe2c6b03
commit cd08c50ca6
63 changed files with 11978 additions and 88 deletions
+14 -11
View File
@@ -3,8 +3,9 @@
Kandev's web UI is internationalized with [i18next](https://www.i18next.com) +
[react-i18next](https://react.i18next.com). English is the source locale,
European Portuguese (`pt-pt`), Simplified Chinese (`zh-cn`), Traditional Chinese
Taiwan (`zh-tw`), and Traditional Chinese Hong Kong (`zh-hk`) are the shipped
human locales, and a generated **pseudo-locale** is used for QA. Requirements live in
Taiwan (`zh-tw`), Traditional Chinese Hong Kong (`zh-hk`), and Japanese (`ja`,
endonym `日本語`) are the shipped human locales, and a generated **pseudo-locale**
is used for QA. Requirements live in
[`docs/specs/platform/requirements/i18n.md`](specs/platform/requirements/i18n.md). Traditional Chinese
catalogs are derived from `zh-cn` via OpenCC plus a product glossary; see
[`docs/specs/platform/requirements/traditional-chinese-locales.md`](specs/platform/requirements/traditional-chinese-locales.md)
@@ -27,8 +28,8 @@ Two enforcement layers, and it matters which is which:
the migration ran; that list is now the migration record, not the bound. See
[The guard is scoped](#the-guard-is-scoped-on-purpose) and
[Externalizing a path](#externalizing-a-path).
- **The four real locales gate the build.** `pt-pt`, `zh-cn`, `zh-hk` and `zh-tw`
are complete, and adding an `en` key without translating it fails
- **The five real locales gate the build.** `pt-pt`, `zh-cn`, `zh-hk`, `zh-tw`
and `ja` are complete, and adding an `en` key without translating it fails
`pnpm run i18n:check`. See [Real locales](#real-locale-catalogs-gate).
## TL;DR for adding a string
@@ -1162,8 +1163,9 @@ Do it in its own PR, not inside a copy-only migration.
## Adding a real language
Use a canonical lowercase locale id such as `zh-cn`. Locale negotiation accepts
case variants such as `zh-CN`, but cookies and `<html lang>` use the canonical id.
Use a canonical lowercase locale id such as `zh-cn` or `ja`. Locale negotiation
accepts case variants such as `zh-CN` and unambiguous region suffixes such as
`ja-JP` (collapsed onto `ja`); cookies and `<html lang>` use the canonical id.
For Traditional Chinese Taiwan / Hong Kong specifically, prefer converting from
`zh-cn` with `node scripts/convert-zh-cn-to-zh-hant.mjs --write` (OpenCC +
@@ -1218,11 +1220,12 @@ entries:
| zh-hk | 415 | 81 | 1 |
| zh-tw | 415 | 81 | 1 |
None of it visible in a green build. The four catalogs are now complete, so the
failure mode has flipped: the risk is no longer "a PR is blocked on a translator"
but "a PR silently re-opens the gap". So the pass **gates**, and the cost is that
adding user-facing copy means adding it in five languages. That is the real cost
of shipping five languages; it was previously being deferred rather than paid.
None of it visible in a green build. The four catalogs were completed first, then
`ja` joined them, so the failure mode has flipped: the risk is no longer "a PR is
blocked on a translator" but "a PR silently re-opens the gap". So the pass
**gates**, and the cost is that adding user-facing copy means adding it in six
languages (`en` plus five real locales). That is the real cost of shipping those
languages; it was previously being deferred rather than paid.
For `zh-tw` and `zh-hk`, do not hand-translate: run `pnpm run i18n:zh-hant`,
which converts `zh-cn` through OpenCC plus the product glossary and emits proper