返回 CodeWhale
config.ts
根目录 / web / lib / i18n / config.ts
1 /**
2 * Locale configuration for codewhale.net.
3 *
4 * This is the single canonical website locale registry — the locale
5 * switcher, Next.js route generation, middleware detection, sitemap, and
6 * hreflang alternates all derive from `ALL_LOCALES`. The cross-surface
7 * matrix (TUI packs, READMEs, website) lives in docs/LOCALIZATION.md.
8 *
9 * Status semantics:
10 * - `shipped` — full website parity with English on first-class pages.
11 * - `partial` — routed and selectable, but intentionally incomplete:
12 * chrome (nav/footer/switcher), the home page, and metadata
13 * are localized via web/lib/i18n/dictionaries/<code>/ and
14 * everything else falls back to English. The switcher marks
15 * these with a visible partial badge. No route 404s and no
16 * untranslated string ever renders a dictionary key.
17 * - `planned` — tracked in docs/LOCALIZATION.md with a target issue; not
18 * routed.
19 * - `deferred` — acknowledged but not scheduled; not routed.
20 *
21 * When adding a locale:
22 * 1. Add/flip the entry in `ALL_LOCALES` below.
23 * 2. Scaffold dictionaries under web/lib/i18n/dictionaries/<code>/
24 * (chrome.ts + home.ts — see dictionaries/en/ for the reference shape).
25 * 3. Run `npm run check:locales` (dictionary key parity) and `npm test`.
26 * 4. Update docs/LOCALIZATION.md.
27 */
28
29 /** Status of a locale relative to the website. */
30 export type LocaleStatus = "shipped" | "partial" | "planned" | "deferred";
31
32 export interface LocaleEntry {
33 /** ISO 639-1 or IETF BCP 47 language tag used in routes. */
34 code: string;
35 /** Display label (native script). */
36 label: string;
37 /** Status relative to the website. */
38 status: LocaleStatus;
39 /** One-line scope note shown to maintainers (not rendered). */
40 note?: string;
41 }
42
43 /**
44 * All locales the project tracks, ordered by priority.
45 *
46 * SHIPPED and PARTIAL locales are included in `locales` (the constrained
47 * set used by Next.js route generation and middleware). PLANNED and
48 * DEFERRED locales are listed here for the matrix but not routed.
49 */
50 export const ALL_LOCALES: LocaleEntry[] = [
51 { code: "en", label: "English", status: "shipped" },
52 {
53 code: "zh",
54 label: "中文",
55 status: "shipped",
56 // Shipped on the strength of translated first-class page BODIES
57 // (install, FAQ, community, contribute, models, runtime) — a chrome
58 // dictionary alone never earns `shipped`. Chrome + home moved to
59 // dictionaries/zh/ in #4934; the remaining page bodies are still
60 // inline `{ en, zh }` content modules awaiting the same move.
61 note: "#4934 — chrome + home dictionary-backed; first-class page bodies translated inline",
62 },
63 {
64 code: "ja",
65 label: "日本語",
66 status: "partial",
67 note: "#3091 — chrome + home localized; page bodies fall back to English",
68 },
69 {
70 code: "vi",
71 label: "Tiếng Việt",
72 status: "partial",
73 note: "#3091 — chrome + home localized; page bodies fall back to English",
74 },
75 {
76 code: "ko",
77 label: "한국어",
78 status: "partial",
79 note: "#3093 — chrome + home localized; page bodies fall back to English",
80 },
81 {
82 code: "ru",
83 label: "Русский",
84 status: "partial",
85 note: "#3092 — chrome + home localized; page bodies fall back to English",
86 },
87 {
88 code: "uk",
89 label: "Українська",
90 status: "partial",
91 note: "#4791 — shipped alongside Russian; same partial scope",
92 },
93 {
94 code: "es",
95 label: "Español",
96 status: "partial",
97 note: "#3093 — chrome + home localized; page bodies fall back to English",
98 },
99 {
100 code: "pt-BR",
101 label: "Português (BR)",
102 status: "partial",
103 note: "#3093 — chrome + home localized; page bodies fall back to English",
104 },
105 {
106 code: "fr",
107 label: "Français",
108 status: "planned",
109 note: "#4788 — TUI pack shipped in v0.9.2; website next wave",
110 },
111 {
112 code: "de",
113 label: "Deutsch",
114 status: "planned",
115 note: "#4788 — TUI pack shipped in v0.9.2; website next wave",
116 },
117 {
118 code: "ca",
119 label: "Català",
120 status: "planned",
121 note: "#4749/#4788 — TUI pack shipped in v0.9.2; website next wave",
122 },
123 {
124 code: "id",
125 label: "Bahasa Indonesia",
126 status: "partial",
127 note: "#4789 — chrome + home localized; page bodies fall back to English",
128 },
129 {
130 code: "hi",
131 label: "हिन्दी",
132 status: "planned",
133 note: "#4790 — TUI pack shipped in v0.9.2; website next wave",
134 },
135 {
136 code: "ar",
137 label: "العربية",
138 status: "deferred",
139 note: "RTL candidate — needs bidirectional layout/typography QA first",
140 },
141 ];
142
143 /**
144 * Active website locales (used by Next.js route generation, middleware,
145 * sitemap, and the switcher). Both `shipped` and `partial` locales route;
146 * `partial` locales carry a visible partial-pack status in the switcher.
147 */
148 export const locales = ALL_LOCALES.filter(
149 (l) => l.status === "shipped" || l.status === "partial",
150 ).map((l) => l.code) as readonly string[];
151
152 export type Locale = (typeof locales)[number];
153 export const defaultLocale: Locale = "en";
154
155 /** Locales whose packs are intentionally incomplete (English fallback). */
156 export const partialLocales = ALL_LOCALES.filter((l) => l.status === "partial").map(
157 (l) => l.code,
158 ) as readonly string[];
159
160 export function isPartialLocale(x: string): boolean {
161 return partialLocales.includes(x);
162 }
163
164 /** Set to "1" once the Gitee mirror at gitee.com/Hmbown/... exists. */
165 export const GITEE_ENABLED = process.env.NEXT_PUBLIC_GITEE_ENABLED === "1";
166
167 export function isValidLocale(x: string): x is Locale {
168 return (locales as readonly string[]).includes(x);
169 }
170
171 /** Check if a locale code is tracked (shipped, partial, planned, or deferred). */
172 export function isTrackedLocale(x: string): boolean {
173 return ALL_LOCALES.some((l) => l.code === x);
174 }
175
175 lines TYPESCRIPT