返回 CodeWhale
index.ts
根目录 / web / lib / i18n / dictionaries / index.ts
1 /**
2 * Dictionary loader for the website localization layer (#3091, #4934).
3 *
4 * Lookup is deterministic: a routed locale with a dictionary gets its own
5 * copy; every other locale gets the English reference dictionary. There is
6 * no per-key fallback chain — each shipped/partial dictionary is held to
7 * exact key parity with English by `web/scripts/check-locales.mjs` and
8 * `dictionaries.test.ts`, so a missing key is a build-time failure, never
9 * a runtime "missing marker".
10 *
11 * English resolves through the `?? enChrome` / `?? enHome` fallback rather
12 * than a map entry, which keeps `DICTIONARY_LOCALES` equal to the set of
13 * non-reference locale directories that `check-locales.mjs` walks.
14 */
15 import type {
16 ChangelogDict,
17 ChromeDict,
18 ComputerUseDict,
19 DocsAuthDict,
20 DocsComputersDict,
21 DocsConfigurationDict,
22 DocsConstitutionDict,
23 DocsFleetDict,
24 DocsGuideDict,
25 DocsHooksDict,
26 DocsMcpDict,
27 DocsRuntimeApiDict,
28 DocsSandboxDict,
29 DocsShellDict,
30 DocsModesDict,
31 DocsSubagentsDict,
32 DocsTroubleshootingDict,
33 DocsTrustDict,
34 DocsWebDict,
35 HomeDict,
36 StatesDict,
37 } from "./types";
38 import { chrome as enChrome } from "./en/chrome";
39 import { home as enHome } from "./en/home";
40 import { docsGuide as enDocsGuide } from "./en/docs-guide";
41 import { docsGuide as zhDocsGuide } from "./zh/docs-guide";
42 import { docsShell as enDocsShell } from "./en/docs-shell";
43 import { docsShell as zhDocsShell } from "./zh/docs-shell";
44 import { docsHooks as enDocsHooks } from "./en/docs-hooks";
45 import { docsHooks as zhDocsHooks } from "./zh/docs-hooks";
46 import { docsTroubleshooting as enDocsTroubleshooting } from "./en/docs-troubleshooting";
47 import { docsTroubleshooting as zhDocsTroubleshooting } from "./zh/docs-troubleshooting";
48 import { docsConfiguration as enDocsConfiguration } from "./en/docs-configuration";
49 import { docsConfiguration as zhDocsConfiguration } from "./zh/docs-configuration";
50 import { docsConstitution as enDocsConstitution } from "./en/docs-constitution";
51 import { docsConstitution as zhDocsConstitution } from "./zh/docs-constitution";
52 import { docsFleet as enDocsFleet } from "./en/docs-fleet";
53 import { docsFleet as zhDocsFleet } from "./zh/docs-fleet";
54 import { docsMcp as enDocsMcp } from "./en/docs-mcp";
55 import { docsMcp as zhDocsMcp } from "./zh/docs-mcp";
56 import { docsModes as enDocsModes } from "./en/docs-modes";
57 import { docsModes as zhDocsModes } from "./zh/docs-modes";
58 import { docsRuntimeApi as enDocsRuntimeApi } from "./en/docs-runtime-api";
59 import { docsRuntimeApi as zhDocsRuntimeApi } from "./zh/docs-runtime-api";
60 import { docsSandbox as enDocsSandbox } from "./en/docs-sandbox";
61 import { docsSandbox as zhDocsSandbox } from "./zh/docs-sandbox";
62 import { docsSubagents as enDocsSubagents } from "./en/docs-subagents";
63 import { docsSubagents as zhDocsSubagents } from "./zh/docs-subagents";
64 import { docsWeb as enDocsWeb } from "./en/docs-web";
65 import { docsWeb as zhDocsWeb } from "./zh/docs-web";
66 import { docsComputers as enDocsComputers } from "./en/docs-computers";
67 import { docsComputers as zhDocsComputers } from "./zh/docs-computers";
68 import { docsAuth as enDocsAuth } from "./en/docs-auth";
69 import { docsAuth as zhDocsAuth } from "./zh/docs-auth";
70 import { docsTrust as enDocsTrust } from "./en/docs-trust";
71 import { docsTrust as zhDocsTrust } from "./zh/docs-trust";
72 import { computerUse as enComputerUse } from "./en/computer-use";
73 import { computerUse as zhComputerUse } from "./zh/computer-use";
74 import { computerUse as jaComputerUse } from "./ja/computer-use";
75 import { computerUse as viComputerUse } from "./vi/computer-use";
76 import { computerUse as koComputerUse } from "./ko/computer-use";
77 import { computerUse as ruComputerUse } from "./ru/computer-use";
78 import { computerUse as ukComputerUse } from "./uk/computer-use";
79 import { computerUse as esComputerUse } from "./es/computer-use";
80 import { computerUse as frComputerUse } from "./fr/computer-use";
81 import { computerUse as deComputerUse } from "./de/computer-use";
82 import { computerUse as caComputerUse } from "./ca/computer-use";
83 import { computerUse as hiComputerUse } from "./hi/computer-use";
84 import { computerUse as trComputerUse } from "./tr/computer-use";
85 import { computerUse as itComputerUse } from "./it/computer-use";
86 import { computerUse as plComputerUse } from "./pl/computer-use";
87 import { computerUse as arComputerUse } from "./ar/computer-use";
88 import { computerUse as ptBrComputerUse } from "./pt-BR/computer-use";
89 import { computerUse as idComputerUse } from "./id/computer-use";
90 import { states as enStates } from "./en/states";
91 import { states as zhStates } from "./zh/states";
92 import { changelog as enChangelog } from "./en/changelog";
93 import { changelog as zhChangelog } from "./zh/changelog";
94 import { chrome as zhChrome } from "./zh/chrome";
95 import { home as zhHome } from "./zh/home";
96 import { chrome as jaChrome } from "./ja/chrome";
97 import { home as jaHome } from "./ja/home";
98 import { chrome as viChrome } from "./vi/chrome";
99 import { home as viHome } from "./vi/home";
100 import { chrome as koChrome } from "./ko/chrome";
101 import { home as koHome } from "./ko/home";
102 import { chrome as ruChrome } from "./ru/chrome";
103 import { home as ruHome } from "./ru/home";
104 import { chrome as ukChrome } from "./uk/chrome";
105 import { home as ukHome } from "./uk/home";
106 import { chrome as esChrome } from "./es/chrome";
107 import { home as esHome } from "./es/home";
108 import { chrome as frChrome } from "./fr/chrome";
109 import { home as frHome } from "./fr/home";
110 import { docsGuide as frDocsGuide } from "./fr/docs-guide";
111 import { chrome as deChrome } from "./de/chrome";
112 import { home as deHome } from "./de/home";
113 import { docsGuide as deDocsGuide } from "./de/docs-guide";
114 import { chrome as caChrome } from "./ca/chrome";
115 import { home as caHome } from "./ca/home";
116 import { docsGuide as caDocsGuide } from "./ca/docs-guide";
117 import { chrome as hiChrome } from "./hi/chrome";
118 import { home as hiHome } from "./hi/home";
119 import { docsGuide as hiDocsGuide } from "./hi/docs-guide";
120 import { chrome as trChrome } from "./tr/chrome";
121 import { home as trHome } from "./tr/home";
122 import { docsGuide as trDocsGuide } from "./tr/docs-guide";
123 import { chrome as itChrome } from "./it/chrome";
124 import { home as itHome } from "./it/home";
125 import { docsGuide as itDocsGuide } from "./it/docs-guide";
126 import { chrome as plChrome } from "./pl/chrome";
127 import { home as plHome } from "./pl/home";
128 import { docsGuide as plDocsGuide } from "./pl/docs-guide";
129 import { chrome as arChrome } from "./ar/chrome";
130 import { home as arHome } from "./ar/home";
131 import { docsGuide as arDocsGuide } from "./ar/docs-guide";
132 import { chrome as ptBrChrome } from "./pt-BR/chrome";
133 import { home as ptBrHome } from "./pt-BR/home";
134 import { chrome as idChrome } from "./id/chrome";
135 import { home as idHome } from "./id/home";
136
137 const CHROME: Record<string, ChromeDict> = {
138 zh: zhChrome,
139 ja: jaChrome,
140 vi: viChrome,
141 ko: koChrome,
142 ru: ruChrome,
143 uk: ukChrome,
144 es: esChrome,
145 fr: frChrome,
146 de: deChrome,
147 ca: caChrome,
148 hi: hiChrome,
149 tr: trChrome,
150 it: itChrome,
151 pl: plChrome,
152 ar: arChrome,
153 "pt-BR": ptBrChrome,
154 id: idChrome,
155 };
156
157 const HOME: Record<string, HomeDict> = {
158 zh: zhHome,
159 ja: jaHome,
160 vi: viHome,
161 ko: koHome,
162 ru: ruHome,
163 uk: ukHome,
164 es: esHome,
165 fr: frHome,
166 de: deHome,
167 ca: caHome,
168 hi: hiHome,
169 tr: trHome,
170 it: itHome,
171 pl: plHome,
172 ar: arHome,
173 "pt-BR": ptBrHome,
174 id: idHome,
175 };
176
177 /** Locales with their own dictionary directory (English is the reference). */
178 export const DICTIONARY_LOCALES = Object.keys(CHROME) as readonly string[];
179
180 /**
181 * Per-page dictionaries (#5337). Unlike chrome/home, a page dictionary is
182 * optional per locale: English is the required reference, any locale that
183 * ships the file is held to exact key parity, and everyone else falls back
184 * to English here — the same behavior page bodies already had for partial
185 * locales, now expressed through one lookup instead of an `isZh` ternary.
186 */
187 const DOCS_GUIDE: Record<string, DocsGuideDict> = {
188 zh: zhDocsGuide,
189 fr: frDocsGuide,
190 de: deDocsGuide,
191 ca: caDocsGuide,
192 hi: hiDocsGuide,
193 tr: trDocsGuide,
194 it: itDocsGuide,
195 pl: plDocsGuide,
196 ar: arDocsGuide,
197 };
198
199 const DOCS_SHELL: Record<string, DocsShellDict> = {
200 zh: zhDocsShell,
201 };
202
203 const DOCS_HOOKS: Record<string, DocsHooksDict> = {
204 zh: zhDocsHooks,
205 };
206
207 const DOCS_TROUBLESHOOTING: Record<string, DocsTroubleshootingDict> = {
208 zh: zhDocsTroubleshooting,
209 };
210
211 const DOCS_CONFIGURATION: Record<string, DocsConfigurationDict> = {
212 zh: zhDocsConfiguration,
213 };
214
215 const DOCS_CONSTITUTION: Record<string, DocsConstitutionDict> = {
216 zh: zhDocsConstitution,
217 };
218
219 const DOCS_FLEET: Record<string, DocsFleetDict> = {
220 zh: zhDocsFleet,
221 };
222
223 const DOCS_MCP: Record<string, DocsMcpDict> = {
224 zh: zhDocsMcp,
225 };
226
227 const DOCS_MODES: Record<string, DocsModesDict> = {
228 zh: zhDocsModes,
229 };
230
231 const DOCS_RUNTIME_API: Record<string, DocsRuntimeApiDict> = {
232 zh: zhDocsRuntimeApi,
233 };
234
235 const DOCS_SANDBOX: Record<string, DocsSandboxDict> = {
236 zh: zhDocsSandbox,
237 };
238
239 const DOCS_SUBAGENTS: Record<string, DocsSubagentsDict> = {
240 zh: zhDocsSubagents,
241 };
242
243 const DOCS_WEB: Record<string, DocsWebDict> = {
244 zh: zhDocsWeb,
245 };
246
247 const DOCS_COMPUTERS: Record<string, DocsComputersDict> = {
248 zh: zhDocsComputers,
249 };
250
251 const DOCS_AUTH: Record<string, DocsAuthDict> = {
252 zh: zhDocsAuth,
253 };
254
255 const COMPUTER_USE: Record<string, ComputerUseDict> = {
256 zh: zhComputerUse,
257 ja: jaComputerUse,
258 vi: viComputerUse,
259 ko: koComputerUse,
260 ru: ruComputerUse,
261 uk: ukComputerUse,
262 es: esComputerUse,
263 fr: frComputerUse,
264 de: deComputerUse,
265 ca: caComputerUse,
266 hi: hiComputerUse,
267 tr: trComputerUse,
268 it: itComputerUse,
269 pl: plComputerUse,
270 ar: arComputerUse,
271 "pt-BR": ptBrComputerUse,
272 id: idComputerUse,
273 };
274
275 const DOCS_TRUST: Record<string, DocsTrustDict> = {
276 zh: zhDocsTrust,
277 };
278
279 /**
280 * Shared surface states and the changelog page follow the same optional
281 * per-locale rule as the docs page dictionaries: English is the reference,
282 * every other locale falls back to it at lookup time.
283 */
284 const STATES: Record<string, StatesDict> = {
285 zh: zhStates,
286 };
287
288 const CHANGELOG: Record<string, ChangelogDict> = {
289 zh: zhChangelog,
290 };
291
292 export function getChrome(locale: string): ChromeDict {
293 return CHROME[locale] ?? enChrome;
294 }
295
296 export function getHome(locale: string): HomeDict {
297 return HOME[locale] ?? enHome;
298 }
299
300 export function getDocsGuide(locale: string): DocsGuideDict {
301 return DOCS_GUIDE[locale] ?? enDocsGuide;
302 }
303
304 export function getDocsShell(locale: string): DocsShellDict {
305 return DOCS_SHELL[locale] ?? enDocsShell;
306 }
307
308 export function getDocsHooks(locale: string): DocsHooksDict {
309 return DOCS_HOOKS[locale] ?? enDocsHooks;
310 }
311
312 export function getDocsTroubleshooting(locale: string): DocsTroubleshootingDict {
313 return DOCS_TROUBLESHOOTING[locale] ?? enDocsTroubleshooting;
314 }
315
316 export function getDocsConfiguration(locale: string): DocsConfigurationDict {
317 return DOCS_CONFIGURATION[locale] ?? enDocsConfiguration;
318 }
319
320 export function getDocsConstitution(locale: string): DocsConstitutionDict {
321 return DOCS_CONSTITUTION[locale] ?? enDocsConstitution;
322 }
323
324 export function getDocsFleet(locale: string): DocsFleetDict {
325 return DOCS_FLEET[locale] ?? enDocsFleet;
326 }
327
328 export function getDocsMcp(locale: string): DocsMcpDict {
329 return DOCS_MCP[locale] ?? enDocsMcp;
330 }
331
332 export function getDocsModes(locale: string): DocsModesDict {
333 return DOCS_MODES[locale] ?? enDocsModes;
334 }
335
336 export function getDocsRuntimeApi(locale: string): DocsRuntimeApiDict {
337 return DOCS_RUNTIME_API[locale] ?? enDocsRuntimeApi;
338 }
339
340 export function getDocsSandbox(locale: string): DocsSandboxDict {
341 return DOCS_SANDBOX[locale] ?? enDocsSandbox;
342 }
343
344 export function getDocsSubagents(locale: string): DocsSubagentsDict {
345 return DOCS_SUBAGENTS[locale] ?? enDocsSubagents;
346 }
347
348 export function getDocsWeb(locale: string): DocsWebDict {
349 return DOCS_WEB[locale] ?? enDocsWeb;
350 }
351
352 export function getDocsComputers(locale: string): DocsComputersDict {
353 return DOCS_COMPUTERS[locale] ?? enDocsComputers;
354 }
355
356 export function getDocsAuth(locale: string): DocsAuthDict {
357 return DOCS_AUTH[locale] ?? enDocsAuth;
358 }
359
360 export function getDocsTrust(locale: string): DocsTrustDict {
361 return DOCS_TRUST[locale] ?? enDocsTrust;
362 }
363
364 export function getComputerUse(locale: string): ComputerUseDict {
365 return COMPUTER_USE[locale] ?? enComputerUse;
366 }
367
368 export function getStates(locale: string): StatesDict {
369 return STATES[locale] ?? enStates;
370 }
371
372 export function getChangelog(locale: string): ChangelogDict {
373 return CHANGELOG[locale] ?? enChangelog;
374 }
375
376 /**
377 * Select one side of a legacy `{ en, zh }` content pair by locale. This is
378 * the transitional bridge for `web/lib/content/` modules that still carry
379 * two-language pairs (#5337 Phase 3 dissolves them into dictionaries): it
380 * moves the branch out of page TSX and into the i18n layer, so call sites
381 * stay locale-agnostic.
382 */
383 export function pickText(pair: { en: string; zh: string }, locale: string): string {
384 return locale === "zh" ? pair.zh : pair.en;
385 }
386
387 /** Reference dictionaries (parity baseline for the locale checks). */
388 export const EN_CHROME = enChrome;
389 export const EN_HOME = enHome;
390 export const EN_DOCS_GUIDE = enDocsGuide;
391 export const EN_DOCS_SHELL = enDocsShell;
392 export const EN_DOCS_HOOKS = enDocsHooks;
393 export const EN_DOCS_TROUBLESHOOTING = enDocsTroubleshooting;
394 export const EN_DOCS_CONFIGURATION = enDocsConfiguration;
395 export const EN_DOCS_CONSTITUTION = enDocsConstitution;
396 export const EN_DOCS_FLEET = enDocsFleet;
397 export const EN_DOCS_MCP = enDocsMcp;
398 export const EN_DOCS_MODES = enDocsModes;
399 export const EN_DOCS_RUNTIME_API = enDocsRuntimeApi;
400 export const EN_DOCS_SANDBOX = enDocsSandbox;
401 export const EN_DOCS_SUBAGENTS = enDocsSubagents;
402 export const EN_DOCS_WEB = enDocsWeb;
403 export const EN_DOCS_COMPUTERS = enDocsComputers;
404 export const EN_DOCS_AUTH = enDocsAuth;
405 export const EN_DOCS_TRUST = enDocsTrust;
406 export const EN_COMPUTER_USE = enComputerUse;
407 export const EN_STATES = enStates;
408 export const EN_CHANGELOG = enChangelog;
409
410 /** Interpolate `{name}` tokens in a dictionary template. Unknown tokens are
411 * left intact so a template/variable drift is visible in review, not silent. */
412 export function fill(template: string, vars: Record<string, string | number>): string {
413 return template.replace(/\{(\w+)\}/g, (match, name: string) =>
414 name in vars ? String(vars[name]) : match,
415 );
416 }
417
418 /**
419 * Split a template on a single `{token}` so a call site can typeset the
420 * substituted value as its own element without concatenating translated
421 * fragments around it. Returns the literal parts in template order — the
422 * caller interleaves its node between them, so a locale that puts the token
423 * in a different position still renders correctly.
424 */
425 export function splitToken(template: string, token: string): string[] {
426 return template.split(`{${token}}`);
427 }
428
429 /**
430 * Split a template on every `{token}` it carries, for a sentence with more
431 * than one substituted node. Returns literal text and token names
432 * interleaved in template order, so a locale that reorders the tokens still
433 * renders correctly and no translated fragment is concatenated by the
434 * call site.
435 */
436 export function splitTokens(template: string): Array<{ text: string } | { token: string }> {
437 return template
438 .split(/\{(\w+)\}/g)
439 .map((part, i) => (i % 2 === 1 ? { token: part } : { text: part }))
440 .filter((part) => "token" in part || part.text !== "");
441 }
442
442 lines TYPESCRIPT