@object-ui/i18n 17.6.0 → 17.7.0

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +4320 -0
  2. package/README.md +192 -13
  3. package/dist/builtinAggregateLabels.d.ts +40 -0
  4. package/dist/builtinAggregateLabels.js +23 -0
  5. package/dist/currency.d.ts +27 -3
  6. package/dist/currency.js +28 -4
  7. package/dist/fallbackInterpolation.d.ts +79 -0
  8. package/dist/fallbackInterpolation.js +124 -0
  9. package/dist/i18n.d.ts +27 -1
  10. package/dist/i18n.js +99 -16
  11. package/dist/index.d.ts +7 -13
  12. package/dist/index.js +41 -17
  13. package/dist/locales/ar.d.ts +946 -113
  14. package/dist/locales/ar.js +1095 -161
  15. package/dist/locales/de.d.ts +618 -113
  16. package/dist/locales/de.js +715 -134
  17. package/dist/locales/en.d.ts +632 -127
  18. package/dist/locales/en.js +1044 -162
  19. package/dist/locales/es.d.ts +700 -113
  20. package/dist/locales/es.js +794 -130
  21. package/dist/locales/fr.d.ts +700 -113
  22. package/dist/locales/fr.js +795 -132
  23. package/dist/locales/index.d.ts +6990 -1169
  24. package/dist/locales/index.js +48 -9
  25. package/dist/locales/ja.d.ts +618 -113
  26. package/dist/locales/ja.js +708 -128
  27. package/dist/locales/ko.d.ts +618 -113
  28. package/dist/locales/ko.js +712 -132
  29. package/dist/locales/pt.d.ts +700 -113
  30. package/dist/locales/pt.js +794 -130
  31. package/dist/locales/registry.d.ts +102 -0
  32. package/dist/locales/registry.js +220 -0
  33. package/dist/locales/rtl.d.ts +24 -0
  34. package/dist/locales/rtl.js +26 -0
  35. package/dist/locales/ru.d.ts +782 -113
  36. package/dist/locales/ru.js +910 -152
  37. package/dist/locales/zh.d.ts +635 -130
  38. package/dist/locales/zh.js +725 -131
  39. package/dist/pickLocalized.d.ts +30 -0
  40. package/dist/pickLocalized.js +39 -0
  41. package/dist/provider.d.ts +101 -12
  42. package/dist/provider.js +335 -50
  43. package/dist/translateFn.d.ts +29 -0
  44. package/dist/translateFn.js +8 -0
  45. package/dist/useDisplayLocale.d.ts +11 -0
  46. package/dist/useDisplayLocale.js +11 -0
  47. package/dist/useObjectLabel.d.ts +40 -18
  48. package/dist/useObjectLabel.js +46 -14
  49. package/dist/useSafeTranslation.js +40 -42
  50. package/dist/utils/formatting.d.ts +16 -0
  51. package/dist/utils/formatting.js +23 -3
  52. package/dist/utils/spec-formatters.d.ts +13 -23
  53. package/dist/utils/spec-formatters.js +41 -2
  54. package/dist/utils/spec-translations.d.ts +16 -3
  55. package/dist/utils/spec-translations.js +52 -13
  56. package/package.json +12 -6
@@ -0,0 +1,102 @@
1
+ import type { TranslationKeys } from './en.js';
2
+ /**
3
+ * A built-in catalogue as the runtime handles one.
4
+ *
5
+ * Deliberately NOT `TranslationKeys`: `en.ts` ends in `as const`, so that type
6
+ * carries `en`'s literal VALUES and no other pack's strings are assignable to
7
+ * it. Consumers that want the shape read {@link TranslationKeys} off `en`
8
+ * directly and cast, exactly as `builtInLocales` consumers already did.
9
+ */
10
+ export type LocaleCatalogue = Readonly<Record<string, unknown>>;
11
+ /**
12
+ * The catalogue every instance can rely on being present, synchronously.
13
+ * See the header for the three mechanisms that require it.
14
+ */
15
+ export declare const DEFAULT_BUILT_IN_LANGUAGE = "en";
16
+ /**
17
+ * Every language code this package ships a catalogue for — the ten, in the
18
+ * order `builtInLocales` declared them, and with NO catalogue bytes behind the
19
+ * list. This is what a language switcher enumerates.
20
+ */
21
+ export declare const BUILT_IN_LANGUAGE_CODES: readonly string[];
22
+ /**
23
+ * Exact-then-base language-tag matching: `tag` itself when `has(tag)`, else
24
+ * its base language (`zh-CN` → `zh`) when `has(base)`, else `null`.
25
+ *
26
+ * The ONE implementation of this order in the package (objectui#11326). The
27
+ * registry asks it which catalogue serves a code, and `I18nProvider` asks it
28
+ * which language a cached seed boots in — each with its own `has`, so the two
29
+ * can never disagree about the ORDER while each keeps its own notion of what
30
+ * exists.
31
+ *
32
+ * Exact first, so a catalogue keyed by a full tag (a `pt-BR` beside `pt`, a
33
+ * `zh-TW` beside `zh`) is never shadowed by its base language.
34
+ */
35
+ export declare function matchLanguageTag(tag: string, has: (code: string) => boolean): string | null;
36
+ /**
37
+ * The code of the built-in catalogue that serves `lang`, or `null` when this
38
+ * package ships none for it.
39
+ *
40
+ * A region-tagged code is served by its base language's catalogue: the
41
+ * platform answers `zh-CN`, a language menu offers `zh-CN`, and the user's
42
+ * stored choice is `zh-CN`, while the catalogue is keyed `zh`. An exact match
43
+ * still wins — see {@link matchLanguageTag}.
44
+ */
45
+ export declare function resolveBuiltInLanguage(lang: string): string | null;
46
+ /**
47
+ * Whether `@object-ui/i18n` ships a catalogue that serves `lang` — exactly, or
48
+ * through its base language (`zh-CN` → `zh`; see {@link resolveBuiltInLanguage}).
49
+ */
50
+ export declare function isBuiltInLanguage(lang: string): boolean;
51
+ /**
52
+ * A synchronous snapshot of the catalogues this process has resolved.
53
+ *
54
+ * ⚠️ A SNAPSHOT, not a live view: callers that render from it must re-read it
55
+ * after `loadBuiltInLocale` resolves, or they will keep rendering the answer
56
+ * they took before the fetch landed.
57
+ */
58
+ export declare function getLoadedBuiltInLocales(): Readonly<Record<string, LocaleCatalogue>>;
59
+ /**
60
+ * Make a catalogue resident WITHOUT fetching it — for a caller that already
61
+ * holds one.
62
+ *
63
+ * The only caller in this repository is `locales/index.ts`'s
64
+ * {@link registerBuiltInLocales}: that module statically imports all ten, so
65
+ * asking {@link loadBuiltInLocale} to `import()` them again would be an await
66
+ * for modules the evaluator already has. It is also what lets a test that needs
67
+ * a non-`en` locale resident say so in ONE SYNCHRONOUS LINE at module scope —
68
+ * the shape AGENTS.md prescribes for a cost that belongs in the import phase,
69
+ * rather than a `beforeAll` bounded by `hookTimeout`.
70
+ *
71
+ * ⛔ Not exported from the package entry, deliberately. An app that wants a
72
+ * catalogue resident asks for it by CODE through `loadBuiltInLocale`; handing
73
+ * the entry a way to inject arbitrary objects into the registry would make
74
+ * "which catalogue is this" unanswerable from the codes alone.
75
+ */
76
+ export declare function registerBuiltInLocale(lang: string, catalogue: LocaleCatalogue): void;
77
+ /**
78
+ * Whether the catalogue that serves `lang` is already resident (no fetch
79
+ * needed). Reads the same exact-then-base resolution as
80
+ * {@link loadBuiltInLocale}, so the two agree for a region-tagged code: when
81
+ * `zh` is resident, `zh-CN` needs no fetch either.
82
+ */
83
+ export declare function isBuiltInLocaleLoaded(lang: string): boolean;
84
+ /**
85
+ * Fetch the built-in catalogue that serves `lang`, or resolve `null` for a code
86
+ * this package has no catalogue for (an app-supplied locale, a typo).
87
+ *
88
+ * A region-tagged code resolves its base language's catalogue (`zh-CN` → the
89
+ * `zh` catalogue) unless a catalogue is keyed by the full tag — see
90
+ * {@link resolveBuiltInLanguage}. The catalogue is fetched and memoised under
91
+ * ITS OWN code, so `zh-CN` and `zh` share one fetch and
92
+ * {@link getLoadedBuiltInLocales} stays keyed by catalogue code, which is how
93
+ * `createI18n` and the app-shell splash read it.
94
+ *
95
+ * Memoised in both directions: a resolved catalogue is returned from the
96
+ * registry without a second import, and concurrent callers share one in-flight
97
+ * promise. ⛔ A REJECTED load is not memoised — a chunk that failed to download
98
+ * (offline, a cache miss against a redeployed origin) must be retryable, and a
99
+ * language preference is never worth wedging permanently over one bad fetch.
100
+ */
101
+ export declare function loadBuiltInLocale(lang: string): Promise<LocaleCatalogue | null>;
102
+ export type { TranslationKeys };
@@ -0,0 +1,220 @@
1
+ /**
2
+ * @object-ui/i18n — the built-in catalogue registry (objectui#7479).
3
+ *
4
+ * ## What this module is for
5
+ *
6
+ * `locales/index.ts` names all ten packs in ten static `import` statements, so
7
+ * every module that reached ANY of them through the package entry reached all
8
+ * ten. The console measured that: one eagerly loaded chunk carrying ten
9
+ * catalogues of which a viewer reads one, and every new translation key paid
10
+ * for by every visitor in every language (objectui#7399's measurement, and the
11
+ * per-chunk ceiling it ran out of).
12
+ *
13
+ * This module is the entry's replacement door. It holds:
14
+ *
15
+ * - the ten language CODES, which cost nothing;
16
+ * - one static `import` — `en`, and only `en` (see below);
17
+ * - nine `() => import(...)` loaders, one per catalogue, each its own chunk;
18
+ * - a synchronous registry of what has actually been loaded so far.
19
+ *
20
+ * ## Why `en` is static and the other nine are not
21
+ *
22
+ * ⛔ This is not a shortcut around the ask; it is what makes the ask safe, and
23
+ * the card admits it in as many words ("no `packages/i18n/src/locales/*` module
24
+ * in any eager chunk EXCEPT THE ACTIVE ONE", and "roughly NINE catalogues'
25
+ * worth" of bytes). Three separate mechanisms need an `en` catalogue to be
26
+ * resident and none of them can await one:
27
+ *
28
+ * 1. `createI18n` declares `fallbackLng: 'en'`. Any key a pack does not
29
+ * define — the four `console.ai.*` outbound messages are exempted from
30
+ * parity by `all-locales-key-parity.test.ts`, and a plural category no
31
+ * pack enumerates is resolved the same way — renders through `en`. With no
32
+ * `en` bundle those render as the RAW KEY, in the UI, in nine languages.
33
+ * 2. `@object-ui/app-shell`'s `LoadingScreen` renders BEFORE i18n is usable
34
+ * and on the server-down path where nothing else loads. It needs a
35
+ * synchronous dictionary, and leaving it with English literals instead is
36
+ * the "delete or untranslate the copy" route objectui#7399 rejected.
37
+ * 3. A caller of the synchronous, published `createI18n()` that never awaits
38
+ * anything must still get a usable instance.
39
+ *
40
+ * So `en` is the documented resident fallback and the other nine are fetched on
41
+ * demand. What a `zh` viewer pays is `en` + `zh`, not ten.
42
+ *
43
+ * ## The loaders are spelled out, one per line, on purpose
44
+ *
45
+ * ⛔ Never collapse them into `import(`./${lang}.js`)`. A template literal makes
46
+ * the specifier dynamic, and every bundler answers that by emitting EVERY file
47
+ * the pattern could match — which is the ten-catalogue closure again, now
48
+ * spelled in a way that reads lazy. Ten literal specifiers are ten chunks the
49
+ * bundler can name, and `apps/console/vite.config.ts` names them
50
+ * (`i18n-locale-<code>`) so `scripts/check-eager-locale-catalogues.mjs` can
51
+ * weigh them in the BUILT bundle rather than in this source file.
52
+ */
53
+ import en from './en.js';
54
+ /**
55
+ * The catalogue every instance can rely on being present, synchronously.
56
+ * See the header for the three mechanisms that require it.
57
+ */
58
+ export const DEFAULT_BUILT_IN_LANGUAGE = 'en';
59
+ /**
60
+ * The nine catalogues fetched on demand — one literal specifier each, so each
61
+ * one is a chunk of its own. ⛔ Read the header before touching the spelling.
62
+ */
63
+ const LAZY_LOCALE_LOADERS = Object.freeze({
64
+ zh: () => import('./zh.js'),
65
+ ja: () => import('./ja.js'),
66
+ ko: () => import('./ko.js'),
67
+ de: () => import('./de.js'),
68
+ fr: () => import('./fr.js'),
69
+ es: () => import('./es.js'),
70
+ pt: () => import('./pt.js'),
71
+ ru: () => import('./ru.js'),
72
+ ar: () => import('./ar.js'),
73
+ });
74
+ /**
75
+ * Every language code this package ships a catalogue for — the ten, in the
76
+ * order `builtInLocales` declared them, and with NO catalogue bytes behind the
77
+ * list. This is what a language switcher enumerates.
78
+ */
79
+ export const BUILT_IN_LANGUAGE_CODES = Object.freeze([
80
+ DEFAULT_BUILT_IN_LANGUAGE,
81
+ ...Object.keys(LAZY_LOCALE_LOADERS),
82
+ ]);
83
+ /**
84
+ * Exact-then-base language-tag matching: `tag` itself when `has(tag)`, else
85
+ * its base language (`zh-CN` → `zh`) when `has(base)`, else `null`.
86
+ *
87
+ * The ONE implementation of this order in the package (objectui#11326). The
88
+ * registry asks it which catalogue serves a code, and `I18nProvider` asks it
89
+ * which language a cached seed boots in — each with its own `has`, so the two
90
+ * can never disagree about the ORDER while each keeps its own notion of what
91
+ * exists.
92
+ *
93
+ * Exact first, so a catalogue keyed by a full tag (a `pt-BR` beside `pt`, a
94
+ * `zh-TW` beside `zh`) is never shadowed by its base language.
95
+ */
96
+ export function matchLanguageTag(tag, has) {
97
+ if (has(tag))
98
+ return tag;
99
+ const base = tag.split('-')[0];
100
+ if (base && base !== tag && has(base))
101
+ return base;
102
+ return null;
103
+ }
104
+ /** Whether a built-in catalogue is keyed by exactly `code`. */
105
+ function hasBuiltInCatalogue(code) {
106
+ return (code === DEFAULT_BUILT_IN_LANGUAGE ||
107
+ Object.prototype.hasOwnProperty.call(LAZY_LOCALE_LOADERS, code));
108
+ }
109
+ /**
110
+ * The code of the built-in catalogue that serves `lang`, or `null` when this
111
+ * package ships none for it.
112
+ *
113
+ * A region-tagged code is served by its base language's catalogue: the
114
+ * platform answers `zh-CN`, a language menu offers `zh-CN`, and the user's
115
+ * stored choice is `zh-CN`, while the catalogue is keyed `zh`. An exact match
116
+ * still wins — see {@link matchLanguageTag}.
117
+ */
118
+ export function resolveBuiltInLanguage(lang) {
119
+ return matchLanguageTag(lang, hasBuiltInCatalogue);
120
+ }
121
+ /**
122
+ * Whether `@object-ui/i18n` ships a catalogue that serves `lang` — exactly, or
123
+ * through its base language (`zh-CN` → `zh`; see {@link resolveBuiltInLanguage}).
124
+ */
125
+ export function isBuiltInLanguage(lang) {
126
+ return resolveBuiltInLanguage(lang) !== null;
127
+ }
128
+ /**
129
+ * The catalogues loaded so far. `en` is in here from module evaluation; each of
130
+ * the other nine appears once {@link loadBuiltInLocale} has resolved for it.
131
+ */
132
+ const loaded = {
133
+ [DEFAULT_BUILT_IN_LANGUAGE]: en,
134
+ };
135
+ /** In-flight loads, so N callers for one language share one fetch. */
136
+ const inFlight = new Map();
137
+ /**
138
+ * A synchronous snapshot of the catalogues this process has resolved.
139
+ *
140
+ * ⚠️ A SNAPSHOT, not a live view: callers that render from it must re-read it
141
+ * after `loadBuiltInLocale` resolves, or they will keep rendering the answer
142
+ * they took before the fetch landed.
143
+ */
144
+ export function getLoadedBuiltInLocales() {
145
+ return { ...loaded };
146
+ }
147
+ /**
148
+ * Make a catalogue resident WITHOUT fetching it — for a caller that already
149
+ * holds one.
150
+ *
151
+ * The only caller in this repository is `locales/index.ts`'s
152
+ * {@link registerBuiltInLocales}: that module statically imports all ten, so
153
+ * asking {@link loadBuiltInLocale} to `import()` them again would be an await
154
+ * for modules the evaluator already has. It is also what lets a test that needs
155
+ * a non-`en` locale resident say so in ONE SYNCHRONOUS LINE at module scope —
156
+ * the shape AGENTS.md prescribes for a cost that belongs in the import phase,
157
+ * rather than a `beforeAll` bounded by `hookTimeout`.
158
+ *
159
+ * ⛔ Not exported from the package entry, deliberately. An app that wants a
160
+ * catalogue resident asks for it by CODE through `loadBuiltInLocale`; handing
161
+ * the entry a way to inject arbitrary objects into the registry would make
162
+ * "which catalogue is this" unanswerable from the codes alone.
163
+ */
164
+ export function registerBuiltInLocale(lang, catalogue) {
165
+ loaded[lang] = catalogue;
166
+ }
167
+ /**
168
+ * Whether the catalogue that serves `lang` is already resident (no fetch
169
+ * needed). Reads the same exact-then-base resolution as
170
+ * {@link loadBuiltInLocale}, so the two agree for a region-tagged code: when
171
+ * `zh` is resident, `zh-CN` needs no fetch either.
172
+ */
173
+ export function isBuiltInLocaleLoaded(lang) {
174
+ const code = resolveBuiltInLanguage(lang);
175
+ return code !== null && Object.prototype.hasOwnProperty.call(loaded, code);
176
+ }
177
+ /**
178
+ * Fetch the built-in catalogue that serves `lang`, or resolve `null` for a code
179
+ * this package has no catalogue for (an app-supplied locale, a typo).
180
+ *
181
+ * A region-tagged code resolves its base language's catalogue (`zh-CN` → the
182
+ * `zh` catalogue) unless a catalogue is keyed by the full tag — see
183
+ * {@link resolveBuiltInLanguage}. The catalogue is fetched and memoised under
184
+ * ITS OWN code, so `zh-CN` and `zh` share one fetch and
185
+ * {@link getLoadedBuiltInLocales} stays keyed by catalogue code, which is how
186
+ * `createI18n` and the app-shell splash read it.
187
+ *
188
+ * Memoised in both directions: a resolved catalogue is returned from the
189
+ * registry without a second import, and concurrent callers share one in-flight
190
+ * promise. ⛔ A REJECTED load is not memoised — a chunk that failed to download
191
+ * (offline, a cache miss against a redeployed origin) must be retryable, and a
192
+ * language preference is never worth wedging permanently over one bad fetch.
193
+ */
194
+ export async function loadBuiltInLocale(lang) {
195
+ const code = resolveBuiltInLanguage(lang);
196
+ if (code === null)
197
+ return null;
198
+ const resident = loaded[code];
199
+ if (resident)
200
+ return resident;
201
+ const loader = Object.prototype.hasOwnProperty.call(LAZY_LOCALE_LOADERS, code)
202
+ ? LAZY_LOCALE_LOADERS[code]
203
+ : undefined;
204
+ if (!loader)
205
+ return null;
206
+ const existing = inFlight.get(code);
207
+ if (existing)
208
+ return existing;
209
+ const pending = loader()
210
+ .then((mod) => {
211
+ const catalogue = mod.default;
212
+ loaded[code] = catalogue;
213
+ return catalogue;
214
+ })
215
+ .finally(() => {
216
+ inFlight.delete(code);
217
+ });
218
+ inFlight.set(code, pending);
219
+ return pending;
220
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @object-ui/i18n — text direction, without any catalogue payload.
3
+ *
4
+ * Split out of `locales/index.ts` by objectui#7479. That barrel statically
5
+ * imports all ten catalogues, so anything reaching `isRTL` through it dragged
6
+ * ~1.8 MB of raw translation data along with two constants. Direction is a
7
+ * property OF a language code, not of its catalogue, so it belongs in a module
8
+ * that holds no catalogue at all.
9
+ *
10
+ * `locales/index.ts` re-exports both names, so the historical import path keeps
11
+ * working for the consumers that genuinely want all ten packs.
12
+ */
13
+ /**
14
+ * List of RTL language codes.
15
+ *
16
+ * Deliberately wider than the built-in catalogue set: `he`, `fa` and `ur` have
17
+ * no pack here, but an app that supplies one through `config.resources` or a
18
+ * `loadLanguage` loader still needs the right direction.
19
+ */
20
+ export declare const RTL_LANGUAGES: readonly ["ar", "he", "fa", "ur"];
21
+ /**
22
+ * Check if a language code is RTL.
23
+ */
24
+ export declare function isRTL(lang: string): boolean;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @object-ui/i18n — text direction, without any catalogue payload.
3
+ *
4
+ * Split out of `locales/index.ts` by objectui#7479. That barrel statically
5
+ * imports all ten catalogues, so anything reaching `isRTL` through it dragged
6
+ * ~1.8 MB of raw translation data along with two constants. Direction is a
7
+ * property OF a language code, not of its catalogue, so it belongs in a module
8
+ * that holds no catalogue at all.
9
+ *
10
+ * `locales/index.ts` re-exports both names, so the historical import path keeps
11
+ * working for the consumers that genuinely want all ten packs.
12
+ */
13
+ /**
14
+ * List of RTL language codes.
15
+ *
16
+ * Deliberately wider than the built-in catalogue set: `he`, `fa` and `ur` have
17
+ * no pack here, but an app that supplies one through `config.resources` or a
18
+ * `loadLanguage` loader still needs the right direction.
19
+ */
20
+ export const RTL_LANGUAGES = ['ar', 'he', 'fa', 'ur'];
21
+ /**
22
+ * Check if a language code is RTL.
23
+ */
24
+ export function isRTL(lang) {
25
+ return RTL_LANGUAGES.includes(lang);
26
+ }