@adminiumjs/i18n 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a11y-keys.d.ts +17 -0
- package/dist/a11y-keys.d.ts.map +1 -0
- package/dist/a11y-keys.js +478 -0
- package/dist/a11y-keys.js.map +1 -0
- package/dist/create-i18n.d.ts.map +1 -1
- package/dist/create-i18n.js +37 -4
- package/dist/create-i18n.js.map +1 -1
- package/dist/editing.d.ts +17 -0
- package/dist/editing.d.ts.map +1 -0
- package/dist/editing.js +16 -0
- package/dist/editing.js.map +1 -0
- package/dist/fallback.d.ts +24 -0
- package/dist/fallback.d.ts.map +1 -0
- package/dist/fallback.js +40 -0
- package/dist/fallback.js.map +1 -0
- package/dist/format-errors.d.ts +41 -0
- package/dist/format-errors.d.ts.map +1 -0
- package/dist/format-errors.js +63 -0
- package/dist/format-errors.js.map +1 -0
- package/dist/icu-format.d.ts +4 -0
- package/dist/icu-format.d.ts.map +1 -1
- package/dist/icu-format.js +43 -3
- package/dist/icu-format.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/keys.d.ts +33 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +77 -0
- package/dist/keys.js.map +1 -0
- package/dist/locales.d.ts +102 -13
- package/dist/locales.d.ts.map +1 -1
- package/dist/locales.js +142 -17
- package/dist/locales.js.map +1 -1
- package/dist/overrides.d.ts +62 -0
- package/dist/overrides.d.ts.map +1 -0
- package/dist/overrides.js +112 -0
- package/dist/overrides.js.map +1 -0
- package/dist/react.d.ts +6 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +17 -26
- package/dist/react.js.map +1 -1
- package/dist/resources/ar-eg/common.d.ts +96 -0
- package/dist/resources/ar-eg/common.d.ts.map +1 -1
- package/dist/resources/ar-eg/common.js +103 -7
- package/dist/resources/ar-eg/common.js.map +1 -1
- package/dist/resources/ar-eg/ui.d.ts +586 -586
- package/dist/resources/ar-eg/ui.js +586 -586
- package/dist/resources/ar-eg/ui.js.map +1 -1
- package/dist/resources/cs-cz/common.d.ts +96 -0
- package/dist/resources/cs-cz/common.d.ts.map +1 -1
- package/dist/resources/cs-cz/common.js +101 -5
- package/dist/resources/cs-cz/common.js.map +1 -1
- package/dist/resources/cs-cz/ui.d.ts +582 -582
- package/dist/resources/cs-cz/ui.js +582 -582
- package/dist/resources/cs-cz/ui.js.map +1 -1
- package/dist/resources/da-dk/common.d.ts +97 -1
- package/dist/resources/da-dk/common.d.ts.map +1 -1
- package/dist/resources/da-dk/common.js +104 -8
- package/dist/resources/da-dk/common.js.map +1 -1
- package/dist/resources/da-dk/ui.d.ts +568 -568
- package/dist/resources/da-dk/ui.js +568 -568
- package/dist/resources/da-dk/ui.js.map +1 -1
- package/dist/resources/de-de/common.d.ts +100 -4
- package/dist/resources/de-de/common.d.ts.map +1 -1
- package/dist/resources/de-de/common.js +107 -11
- package/dist/resources/de-de/common.js.map +1 -1
- package/dist/resources/de-de/ui.d.ts +569 -569
- package/dist/resources/de-de/ui.js +569 -569
- package/dist/resources/de-de/ui.js.map +1 -1
- package/dist/resources/en-us/common.d.ts +96 -0
- package/dist/resources/en-us/common.d.ts.map +1 -1
- package/dist/resources/en-us/common.js +103 -7
- package/dist/resources/en-us/common.js.map +1 -1
- package/dist/resources/fr-fr/common.d.ts +98 -2
- package/dist/resources/fr-fr/common.d.ts.map +1 -1
- package/dist/resources/fr-fr/common.js +105 -9
- package/dist/resources/fr-fr/common.js.map +1 -1
- package/dist/resources/fr-fr/ui.d.ts +559 -559
- package/dist/resources/fr-fr/ui.js +559 -559
- package/dist/resources/fr-fr/ui.js.map +1 -1
- package/dist/resources/zh-cn/common.d.ts +97 -1
- package/dist/resources/zh-cn/common.d.ts.map +1 -1
- package/dist/resources/zh-cn/common.js +104 -8
- package/dist/resources/zh-cn/common.js.map +1 -1
- package/dist/resources/zh-cn/ui.d.ts +575 -575
- package/dist/resources/zh-cn/ui.js +575 -575
- package/dist/resources/zh-cn/ui.js.map +1 -1
- package/dist/resources/zh-tw/common.d.ts +96 -0
- package/dist/resources/zh-tw/common.d.ts.map +1 -1
- package/dist/resources/zh-tw/common.js +103 -7
- package/dist/resources/zh-tw/common.js.map +1 -1
- package/dist/resources/zh-tw/ui.d.ts +575 -575
- package/dist/resources/zh-tw/ui.js +575 -575
- package/dist/resources/zh-tw/ui.js.map +1 -1
- package/dist/review-status.d.ts +55 -0
- package/dist/review-status.d.ts.map +1 -0
- package/dist/review-status.js +59 -0
- package/dist/review-status.js.map +1 -0
- package/dist/revision.d.ts +35 -0
- package/dist/revision.d.ts.map +1 -0
- package/dist/revision.js +55 -0
- package/dist/revision.js.map +1 -0
- package/dist/server.d.ts +36 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +35 -0
- package/dist/server.js.map +1 -0
- package/dist/validate-message.d.ts +57 -0
- package/dist/validate-message.d.ts.map +1 -0
- package/dist/validate-message.js +178 -0
- package/dist/validate-message.js.map +1 -0
- package/locales/ar-EG/.meta.json +11134 -0
- package/locales/ar-EG/common.json +103 -7
- package/locales/ar-EG/ui.json +586 -586
- package/locales/cs-CZ/.meta.json +11134 -0
- package/locales/cs-CZ/common.json +101 -5
- package/locales/cs-CZ/ui.json +582 -582
- package/locales/da-DK/.meta.json +11134 -0
- package/locales/da-DK/common.json +104 -8
- package/locales/da-DK/ui.json +568 -568
- package/locales/de-DE/.meta.json +11134 -0
- package/locales/de-DE/common.json +107 -11
- package/locales/de-DE/ui.json +569 -569
- package/locales/en-US/common.json +103 -7
- package/locales/fr-FR/.meta.json +11134 -0
- package/locales/fr-FR/common.json +105 -9
- package/locales/fr-FR/ui.json +559 -559
- package/locales/zh-CN/.meta.json +11134 -0
- package/locales/zh-CN/common.json +104 -8
- package/locales/zh-CN/ui.json +575 -575
- package/locales/zh-TW/.meta.json +11134 -0
- package/locales/zh-TW/common.json +103 -7
- package/locales/zh-TW/ui.json +575 -575
- package/package.json +10 -2
package/dist/locales.d.ts
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Canonical locale registry (10-i18n-theming.md §2.1)
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* Canonical locale registry (10-i18n-theming.md §2.1) plus the runtime
|
|
3
|
+
* overlay that lets an admin add locales the build never saw
|
|
4
|
+
* (23-runtime-translations.md §5).
|
|
5
|
+
*
|
|
6
|
+
* Two id types, and the split is load-bearing:
|
|
7
|
+
*
|
|
8
|
+
* - {@link BuiltinLocaleId} is the closed union of the eight COMPILED
|
|
9
|
+
* locales. Everything that must stay exhaustive keys off it —
|
|
10
|
+
* `REVIEW_STATUS`, the parity gate, `gen-resources.mjs`, `lazy.ts` — so a
|
|
11
|
+
* ninth *compiled* locale is still a compile error, and no runtime locale
|
|
12
|
+
* can ever reach a build-time gate.
|
|
13
|
+
* - {@link LocaleId} additionally admits runtime ids. `(string & {})` keeps
|
|
14
|
+
* literal autocomplete for the eight while accepting anything a custom
|
|
15
|
+
* locale row can hold.
|
|
16
|
+
*
|
|
17
|
+
* Every lookup helper here is TOTAL: none of them throws on an unknown id.
|
|
18
|
+
* That is deliberate. Once locales are data, the orphan case — a deleted
|
|
19
|
+
* locale still referenced by a user pref, a restored backup, an imported
|
|
20
|
+
* config bundle — is the NORMAL aftermath of the feature, not an edge case,
|
|
21
|
+
* and these helpers run inside a theme subscriber whose emitter has no
|
|
22
|
+
* per-listener isolation. A throw there is a white screen, not a warning.
|
|
23
|
+
*
|
|
24
|
+
* `dir` is still derived from the locale, never independently settable
|
|
25
|
+
* (02-design-system.md §4.2).
|
|
8
26
|
*/
|
|
9
27
|
export interface LocaleEntry {
|
|
10
28
|
/** Canonical underscore id stored in `adminium_settings`/`adminium_user_prefs`. */
|
|
@@ -24,6 +42,23 @@ export interface LocaleEntry {
|
|
|
24
42
|
*/
|
|
25
43
|
fontHint: 'latin' | 'arabic' | 'cjk';
|
|
26
44
|
}
|
|
45
|
+
/** A runtime locale entry, plus the fields only the DB registry knows. */
|
|
46
|
+
export interface RuntimeLocaleEntry extends LocaleEntry {
|
|
47
|
+
/** False for admin-created locales. */
|
|
48
|
+
builtin: boolean;
|
|
49
|
+
/** Offered in pickers? (23 §3.1 `enabled`.) */
|
|
50
|
+
enabled: boolean;
|
|
51
|
+
/** Picker ordering. */
|
|
52
|
+
sortOrder: number;
|
|
53
|
+
/**
|
|
54
|
+
* The REAL BCP-47 tag whose `Intl` behaviour this locale borrows (23 §5.6).
|
|
55
|
+
* For a built-in this is just `tag`; for a custom locale it is the admin's
|
|
56
|
+
* choice, because `tag` itself may be something `Intl` has never heard of.
|
|
57
|
+
*/
|
|
58
|
+
intlTag: string;
|
|
59
|
+
/** Plural categories frozen at create time from `intlTag`. */
|
|
60
|
+
pluralCategories: readonly string[];
|
|
61
|
+
}
|
|
27
62
|
export declare const LOCALES: readonly [{
|
|
28
63
|
readonly id: "en_US";
|
|
29
64
|
readonly tag: "en-US";
|
|
@@ -81,19 +116,73 @@ export declare const LOCALES: readonly [{
|
|
|
81
116
|
readonly dir: "rtl";
|
|
82
117
|
readonly fontHint: "arabic";
|
|
83
118
|
}];
|
|
84
|
-
|
|
85
|
-
export
|
|
119
|
+
/** The eight COMPILED locales — the exhaustive axis (23 §5.1). */
|
|
120
|
+
export type BuiltinLocaleId = (typeof LOCALES)[number]['id'];
|
|
121
|
+
/**
|
|
122
|
+
* Any locale id the running instance may hold — the eight compiled ones plus
|
|
123
|
+
* whatever an admin created. `(string & {})` preserves literal autocomplete
|
|
124
|
+
* for the eight instead of collapsing to a bare `string`.
|
|
125
|
+
*/
|
|
126
|
+
export type LocaleId = BuiltinLocaleId | (string & {});
|
|
127
|
+
export declare const BUILTIN_LOCALE_IDS: readonly BuiltinLocaleId[];
|
|
128
|
+
/** @deprecated Prefer {@link BUILTIN_LOCALE_IDS} (or the runtime list) by name. */
|
|
129
|
+
export declare const LOCALE_IDS: readonly ("en_US" | "de_DE" | "fr_FR" | "cs_CZ" | "da_DK" | "zh_CN" | "zh_TW" | "ar_EG")[];
|
|
130
|
+
/**
|
|
131
|
+
* Canonical id shape: a language subtag plus up to two more (script, region),
|
|
132
|
+
* e.g. `en_US`, `sw_KE`, `zh_Hant_TW`. Three subtags is why the DB column is
|
|
133
|
+
* `varchar(35)` and why {@link tagFromLocaleId} replaces EVERY underscore.
|
|
134
|
+
*/
|
|
135
|
+
export declare const LOCALE_ID_RE: RegExp;
|
|
136
|
+
/** Replace the runtime registry wholesale (boot, and every version bump). */
|
|
137
|
+
export declare function setRuntimeLocales(entries: readonly RuntimeLocaleEntry[]): void;
|
|
138
|
+
/** Drop the runtime registry — tests, and teardown. */
|
|
139
|
+
export declare function resetRuntimeLocales(): void;
|
|
140
|
+
/** Every locale the instance knows: compiled defaults merged under runtime rows. */
|
|
141
|
+
export declare function allLocales(): readonly RuntimeLocaleEntry[];
|
|
142
|
+
/** The locales a picker may offer, in picker order (23 §3.1 `enabled`). */
|
|
143
|
+
export declare function availableLocales(): readonly RuntimeLocaleEntry[];
|
|
144
|
+
/** True for one of the eight COMPILED locales. */
|
|
145
|
+
export declare function isBuiltinLocaleId(value: unknown): value is BuiltinLocaleId;
|
|
146
|
+
/**
|
|
147
|
+
* True for anything shaped like a locale id. This is a SHAPE check, not a
|
|
148
|
+
* membership check: a cached preference may name a locale this build does not
|
|
149
|
+
* compile in, and rejecting it would strand the user on en-US. Use
|
|
150
|
+
* {@link isBuiltinLocaleId} when compiled membership is what you mean.
|
|
151
|
+
*/
|
|
86
152
|
export declare function isLocaleId(value: unknown): value is LocaleId;
|
|
153
|
+
/**
|
|
154
|
+
* `en_US` → `en-US`, `zh_Hant_TW` → `zh-Hant-TW`.
|
|
155
|
+
*
|
|
156
|
+
* Replaces EVERY underscore. A single-replacement version leaves
|
|
157
|
+
* `zh-Hant_TW`, which is not a valid BCP-47 tag, and the formatter layer
|
|
158
|
+
* coalesces invalid tags to `en-US` — silently degrading exactly the way the
|
|
159
|
+
* admin-chosen `intlTag` exists to prevent.
|
|
160
|
+
*/
|
|
161
|
+
export declare function tagFromLocaleId(id: string): string;
|
|
162
|
+
/**
|
|
163
|
+
* The presentation record for a locale: compiled entry, else runtime entry,
|
|
164
|
+
* else a synthesized one. Total by contract — see the file header for why
|
|
165
|
+
* throwing here is a white screen.
|
|
166
|
+
*/
|
|
87
167
|
export declare function localeEntry(id: LocaleId): LocaleEntry;
|
|
88
|
-
/** `'rtl'` iff the registry says so
|
|
168
|
+
/** `'rtl'` iff the registry says so — data-driven, never hardcoded to `ar_EG`. */
|
|
89
169
|
export declare function dirForLocale(id: LocaleId): 'ltr' | 'rtl';
|
|
90
170
|
export declare function isRtlLocale(id: LocaleId): boolean;
|
|
91
|
-
/** `ar_EG` → `ar-EG` for
|
|
171
|
+
/** `ar_EG` → `ar-EG` for i18next and the `lang` attribute. */
|
|
92
172
|
export declare function tagForLocale(id: LocaleId): string;
|
|
93
173
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
174
|
+
* The tag to hand `Intl.*` and `IntlMessageFormat` (23 §4.5). For a compiled
|
|
175
|
+
* locale this is just the tag; for a custom locale it is the admin-chosen
|
|
176
|
+
* borrow tag, because the locale's own tag may be something no ICU
|
|
177
|
+
* implementation has data for.
|
|
178
|
+
*/
|
|
179
|
+
export declare function intlTagForLocale(id: LocaleId): string;
|
|
180
|
+
/** Plural categories to validate a message against (frozen at create time). */
|
|
181
|
+
export declare function pluralCategoriesForLocale(id: LocaleId): readonly string[];
|
|
182
|
+
/**
|
|
183
|
+
* Best-effort inverse of {@link tagForLocale}: exact compiled tag, then a
|
|
184
|
+
* runtime tag, then a language-only compiled match (`de` → `de_DE`), else
|
|
185
|
+
* `en_US`. Maps `i18next.language` / `navigator.language` back to an id.
|
|
97
186
|
*/
|
|
98
187
|
export declare function localeFromTag(tag: string): LocaleId;
|
|
99
188
|
//# sourceMappingURL=locales.d.ts.map
|
package/dist/locales.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../src/locales.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../src/locales.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,WAAW,WAAW;IAC1B,mFAAmF;IACnF,EAAE,EAAE,MAAM,CAAC;IACX,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,KAAK,GAAG,KAAK,CAAC;IACnB;;;;;OAKG;IACH,QAAQ,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;CACtC;AAED,0EAA0E;AAC1E,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASuB,CAAC;AAE5C,kEAAkE;AAClE,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAE7D;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAAG,eAAe,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAEvD,eAAO,MAAM,kBAAkB,EAA0C,SAAS,eAAe,EAAE,CAAC;AAEpG,mFAAmF;AACnF,eAAO,MAAM,UAAU,4FAAqB,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,YAAY,QAAyC,CAAC;AAiBnE,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,GAAG,IAAI,CAE9E;AAED,uDAAuD;AACvD,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,oFAAoF;AACpF,wBAAgB,UAAU,IAAI,SAAS,kBAAkB,EAAE,CA2B1D;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,IAAI,SAAS,kBAAkB,EAAE,CAEhE;AAID,kDAAkD;AAClD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAE1E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAElD;AAOD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,GAAG,WAAW,CAErD;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,KAAK,GAAG,KAAK,CAExD;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAEjD;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,CAEjD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,CAErD;AAED,+EAA+E;AAC/E,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,QAAQ,GAAG,SAAS,MAAM,EAAE,CAQzE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CASnD"}
|
package/dist/locales.js
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Canonical locale registry (10-i18n-theming.md §2.1)
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* Canonical locale registry (10-i18n-theming.md §2.1) plus the runtime
|
|
3
|
+
* overlay that lets an admin add locales the build never saw
|
|
4
|
+
* (23-runtime-translations.md §5).
|
|
5
|
+
*
|
|
6
|
+
* Two id types, and the split is load-bearing:
|
|
7
|
+
*
|
|
8
|
+
* - {@link BuiltinLocaleId} is the closed union of the eight COMPILED
|
|
9
|
+
* locales. Everything that must stay exhaustive keys off it —
|
|
10
|
+
* `REVIEW_STATUS`, the parity gate, `gen-resources.mjs`, `lazy.ts` — so a
|
|
11
|
+
* ninth *compiled* locale is still a compile error, and no runtime locale
|
|
12
|
+
* can ever reach a build-time gate.
|
|
13
|
+
* - {@link LocaleId} additionally admits runtime ids. `(string & {})` keeps
|
|
14
|
+
* literal autocomplete for the eight while accepting anything a custom
|
|
15
|
+
* locale row can hold.
|
|
16
|
+
*
|
|
17
|
+
* Every lookup helper here is TOTAL: none of them throws on an unknown id.
|
|
18
|
+
* That is deliberate. Once locales are data, the orphan case — a deleted
|
|
19
|
+
* locale still referenced by a user pref, a restored backup, an imported
|
|
20
|
+
* config bundle — is the NORMAL aftermath of the feature, not an edge case,
|
|
21
|
+
* and these helpers run inside a theme subscriber whose emitter has no
|
|
22
|
+
* per-listener isolation. A throw there is a white screen, not a warning.
|
|
23
|
+
*
|
|
24
|
+
* `dir` is still derived from the locale, never independently settable
|
|
25
|
+
* (02-design-system.md §4.2).
|
|
8
26
|
*/
|
|
9
27
|
export const LOCALES = [
|
|
10
28
|
{ id: 'en_US', tag: 'en-US', english: 'English (US)', native: 'English (US)', dir: 'ltr', fontHint: 'latin' },
|
|
@@ -16,38 +34,145 @@ export const LOCALES = [
|
|
|
16
34
|
{ id: 'zh_TW', tag: 'zh-TW', english: 'Chinese (Traditional)', native: '繁體中文', dir: 'ltr', fontHint: 'cjk' },
|
|
17
35
|
{ id: 'ar_EG', tag: 'ar-EG', english: 'Arabic (Egypt)', native: 'العربية (مصر)', dir: 'rtl', fontHint: 'arabic' },
|
|
18
36
|
];
|
|
19
|
-
export const
|
|
37
|
+
export const BUILTIN_LOCALE_IDS = LOCALES.map((l) => l.id);
|
|
38
|
+
/** @deprecated Prefer {@link BUILTIN_LOCALE_IDS} (or the runtime list) by name. */
|
|
39
|
+
export const LOCALE_IDS = BUILTIN_LOCALE_IDS;
|
|
40
|
+
/**
|
|
41
|
+
* Canonical id shape: a language subtag plus up to two more (script, region),
|
|
42
|
+
* e.g. `en_US`, `sw_KE`, `zh_Hant_TW`. Three subtags is why the DB column is
|
|
43
|
+
* `varchar(35)` and why {@link tagFromLocaleId} replaces EVERY underscore.
|
|
44
|
+
*/
|
|
45
|
+
export const LOCALE_ID_RE = /^[a-z]{2,3}(_[A-Za-z0-9]{2,8}){0,2}$/;
|
|
20
46
|
const BY_ID = new Map(LOCALES.map((l) => [l.id, l]));
|
|
21
47
|
const BY_TAG = new Map(LOCALES.map((l) => [l.tag, l]));
|
|
22
|
-
|
|
48
|
+
// --- runtime overlay ---------------------------------------------------------
|
|
49
|
+
/**
|
|
50
|
+
* Locales the running instance knows about beyond the compiled eight. Set
|
|
51
|
+
* once from the server manifest at boot and on every i18n version bump; empty
|
|
52
|
+
* on a server/CLI that never loads it, which is exactly the pre-23 behaviour.
|
|
53
|
+
*
|
|
54
|
+
* A module-level singleton is correct here: one Adminium instance has one
|
|
55
|
+
* meta store and therefore one locale registry, same as the compiled one.
|
|
56
|
+
*/
|
|
57
|
+
let runtimeById = new Map();
|
|
58
|
+
/** Replace the runtime registry wholesale (boot, and every version bump). */
|
|
59
|
+
export function setRuntimeLocales(entries) {
|
|
60
|
+
runtimeById = new Map(entries.map((e) => [e.id, e]));
|
|
61
|
+
}
|
|
62
|
+
/** Drop the runtime registry — tests, and teardown. */
|
|
63
|
+
export function resetRuntimeLocales() {
|
|
64
|
+
runtimeById = new Map();
|
|
65
|
+
}
|
|
66
|
+
/** Every locale the instance knows: compiled defaults merged under runtime rows. */
|
|
67
|
+
export function allLocales() {
|
|
68
|
+
const merged = new Map();
|
|
69
|
+
for (const entry of LOCALES) {
|
|
70
|
+
merged.set(entry.id, {
|
|
71
|
+
...entry,
|
|
72
|
+
builtin: true,
|
|
73
|
+
enabled: true,
|
|
74
|
+
sortOrder: 0,
|
|
75
|
+
intlTag: entry.tag,
|
|
76
|
+
pluralCategories: [],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
for (const [id, entry] of runtimeById) {
|
|
80
|
+
const base = merged.get(id);
|
|
81
|
+
// A built-in row may only carry `enabled`/`sortOrder` (23 §3.1 field
|
|
82
|
+
// lock) — presentation always comes from the compiled entry, so an admin
|
|
83
|
+
// cannot flip ar_EG to ltr and corrupt a shipped bundle's rendering.
|
|
84
|
+
merged.set(id, base === undefined
|
|
85
|
+
? entry
|
|
86
|
+
: { ...base, enabled: entry.enabled, sortOrder: entry.sortOrder, builtin: true });
|
|
87
|
+
}
|
|
88
|
+
return [...merged.values()].sort((a, b) => a.sortOrder - b.sortOrder || a.id.localeCompare(b.id));
|
|
89
|
+
}
|
|
90
|
+
/** The locales a picker may offer, in picker order (23 §3.1 `enabled`). */
|
|
91
|
+
export function availableLocales() {
|
|
92
|
+
return allLocales().filter((l) => l.enabled);
|
|
93
|
+
}
|
|
94
|
+
// --- lookups (all total) -----------------------------------------------------
|
|
95
|
+
/** True for one of the eight COMPILED locales. */
|
|
96
|
+
export function isBuiltinLocaleId(value) {
|
|
23
97
|
return typeof value === 'string' && BY_ID.has(value);
|
|
24
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* True for anything shaped like a locale id. This is a SHAPE check, not a
|
|
101
|
+
* membership check: a cached preference may name a locale this build does not
|
|
102
|
+
* compile in, and rejecting it would strand the user on en-US. Use
|
|
103
|
+
* {@link isBuiltinLocaleId} when compiled membership is what you mean.
|
|
104
|
+
*/
|
|
105
|
+
export function isLocaleId(value) {
|
|
106
|
+
return typeof value === 'string' && LOCALE_ID_RE.test(value);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* `en_US` → `en-US`, `zh_Hant_TW` → `zh-Hant-TW`.
|
|
110
|
+
*
|
|
111
|
+
* Replaces EVERY underscore. A single-replacement version leaves
|
|
112
|
+
* `zh-Hant_TW`, which is not a valid BCP-47 tag, and the formatter layer
|
|
113
|
+
* coalesces invalid tags to `en-US` — silently degrading exactly the way the
|
|
114
|
+
* admin-chosen `intlTag` exists to prevent.
|
|
115
|
+
*/
|
|
116
|
+
export function tagFromLocaleId(id) {
|
|
117
|
+
return id.replaceAll('_', '-');
|
|
118
|
+
}
|
|
119
|
+
/** Synthesized entry for an id no registry knows — never throws. */
|
|
120
|
+
function synthesize(id) {
|
|
121
|
+
return { id, tag: tagFromLocaleId(id), english: id, native: id, dir: 'ltr', fontHint: 'latin' };
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The presentation record for a locale: compiled entry, else runtime entry,
|
|
125
|
+
* else a synthesized one. Total by contract — see the file header for why
|
|
126
|
+
* throwing here is a white screen.
|
|
127
|
+
*/
|
|
25
128
|
export function localeEntry(id) {
|
|
26
|
-
|
|
27
|
-
if (entry === undefined)
|
|
28
|
-
throw new Error(`unknown locale id: ${JSON.stringify(id)}`);
|
|
29
|
-
return entry;
|
|
129
|
+
return BY_ID.get(id) ?? runtimeById.get(id) ?? synthesize(id);
|
|
30
130
|
}
|
|
31
|
-
/** `'rtl'` iff the registry says so
|
|
131
|
+
/** `'rtl'` iff the registry says so — data-driven, never hardcoded to `ar_EG`. */
|
|
32
132
|
export function dirForLocale(id) {
|
|
33
133
|
return localeEntry(id).dir;
|
|
34
134
|
}
|
|
35
135
|
export function isRtlLocale(id) {
|
|
36
136
|
return dirForLocale(id) === 'rtl';
|
|
37
137
|
}
|
|
38
|
-
/** `ar_EG` → `ar-EG` for
|
|
138
|
+
/** `ar_EG` → `ar-EG` for i18next and the `lang` attribute. */
|
|
39
139
|
export function tagForLocale(id) {
|
|
40
140
|
return localeEntry(id).tag;
|
|
41
141
|
}
|
|
42
142
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
143
|
+
* The tag to hand `Intl.*` and `IntlMessageFormat` (23 §4.5). For a compiled
|
|
144
|
+
* locale this is just the tag; for a custom locale it is the admin-chosen
|
|
145
|
+
* borrow tag, because the locale's own tag may be something no ICU
|
|
146
|
+
* implementation has data for.
|
|
147
|
+
*/
|
|
148
|
+
export function intlTagForLocale(id) {
|
|
149
|
+
return runtimeById.get(id)?.intlTag ?? tagForLocale(id);
|
|
150
|
+
}
|
|
151
|
+
/** Plural categories to validate a message against (frozen at create time). */
|
|
152
|
+
export function pluralCategoriesForLocale(id) {
|
|
153
|
+
const frozen = runtimeById.get(id)?.pluralCategories;
|
|
154
|
+
if (frozen !== undefined && frozen.length > 0)
|
|
155
|
+
return frozen;
|
|
156
|
+
try {
|
|
157
|
+
return new Intl.PluralRules(intlTagForLocale(id)).resolvedOptions().pluralCategories;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return ['other'];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Best-effort inverse of {@link tagForLocale}: exact compiled tag, then a
|
|
165
|
+
* runtime tag, then a language-only compiled match (`de` → `de_DE`), else
|
|
166
|
+
* `en_US`. Maps `i18next.language` / `navigator.language` back to an id.
|
|
46
167
|
*/
|
|
47
168
|
export function localeFromTag(tag) {
|
|
48
169
|
const exact = BY_TAG.get(tag);
|
|
49
170
|
if (exact !== undefined)
|
|
50
171
|
return exact.id;
|
|
172
|
+
for (const entry of runtimeById.values()) {
|
|
173
|
+
if (entry.tag === tag)
|
|
174
|
+
return entry.id;
|
|
175
|
+
}
|
|
51
176
|
const lang = tag.toLowerCase().split('-')[0] ?? '';
|
|
52
177
|
const byLang = LOCALES.find((l) => l.tag.toLowerCase().startsWith(`${lang}-`) || l.tag.toLowerCase() === lang);
|
|
53
178
|
return byLang?.id ?? 'en_US';
|
package/dist/locales.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.js","sourceRoot":"","sources":["../src/locales.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"locales.js","sourceRoot":"","sources":["../src/locales.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime override layer (23-runtime-translations.md §4.3).
|
|
3
|
+
*
|
|
4
|
+
* The compiled bundle and the DB override tree are held SEPARATELY and merged
|
|
5
|
+
* in userland; the i18next resource store is written once, at `init`, and
|
|
6
|
+
* never mutated afterwards. Two facts about i18next 25 force that shape, and
|
|
7
|
+
* both were verified against its source rather than assumed:
|
|
8
|
+
*
|
|
9
|
+
* 1. `addResourceBundle` cannot DELETE a key — its `deepExtend` only ever
|
|
10
|
+
* writes keys present in the incoming object. So the store cannot express
|
|
11
|
+
* "reset this key to the built-in", which is the single most common admin
|
|
12
|
+
* operation. And `removeResourceBundle` is not an escape: it calls
|
|
13
|
+
* `removeNamespaces`, which splices the namespace out of `options.ns` for
|
|
14
|
+
* the WHOLE instance, breaking en-US resolution and every future backend
|
|
15
|
+
* load for that namespace.
|
|
16
|
+
* 2. The backend loader fires at most once per (language, namespace) per
|
|
17
|
+
* instance — `queueLoad` short-circuits on `hasResourceBundle` — so
|
|
18
|
+
* merging inside `loadBundle` is a first-load mechanism, not a
|
|
19
|
+
* steady-state one.
|
|
20
|
+
*
|
|
21
|
+
* Therefore: merge at load, and REBUILD the instance on every version bump,
|
|
22
|
+
* swapping it in through the existing provider seam. That is the only
|
|
23
|
+
* operation this i18next version performs losslessly.
|
|
24
|
+
*/
|
|
25
|
+
import { type CreateI18nOptions, type I18nInstance } from './create-i18n.js';
|
|
26
|
+
import { type LocaleId } from './locales.js';
|
|
27
|
+
import type { Namespace, ResourceBundle } from './resources/index.js';
|
|
28
|
+
/** `tag → namespace → flat dotted key → message`. Sparse: only overrides. */
|
|
29
|
+
export type OverrideMap = Readonly<Record<string, Readonly<Partial<Record<Namespace, Readonly<Record<string, string>>>>>>>;
|
|
30
|
+
/**
|
|
31
|
+
* Merge flat dotted overrides into a (cloned) nested bundle.
|
|
32
|
+
*
|
|
33
|
+
* An empty-string value is written through deliberately — it is the third
|
|
34
|
+
* state, "render nothing" (23 §3.3), and is why `createI18n` sets
|
|
35
|
+
* `returnEmptyString: true`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function mergeOverrides(base: ResourceBundle | null, flat: Readonly<Record<string, string>>): ResourceBundle;
|
|
38
|
+
export interface CreateI18nWithOverridesOptions extends CreateI18nOptions {
|
|
39
|
+
/** DB-sourced overrides, keyed by BCP-47 tag. */
|
|
40
|
+
overrides?: OverrideMap | undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `createI18n` with the override layer folded in.
|
|
44
|
+
*
|
|
45
|
+
* en-US overrides are applied through the SAME merge as every other locale,
|
|
46
|
+
* never through `createI18n`'s `resources` option: that option does
|
|
47
|
+
* `target[ns] = bundle`, a whole-namespace REPLACEMENT, so routing a sparse
|
|
48
|
+
* override tree through it would delete the ~1,500 other `common` keys.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createI18nWithOverrides(opts: CreateI18nWithOverridesOptions): Promise<I18nInstance>;
|
|
51
|
+
/**
|
|
52
|
+
* Rebuild the instance with a new override set and hand it back.
|
|
53
|
+
*
|
|
54
|
+
* The caller swaps the result into whatever holds the active instance and
|
|
55
|
+
* calls `bumpI18nRevision()` — which this does for you — so subscribers
|
|
56
|
+
* re-render. Rebuilding rather than patching is the whole point: see the
|
|
57
|
+
* module header.
|
|
58
|
+
*/
|
|
59
|
+
export declare function rebuildWithOverrides(opts: CreateI18nWithOverridesOptions): Promise<I18nInstance>;
|
|
60
|
+
/** The BCP-47 key an override map uses for a locale id. */
|
|
61
|
+
export declare function overrideTag(locale: LocaleId): string;
|
|
62
|
+
//# sourceMappingURL=overrides.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overrides.d.ts","sourceRoot":"","sources":["../src/overrides.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAc,KAAK,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtE,6EAA6E;AAC7E,MAAM,MAAM,WAAW,GAAG,QAAQ,CAChC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACvF,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,cAAc,GAAG,IAAI,EAC3B,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACrC,cAAc,CAiBhB;AAED,MAAM,WAAW,8BAA+B,SAAQ,iBAAiB;IACvE,iDAAiD;IACjD,SAAS,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CACrC;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC,YAAY,CAAC,CAkCvB;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC,YAAY,CAAC,CAIvB;AAED,2DAA2D;AAC3D,wBAAgB,WAAW,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAEpD"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime override layer (23-runtime-translations.md §4.3).
|
|
3
|
+
*
|
|
4
|
+
* The compiled bundle and the DB override tree are held SEPARATELY and merged
|
|
5
|
+
* in userland; the i18next resource store is written once, at `init`, and
|
|
6
|
+
* never mutated afterwards. Two facts about i18next 25 force that shape, and
|
|
7
|
+
* both were verified against its source rather than assumed:
|
|
8
|
+
*
|
|
9
|
+
* 1. `addResourceBundle` cannot DELETE a key — its `deepExtend` only ever
|
|
10
|
+
* writes keys present in the incoming object. So the store cannot express
|
|
11
|
+
* "reset this key to the built-in", which is the single most common admin
|
|
12
|
+
* operation. And `removeResourceBundle` is not an escape: it calls
|
|
13
|
+
* `removeNamespaces`, which splices the namespace out of `options.ns` for
|
|
14
|
+
* the WHOLE instance, breaking en-US resolution and every future backend
|
|
15
|
+
* load for that namespace.
|
|
16
|
+
* 2. The backend loader fires at most once per (language, namespace) per
|
|
17
|
+
* instance — `queueLoad` short-circuits on `hasResourceBundle` — so
|
|
18
|
+
* merging inside `loadBundle` is a first-load mechanism, not a
|
|
19
|
+
* steady-state one.
|
|
20
|
+
*
|
|
21
|
+
* Therefore: merge at load, and REBUILD the instance on every version bump,
|
|
22
|
+
* swapping it in through the existing provider seam. That is the only
|
|
23
|
+
* operation this i18next version performs losslessly.
|
|
24
|
+
*/
|
|
25
|
+
import { createI18n } from './create-i18n.js';
|
|
26
|
+
import { tagForLocale } from './locales.js';
|
|
27
|
+
import { bumpI18nRevision } from './revision.js';
|
|
28
|
+
/**
|
|
29
|
+
* Merge flat dotted overrides into a (cloned) nested bundle.
|
|
30
|
+
*
|
|
31
|
+
* An empty-string value is written through deliberately — it is the third
|
|
32
|
+
* state, "render nothing" (23 §3.3), and is why `createI18n` sets
|
|
33
|
+
* `returnEmptyString: true`.
|
|
34
|
+
*/
|
|
35
|
+
export function mergeOverrides(base, flat) {
|
|
36
|
+
const root = base === null ? {} : structuredClone(base);
|
|
37
|
+
for (const [path, value] of Object.entries(flat)) {
|
|
38
|
+
const parts = path.split('.');
|
|
39
|
+
let node = root;
|
|
40
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
41
|
+
const part = parts[i];
|
|
42
|
+
const next = node[part];
|
|
43
|
+
// A key may override a leaf that used to be a subtree (or vice versa);
|
|
44
|
+
// replace rather than crash.
|
|
45
|
+
if (typeof next !== 'object' || next === null)
|
|
46
|
+
node[part] = {};
|
|
47
|
+
node = node[part];
|
|
48
|
+
}
|
|
49
|
+
node[parts[parts.length - 1]] = value;
|
|
50
|
+
}
|
|
51
|
+
return root;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* `createI18n` with the override layer folded in.
|
|
55
|
+
*
|
|
56
|
+
* en-US overrides are applied through the SAME merge as every other locale,
|
|
57
|
+
* never through `createI18n`'s `resources` option: that option does
|
|
58
|
+
* `target[ns] = bundle`, a whole-namespace REPLACEMENT, so routing a sparse
|
|
59
|
+
* override tree through it would delete the ~1,500 other `common` keys.
|
|
60
|
+
*/
|
|
61
|
+
export async function createI18nWithOverrides(opts) {
|
|
62
|
+
const { overrides, loadBundle, resources, ...rest } = opts;
|
|
63
|
+
if (overrides === undefined || Object.keys(overrides).length === 0) {
|
|
64
|
+
return createI18n(opts);
|
|
65
|
+
}
|
|
66
|
+
const enUsOverrides = overrides['en-US'];
|
|
67
|
+
const mergedEnUs = {};
|
|
68
|
+
if (enUsOverrides !== undefined) {
|
|
69
|
+
const { EN_US_RESOURCES } = await import('./resources/index.js');
|
|
70
|
+
for (const [ns, flat] of Object.entries(enUsOverrides)) {
|
|
71
|
+
if (flat === undefined)
|
|
72
|
+
continue;
|
|
73
|
+
mergedEnUs[ns] = mergeOverrides(EN_US_RESOURCES[ns], flat);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return createI18n({
|
|
77
|
+
...rest,
|
|
78
|
+
resources: {
|
|
79
|
+
...resources,
|
|
80
|
+
// Full merged namespaces, so the whole-namespace replacement is safe.
|
|
81
|
+
...(Object.keys(mergedEnUs).length > 0 ? { 'en-US': mergedEnUs } : {}),
|
|
82
|
+
},
|
|
83
|
+
loadBundle: async (tag, ns) => {
|
|
84
|
+
const base = loadBundle === undefined ? null : await loadBundle(tag, ns);
|
|
85
|
+
const flat = overrides[tag]?.[ns];
|
|
86
|
+
const baseBundle = base === null ? null : 'default' in base ? base.default : base;
|
|
87
|
+
if (flat === undefined)
|
|
88
|
+
return baseBundle;
|
|
89
|
+
// Merged HERE rather than after the fact, so a slow compiled chunk
|
|
90
|
+
// landing later can never clobber an override that was already applied.
|
|
91
|
+
return { default: mergeOverrides(baseBundle, flat) };
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Rebuild the instance with a new override set and hand it back.
|
|
97
|
+
*
|
|
98
|
+
* The caller swaps the result into whatever holds the active instance and
|
|
99
|
+
* calls `bumpI18nRevision()` — which this does for you — so subscribers
|
|
100
|
+
* re-render. Rebuilding rather than patching is the whole point: see the
|
|
101
|
+
* module header.
|
|
102
|
+
*/
|
|
103
|
+
export async function rebuildWithOverrides(opts) {
|
|
104
|
+
const instance = await createI18nWithOverrides(opts);
|
|
105
|
+
bumpI18nRevision();
|
|
106
|
+
return instance;
|
|
107
|
+
}
|
|
108
|
+
/** The BCP-47 key an override map uses for a locale id. */
|
|
109
|
+
export function overrideTag(locale) {
|
|
110
|
+
return tagForLocale(locale);
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=overrides.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overrides.js","sourceRoot":"","sources":["../src/overrides.ts"],"names":[],"mappings":"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"}
|
package/dist/react.d.ts
CHANGED
|
@@ -5,6 +5,12 @@ import { type LocaleId } from './locales.js';
|
|
|
5
5
|
export interface I18nContextValue {
|
|
6
6
|
i18n: I18nInstance;
|
|
7
7
|
locale: LocaleId;
|
|
8
|
+
/**
|
|
9
|
+
* Bumps whenever resolved text changes without the language changing —
|
|
10
|
+
* i.e. when runtime overrides are applied (23 §4.4). Hooks depend on it so
|
|
11
|
+
* memoized consumers re-render on an admin's edit, not only on a switch.
|
|
12
|
+
*/
|
|
13
|
+
revision: number;
|
|
8
14
|
}
|
|
9
15
|
export interface I18nProviderProps {
|
|
10
16
|
i18n: I18nInstance;
|
package/dist/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAA4C,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGvF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EAAE,QAAQ,CAAC;IACjB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,iBAAiB,GAAG,SAAS,CAqB7E;AAQD,kEAAkE;AAClE,wBAAgB,OAAO,IAAI,YAAY,CAEtC;AAED,gEAAgE;AAChE,wBAAgB,SAAS,IAAI,QAAQ,CAEpC;AAED,gEAAgE;AAChE,wBAAgB,MAAM,IAAI,OAAO,CAEhC;AAED,4EAA4E;AAC5E,wBAAgB,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAUhG;AAED,gFAAgF;AAChF,wBAAgB,YAAY,IAAI,gBAAgB,GAAG,IAAI,CAEtD;AAKD;;;;;;GAMG;AACH,wBAAgB,SAAS,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAarG;AAED,yEAAyE;AACzE,wBAAgB,MAAM,IAAI,UAAU,CAGnC;AAED,0CAA0C;AAC1C,eAAO,MAAM,aAAa,eAAS,CAAC"}
|