How to enforce identical keys across multiple i18n JSON translation files in TypeScript?

clock icon

asked 2 months ago

message icon

0

eye icon

170

I’m working on a multilingual application with the following structure:

1messages/
2├── en.d.json.ts // baseline type definition
3├── en.json
4├── ja.json
5├── zh-CN.json
6└── zh-TW.json
7
1messages/
2├── en.d.json.ts // baseline type definition
3├── en.json
4├── ja.json
5├── zh-CN.json
6└── zh-TW.json
7

Each file has nested keys, for example:

1// en.json
2{
3 "navbar": {
4 "navbar-mobile-menu": {
5 "dashboard": "Dashboard",
6 "profile": "Profile",
7 "logout": "Logout"
8 },
9 "navbar-user-menu": {
10 "login": "Login",
11 "dashboard": "Dashboard",
12 "profile": "Profile",
13 "logout": "Logout"
14 }
15 }
16}
17
1// en.json
2{
3 "navbar": {
4 "navbar-mobile-menu": {
5 "dashboard": "Dashboard",
6 "profile": "Profile",
7 "logout": "Logout"
8 },
9 "navbar-user-menu": {
10 "login": "Login",
11 "dashboard": "Dashboard",
12 "profile": "Profile",
13 "logout": "Logout"
14 }
15 }
16}
17

I want TypeScript (and my IDE) to check at compile time that:

  1. All translation files (ja.json, zh-CN.json, zh-TW.json) have exactly the same keys as en.json.
  2. No keys are missing and no extra keys exist.
  3. Ideally, TypeScript should give me red squiggles in VSCode if a key is missing or spelled incorrectly.

How can I set this up in a clean and maintainable way?

2 Answers

To enforce identical keys across i18n JSON files:

  1. tsconfig.json: Enable resolveJsonModule and esModuleInterop.

    1{
    2 "compilerOptions": {
    3 "resolveJsonModule": true,
    4 "esModuleInterop": true,
    5 "strict": true
    6 }
    7}
    1{
    2 "compilerOptions": {
    3 "resolveJsonModule": true,
    4 "esModuleInterop": true,
    5 "strict": true
    6 }
    7}
  2. Define Base Type: Create messages/i18n.types.ts to infer the type from en.json.

    1// messages/i18n.types.ts
    2import en from './en.json';
    3export type TranslationKeys = typeof en;
    1// messages/i18n.types.ts
    2import en from './en.json';
    3export type TranslationKeys = typeof en;
  3. Validate All Files: For each JSON file, create a corresponding .ts file using satisfies (TypeScript 4.9+).

    1// messages/ja.ts
    2import ja from './ja.json';
    3import { TranslationKeys } from './i18n.types';
    4
    5const typedJa = ja satisfies TranslationKeys;
    6export default typedJa;
    1// messages/ja.ts
    2import ja from './ja.json';
    3import { TranslationKeys } from './i18n.types';
    4
    5const typedJa = ja satisfies TranslationKeys;
    6export default typedJa;

    Repeat for en.ts, zh-CN.ts, zh-TW.ts. Import these .ts files in your application.

Use your English file as the type source and make all others satisfy it:

1// messages/en.json
2{
3 "navbar": {
4 "navbar-mobile-menu": {
5 "dashboard": "Dashboard",
6 "profile": "Profile",
7 "logout": "Logout"
8 }
9 }
10}
11
12// messages/index.ts
13import en from "./en.json" assert { type: "json" };
14export type TranslationSchema = typeof en;
15
16import ja from "./ja.json" assert { type: "json" };
17import zhCN from "./zh-CN.json" assert { type: "json" };
18import zhTW from "./zh-TW.json" assert { type: "json" };
19
20void [
21 ja satisfies TranslationSchema,
22 zhCN satisfies TranslationSchema,
23 zhTW satisfies TranslationSchema,
24];
25
1// messages/en.json
2{
3 "navbar": {
4 "navbar-mobile-menu": {
5 "dashboard": "Dashboard",
6 "profile": "Profile",
7 "logout": "Logout"
8 }
9 }
10}
11
12// messages/index.ts
13import en from "./en.json" assert { type: "json" };
14export type TranslationSchema = typeof en;
15
16import ja from "./ja.json" assert { type: "json" };
17import zhCN from "./zh-CN.json" assert { type: "json" };
18import zhTW from "./zh-TW.json" assert { type: "json" };
19
20void [
21 ja satisfies TranslationSchema,
22 zhCN satisfies TranslationSchema,
23 zhTW satisfies TranslationSchema,
24];
25

✅ TypeScript will now show errors if any translation file has missing or extra keys.

1

Write your answer here

Top Questions