feat(i18n): add Japanese (ja) locale (#1694)

* feat(i18n): add Japanese (ja) locale

- Add packages/app/src/i18n/resources/ja.ts with 1287 keys matching en.ts
- Register ja in i18next.ts, locales.ts (SupportedLocale, LANGUAGE_OPTIONS,
  SUPPORTED_LANGUAGES, LANGUAGE_NATIVE_NAMES, LANGUAGE_NAMES_BY_LOCALE,
  resolveSupportedLocale)
- Add ja label key to all six existing locale files (ar, en, es, fr, ru, zh-CN)
- Update locales.test.ts to cover ja in all relevant assertions

Translation reviewed by Codex across four passes; terminology, placeholder
parity, and naturalness confirmed clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(i18n): add ja to resources.test.ts and fix test descriptions

- Import ja and add to key-parity, fallback-ratio, interpolation, and
  model-count assertions in resources.test.ts
- Update test descriptions to say "all supported language(s)" instead of
  "UN official language(s)" (ja is not a UN official language)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add Japanese README (README.ja.md)

Add README.ja.md translated from the English source, reviewed by Codex
across two passes. Link it in the language navigation of README.md and
README.zh-CN.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: apply oxfmt formatting to locales.ts and ja.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysCat64
2026-06-24 13:11:35 +00:00
committed by GitHub
co-authored by Claude Sonnet 4.6
parent 70ea960153
commit 507345dbee
14 changed files with 2139 additions and 10 deletions
+8 -2
View File
@@ -5,6 +5,7 @@ import { ar } from "./resources/ar";
import { en } from "./resources/en";
import { es } from "./resources/es";
import { fr } from "./resources/fr";
import { ja } from "./resources/ja";
import { ru } from "./resources/ru";
import { zhCN } from "./resources/zh-CN";
@@ -101,21 +102,23 @@ function findUntranslatedConnectionErrors(): string[] {
}
describe("translation resources", () => {
it("keeps UN official language keys in sync with English", () => {
it("keeps all supported language keys in sync with English", () => {
const englishKeys = flattenKeys(en).sort();
expect(flattenKeys(ar).sort()).toEqual(englishKeys);
expect(flattenKeys(es).sort()).toEqual(englishKeys);
expect(flattenKeys(fr).sort()).toEqual(englishKeys);
expect(flattenKeys(ja).sort()).toEqual(englishKeys);
expect(flattenKeys(ru).sort()).toEqual(englishKeys);
expect(flattenKeys(zhCN).sort()).toEqual(englishKeys);
});
it("keeps non-English UN official languages translated beyond fallback labels", () => {
it("keeps non-English supported languages translated beyond fallback labels", () => {
const totalStrings = Object.keys(flattenStrings(en)).length;
const maxFallbackStrings = Math.floor(totalStrings * 0.25);
expect(countMatchingEnglishStrings(ar)).toBeLessThan(maxFallbackStrings);
expect(countMatchingEnglishStrings(es)).toBeLessThan(maxFallbackStrings);
expect(countMatchingEnglishStrings(fr)).toBeLessThan(maxFallbackStrings);
expect(countMatchingEnglishStrings(ja)).toBeLessThan(maxFallbackStrings);
expect(countMatchingEnglishStrings(ru)).toBeLessThan(maxFallbackStrings);
});
@@ -123,6 +126,7 @@ describe("translation resources", () => {
expect(findInterpolationMismatches(ar)).toEqual([]);
expect(findInterpolationMismatches(es)).toEqual([]);
expect(findInterpolationMismatches(fr)).toEqual([]);
expect(findInterpolationMismatches(ja)).toEqual([]);
expect(findInterpolationMismatches(ru)).toEqual([]);
expect(findInterpolationMismatches(zhCN)).toEqual([]);
});
@@ -137,11 +141,13 @@ describe("translation resources", () => {
expect(ar.modelSelector.modelCountPlural).toBe("{{count}} نماذج");
expect(es.modelSelector.modelCountPlural).toBe("{{count}} modelos");
expect(fr.modelSelector.modelCountPlural).toBe("{{count}} modèles");
expect(ja.modelSelector.modelCountPlural).toBe("{{count}}つのモデル");
expect(ru.modelSelector.modelCountPlural).toBe("{{count}} моделей");
expect(zhCN.modelSelector.modelCountPlural).toBe("{{count}} 个模型");
expect(ar.settings.providers.models.many).toBe("{{count}} نماذج");
expect(es.settings.providers.models.many).toBe("{{count}} modelos");
expect(fr.settings.providers.models.many).toBe("{{count}} modèles");
expect(ja.settings.providers.models.many).toBe("{{count}}つのモデル");
expect(ru.settings.providers.models.many).toBe("{{count}} моделей");
expect(zhCN.settings.providers.models.many).toBe("{{count}} 个 Model");
});