@object-ui/i18n 17.5.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 (58) hide show
  1. package/CHANGELOG.md +4954 -0
  2. package/README.md +234 -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 +28 -2
  10. package/dist/i18n.js +99 -17
  11. package/dist/index.d.ts +17 -23
  12. package/dist/index.js +51 -27
  13. package/dist/locales/ar.d.ts +1113 -370
  14. package/dist/locales/ar.js +1276 -428
  15. package/dist/locales/de.d.ts +785 -370
  16. package/dist/locales/de.js +896 -401
  17. package/dist/locales/en.d.ts +800 -385
  18. package/dist/locales/en.js +1261 -419
  19. package/dist/locales/es.d.ts +867 -370
  20. package/dist/locales/es.js +976 -398
  21. package/dist/locales/fr.d.ts +867 -370
  22. package/dist/locales/fr.js +976 -399
  23. package/dist/locales/index.d.ts +8682 -3761
  24. package/dist/locales/index.js +68 -29
  25. package/dist/locales/ja.d.ts +785 -370
  26. package/dist/locales/ja.js +891 -395
  27. package/dist/locales/ko.d.ts +785 -370
  28. package/dist/locales/ko.js +893 -399
  29. package/dist/locales/pt.d.ts +867 -370
  30. package/dist/locales/pt.js +975 -397
  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 +949 -370
  36. package/dist/locales/ru.js +1093 -419
  37. package/dist/locales/zh.d.ts +803 -388
  38. package/dist/locales/zh.js +899 -388
  39. package/dist/pickLocalized.d.ts +67 -0
  40. package/dist/pickLocalized.js +114 -0
  41. package/dist/provider.d.ts +102 -13
  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 +13 -2
  47. package/dist/useObjectLabel.d.ts +118 -22
  48. package/dist/useObjectLabel.js +148 -23
  49. package/dist/useSafeTranslation.js +41 -43
  50. package/dist/utils/formatting.d.ts +16 -0
  51. package/dist/utils/formatting.js +23 -3
  52. package/dist/utils/index.d.ts +4 -4
  53. package/dist/utils/index.js +4 -4
  54. package/dist/utils/spec-formatters.d.ts +13 -23
  55. package/dist/utils/spec-formatters.js +41 -2
  56. package/dist/utils/spec-translations.d.ts +16 -3
  57. package/dist/utils/spec-translations.js +52 -13
  58. package/package.json +12 -6
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The ONE interpolator every provider-less translation path in this package
3
+ * runs — extracted so there is exactly one of it (objectui#6219).
4
+ *
5
+ * ## Why this module exists at all
6
+ *
7
+ * Three things can render a translation in this repo, and each has its own
8
+ * answer to "does a `{{hole}}` get filled?":
9
+ *
10
+ * 1. **i18next, with an `I18nProvider` mounted.** It interpolates. This is
11
+ * the reference behaviour every other path has to agree with.
12
+ * 2. **`createSafeTranslation`'s `fallbackT`** (`useSafeTranslation.ts`).
13
+ * It interpolates — with the exact literal needle below.
14
+ * 3. **react-i18next's not-ready `t`**, which is what a bare
15
+ * `useObjectTranslation()` yields when no i18next instance is initialised.
16
+ * Measured in `react-i18next@17.0.11/dist/es/useTranslation.js`:
17
+ * `notReadyT` returns `options.defaultValue` VERBATIM and does not
18
+ * interpolate at all.
19
+ *
20
+ * (3) was objectui#6219: 68 inline defaults across 24 files carried a
21
+ * `{{hole}}` that reached the user as literal braces on any host that embeds an
22
+ * ObjectUI component without `I18nProvider` — the configuration
23
+ * `createSafeTranslation` exists for (objectui#3865), so a supported one.
24
+ * `useObjectTranslation` now runs its not-ready result through this function,
25
+ * which makes (3) agree with (2), and (2) already agrees with (1) for the one
26
+ * spelling this repo is allowed to author.
27
+ *
28
+ * ## Why one shared function rather than a second copy
29
+ *
30
+ * The semantics below are pinned in three places at once —
31
+ * `useSafeTranslation.test.tsx` (behaviour), `fallback-placeholder-spelling-3512.test.ts`
32
+ * (the copy is held to what this can resolve) and
33
+ * `scripts/check-i18n-call-site-keys.mjs` class 7 (the same rule over inline
34
+ * defaults). A second interpolator with its own drift would have made all three
35
+ * of those true of one path and quietly false of the other. There is one
36
+ * function, so "the fallback resolves only `{{name}}`" is one fact.
37
+ *
38
+ * ## The exact spelling, and why only that one
39
+ *
40
+ * objectui#3512 measured that i18next additionally accepts `{{ name }}`,
41
+ * `{{count, number}}`, `{{- name}}` and `$t(key)`, and the ruling was
42
+ * deliberately NOT to teach the fallback three more dialects (a second
43
+ * interpolator to keep in step with i18next forever) but to hold the copy to
44
+ * the one spelling both paths agree on — enforced by the two gates named above.
45
+ * This module keeps that ruling: it widens WHICH BINDINGS interpolate, never
46
+ * WHICH SPELLINGS resolve.
47
+ */
48
+ /**
49
+ * The i18next option that names the string to use when the lookup misses.
50
+ *
51
+ * It is a LOOKUP CONTROL, not interpolation data (objectui#3865): it chooses
52
+ * which string is rendered, so it must never also be spliced into a
53
+ * `{{defaultValue}}` hole in the string it chose — or in any other.
54
+ */
55
+ export const DEFAULT_VALUE_OPTION = 'defaultValue';
56
+ /**
57
+ * Fill `{{name}}` holes in `value` from `options`, i18next-compatibly.
58
+ *
59
+ * Returns `value` untouched when there is nothing to fill from, so a caller can
60
+ * hand it every result unconditionally.
61
+ *
62
+ * @param value - The string that was chosen for rendering.
63
+ * @param options - The `t()` call's options object, or `undefined`.
64
+ */
65
+ export function interpolateFallback(value, options) {
66
+ if (!options)
67
+ return value;
68
+ let out = value;
69
+ for (const [k, v] of Object.entries(options)) {
70
+ // Reserved: see DEFAULT_VALUE_OPTION. Skipped whatever its type, so an
71
+ // ignored non-string default cannot re-enter through this loop.
72
+ //
73
+ // This is the one deliberate divergence from i18next on this path, and it
74
+ // is unreachable with today's strings: i18next passes the whole options
75
+ // object to its interpolator, so it WOULD render
76
+ // `'Fallback: {{defaultValue}}'` as `'Fallback: INLINE'` (measured on
77
+ // 26.3.6) — but no value in this repo spells that hole (zero hits for
78
+ // `{{defaultValue` across packages/apps/examples), and splicing a fallback
79
+ // string into a hole named after itself has no sensible reading. The
80
+ // alternative — letting a call site's fallback text leak into an unrelated
81
+ // table value — is the worse of the two.
82
+ if (k === DEFAULT_VALUE_OPTION)
83
+ continue;
84
+ // `split(needle).join(value)` — deliberately not `replace`, and not
85
+ // `replaceAll` either (objectui#3418). This path must agree with i18next,
86
+ // which serves the *provider* path; any divergence is a silent fork that
87
+ // only shows up on provider-less hosts, where we are least likely to see
88
+ // it:
89
+ // 1. `replace` with a string needle substitutes only the FIRST
90
+ // occurrence. i18next substitutes every one, so a sentence that
91
+ // repeats a placeholder ("Selected {{count}} of {{count}} items" —
92
+ // natural in many locales, and often required by RTL / agglutinative
93
+ // word order) leaked literal braces to users.
94
+ // 2. `replace` AND `replaceAll` both interpret `$&`, `` $` ``, `$'` and
95
+ // `$$` in the *replacement* string. i18next does not. Values here are
96
+ // runtime data — record labels, search terms — so this one is
97
+ // reachable today, unlike (1).
98
+ // split/join is literal on both sides, which is exactly i18next's
99
+ // behaviour, and needs no regex escaping of the placeholder name.
100
+ out = out.split(`{{${k}}}`).join(String(v));
101
+ }
102
+ return out;
103
+ }
104
+ /**
105
+ * The options object of a `t()` call, or `undefined` when the call passed none.
106
+ *
107
+ * i18next's `t` accepts both `t(key, options)` and `t(key, defaultValue,
108
+ * options)`, so the options object is the LAST argument when it is an object at
109
+ * all. A string second argument is a default value, not data — there is nothing
110
+ * to interpolate from, which is why `t(key, 'Hi {{name}}')` keeps its braces on
111
+ * both paths.
112
+ *
113
+ * Arrays are excluded deliberately: `t(key, [...])` is not an i18next options
114
+ * shape, and `Object.entries` over one would mint `{{0}}`-style needles that no
115
+ * copy in this repo spells.
116
+ */
117
+ export function optionsOf(args) {
118
+ if (args.length < 2)
119
+ return undefined;
120
+ const last = args[args.length - 1];
121
+ if (typeof last !== 'object' || last === null || Array.isArray(last))
122
+ return undefined;
123
+ return last;
124
+ }
package/dist/i18n.d.ts CHANGED
@@ -4,13 +4,21 @@
4
4
  * Wraps i18next with Object UI defaults and built-in locale support.
5
5
  */
6
6
  import { type i18n as I18nInstance } from 'i18next';
7
- import type { TranslationKeys } from './locales/en';
7
+ import type { TranslationKeys } from './locales/en.js';
8
8
  export interface I18nConfig {
9
9
  /** Default language (default: 'en') */
10
10
  defaultLanguage?: string;
11
11
  /** Fallback language (default: 'en') */
12
12
  fallbackLanguage?: string;
13
- /** Additional translation resources to merge with built-in locales */
13
+ /**
14
+ * Additional translation resources, **deep-merged** over the built-in packs.
15
+ *
16
+ * The packs' top-level keys are the namespace groups (`common`, `calendar`,
17
+ * `list`, ...), so supplying a partial group merges into it and leaves the
18
+ * group's other keys in place:
19
+ * `{ en: { calendar: { today: 'Heute' } } }` overrides `calendar.today` only.
20
+ * Arrays are replaced wholesale, never concatenated (objectui#7572).
21
+ */
14
22
  resources?: Record<string, Record<string, unknown>>;
15
23
  /** Whether to detect browser language automatically (default: true) */
16
24
  detectBrowserLanguage?: boolean;
@@ -39,6 +47,24 @@ export interface I18nConfig {
39
47
  * API — shared between `i18n.ts` and `useObjectLabel.ts` to avoid drift.
40
48
  */
41
49
  export declare const I18N_PROBE_FLAG = "__ouiLabelProbe";
50
+ /**
51
+ * The language {@link createI18n} will boot in for this config — `defaultLanguage`,
52
+ * overridden by a browser language this renderer can produce.
53
+ *
54
+ * Exported because the answer is needed BEFORE an instance exists
55
+ * (objectui#7479): with the catalogues lazy, a host that wants its first render
56
+ * to already be in the right language has to know which catalogue to await, and
57
+ * the only alternative — create an instance, read `.language`, throw it away —
58
+ * would boot i18next in the wrong locale to find out what the right one is.
59
+ * `I18nProvider` and {@link preloadBootstrapLocale} answer with THIS function,
60
+ * so the host and the provider cannot disagree about which catalogue is active.
61
+ *
62
+ * ⚠️ Browser detection is asked of {@link isBuiltInLanguage} rather than of the
63
+ * merged resource map: a catalogue that has not been fetched yet is still a
64
+ * language this renderer can produce, and asking the map would have made
65
+ * detection depend on load order.
66
+ */
67
+ export declare function pickInitialLanguage(config?: I18nConfig): string;
42
68
  /**
43
69
  * Create and initialize an i18next instance with Object UI defaults
44
70
  */
package/dist/i18n.js CHANGED
@@ -5,7 +5,8 @@
5
5
  */
6
6
  import i18next from 'i18next';
7
7
  import { initReactI18next } from 'react-i18next';
8
- import { builtInLocales, isRTL } from './locales/index';
8
+ import { BUILT_IN_LANGUAGE_CODES, getLoadedBuiltInLocales, isBuiltInLanguage, } from './locales/registry.js';
9
+ import { isRTL } from './locales/rtl.js';
9
10
  /**
10
11
  * Internal `t()` option flag set by `useObjectLabel` on its convention-key
11
12
  * probes. The missing-key handler skips any lookup carrying this flag, so the
@@ -34,23 +35,111 @@ function createMissingKeyHandler() {
34
35
  return;
35
36
  seen.add(dedupeKey);
36
37
  const fb = fallbackValue ? `"${fallbackValue}"` : 'the key itself';
37
- // eslint-disable-next-line no-console
38
38
  console.warn(`[object-ui i18n] Missing translation for "${key}" (language "${lng}") — falling back to ${fb}.`);
39
39
  };
40
40
  }
41
+ /**
42
+ * A plain object — the only shape the resource merge recurses into. Arrays and
43
+ * `null` are excluded on purpose; see {@link deepMergeTranslations}.
44
+ */
45
+ function isPlainObject(value) {
46
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
47
+ }
48
+ /**
49
+ * Deep-merge caller-supplied translations over a built-in language pack.
50
+ *
51
+ * A language pack's top-level keys ARE the namespace groups (`common`,
52
+ * `calendar`, `list`, ...), each a nested object, so the one-level merge this
53
+ * replaced made `resources: { en: { calendar: { today: 'Heute' } } }` **replace**
54
+ * the whole `calendar` group: `month` / `week` / `day` / `allDay` / `newEvent` /
55
+ * `moreEvents` / `unscheduled` left the instance, `t('calendar.allDay')` returned
56
+ * the bare key, and `calendar.allDay` reached the DOM as literal text
57
+ * (objectui#7572). Packs nest up to four levels below the group
58
+ * (`console.ai.empty.build.title`), so the merge recurses rather than adding one
59
+ * fixed extra level.
60
+ *
61
+ * ARRAYS ARE REPLACED, NOT CONCATENATED — a deliberate choice, not a library
62
+ * default. Measured: no built-in pack carries an array value today (every leaf
63
+ * in all ten packs is a string), so nothing observable rides on this; the rule
64
+ * decides what a future array means. An author who writes an array is naming the
65
+ * whole list, so replacement is the only rule that lets them shorten or reorder
66
+ * one, and the only one that is idempotent when the merge runs again. This is
67
+ * narrower than i18next's own `deepExtend` (which the provider's async
68
+ * `addResourceBundle` path uses): that recurses into arrays index-wise and would
69
+ * leave a longer base array's tail behind — the same silent-hybrid shape this
70
+ * issue is about.
71
+ *
72
+ * `__proto__` is skipped. Recursive assignment, unlike the object spread it
73
+ * replaces, would otherwise reach the prototype setter; i18next's `deepExtend`
74
+ * guards the same key.
75
+ */
76
+ function deepMergeTranslations(base, override) {
77
+ const merged = { ...base };
78
+ for (const key of Object.keys(override)) {
79
+ if (key === '__proto__')
80
+ continue;
81
+ const incoming = override[key];
82
+ const existing = merged[key];
83
+ merged[key] =
84
+ isPlainObject(existing) && isPlainObject(incoming)
85
+ ? deepMergeTranslations(existing, incoming)
86
+ : incoming;
87
+ }
88
+ return merged;
89
+ }
90
+ /**
91
+ * The language {@link createI18n} will boot in for this config — `defaultLanguage`,
92
+ * overridden by a browser language this renderer can produce.
93
+ *
94
+ * Exported because the answer is needed BEFORE an instance exists
95
+ * (objectui#7479): with the catalogues lazy, a host that wants its first render
96
+ * to already be in the right language has to know which catalogue to await, and
97
+ * the only alternative — create an instance, read `.language`, throw it away —
98
+ * would boot i18next in the wrong locale to find out what the right one is.
99
+ * `I18nProvider` and {@link preloadBootstrapLocale} answer with THIS function,
100
+ * so the host and the provider cannot disagree about which catalogue is active.
101
+ *
102
+ * ⚠️ Browser detection is asked of {@link isBuiltInLanguage} rather than of the
103
+ * merged resource map: a catalogue that has not been fetched yet is still a
104
+ * language this renderer can produce, and asking the map would have made
105
+ * detection depend on load order.
106
+ */
107
+ export function pickInitialLanguage(config = {}) {
108
+ const { defaultLanguage = 'en', resources = {}, detectBrowserLanguage = true } = config;
109
+ if (!detectBrowserLanguage || typeof navigator === 'undefined')
110
+ return defaultLanguage;
111
+ const browserLang = navigator.language?.split('-')[0];
112
+ if (!browserLang)
113
+ return defaultLanguage;
114
+ const known = isBuiltInLanguage(browserLang) ||
115
+ Object.prototype.hasOwnProperty.call(resources, browserLang);
116
+ return known ? browserLang : defaultLanguage;
117
+ }
41
118
  /**
42
119
  * Create and initialize an i18next instance with Object UI defaults
43
120
  */
44
121
  export function createI18n(config = {}) {
45
- const { defaultLanguage = 'en', fallbackLanguage = 'en', resources = {}, detectBrowserLanguage = true, interpolation, warnMissingKeys = isDevEnv(), } = config;
46
- // Merge built-in locales with user-provided resources
122
+ const { fallbackLanguage = 'en', resources = {}, interpolation, warnMissingKeys = isDevEnv(), } = config;
123
+ // `defaultLanguage` and `detectBrowserLanguage` are read by
124
+ // {@link pickInitialLanguage}, which owns that precedence for this function
125
+ // AND for the hosts that must know the answer before an instance exists.
126
+ // Merge the catalogues that are RESIDENT with user-provided resources.
127
+ //
128
+ // ⛔ Every built-in code gets a bundle here even when its catalogue has not
129
+ // been fetched yet (objectui#7479). The empty bundle is load-bearing, not a
130
+ // placeholder: it keeps `instance.options.resources` — which
131
+ // {@link getAvailableLanguages}, the browser detection below and i18next's
132
+ // own `hasResourceBundle` all read — enumerating every language this package
133
+ // can produce, instead of shrinking to whatever happened to be loaded at
134
+ // instance-creation time. Until the catalogue lands, `fallbackLng` renders
135
+ // those keys through `en`; `I18nProvider` then deep-merges the real
136
+ // catalogue in WITHOUT overwriting anything `resources` put there, so the
137
+ // precedence below survives the async arrival.
47
138
  const mergedResources = {};
48
- for (const [lang, translations] of Object.entries(builtInLocales)) {
139
+ const residentCatalogues = getLoadedBuiltInLocales();
140
+ for (const lang of BUILT_IN_LANGUAGE_CODES) {
49
141
  mergedResources[lang] = {
50
- translation: {
51
- ...translations,
52
- ...(resources[lang] || {}),
53
- },
142
+ translation: deepMergeTranslations((residentCatalogues[lang] ?? {}), resources[lang] || {}),
54
143
  };
55
144
  }
56
145
  // Add any additional languages from resources not in built-in locales
@@ -59,14 +148,7 @@ export function createI18n(config = {}) {
59
148
  mergedResources[lang] = { translation: translations };
60
149
  }
61
150
  }
62
- // Detect browser language if enabled
63
- let lng = defaultLanguage;
64
- if (detectBrowserLanguage && typeof navigator !== 'undefined') {
65
- const browserLang = navigator.language?.split('-')[0];
66
- if (browserLang && mergedResources[browserLang]) {
67
- lng = browserLang;
68
- }
69
- }
151
+ const lng = pickInitialLanguage(config);
70
152
  const instance = i18next.createInstance();
71
153
  // IMPORTANT: disable react-i18next Suspense. Otherwise `useTranslation()`
72
154
  // calls during bootstrap throw a Suspense promise (the i18n instance is
package/dist/index.d.ts CHANGED
@@ -30,26 +30,20 @@
30
30
  *
31
31
  * @packageDocumentation
32
32
  */
33
- export { createI18n, getDirection, getAvailableLanguages, type I18nConfig, type TranslationKeys } from './i18n';
34
- export { I18nProvider, useObjectTranslation, useI18nContext, LOCALE_STORAGE_KEY, readStoredLanguage, LOCALE_SEED_STORAGE_KEY, readCachedLanguageSeed, cacheLanguageSeed, type I18nProviderProps, } from './provider';
35
- export { createSafeTranslation, useSafeTranslate } from './useSafeTranslation';
36
- export { useObjectLabel, useSafeFieldLabel } from './useObjectLabel';
37
- export { builtInLocales, isRTL, RTL_LANGUAGES } from './locales/index';
38
- export { default as en } from './locales/en';
39
- export { default as zh } from './locales/zh';
40
- export { default as ja } from './locales/ja';
41
- export { default as ko } from './locales/ko';
42
- export { default as de } from './locales/de';
43
- export { default as fr } from './locales/fr';
44
- export { default as es } from './locales/es';
45
- export { default as pt } from './locales/pt';
46
- export { default as ru } from './locales/ru';
47
- export { default as ar } from './locales/ar';
48
- export { formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, type DateFormatOptions, type CurrencyFormatOptions, type NumberFormatOptions, } from './utils/index';
49
- export { resolvePlural, formatDateSpec, formatNumberSpec, applyLocaleConfig, type SpecPluralRule, type SpecDateFormat, type SpecNumberFormat, type SpecLocaleConfig, } from './utils/index';
50
- export { isSpecTranslationData, transformSpecTranslations, type SpecTranslationData, } from './utils/index';
51
- export { pickLocalized } from './pickLocalized';
52
- export { LocalizationProvider, useLocalization, type LocalizationValue } from './LocalizationContext';
53
- export { resolveFieldCurrency } from './currency';
54
- export { useDisplayLocale } from './useDisplayLocale';
55
- export { formatDisplayNumber, shouldGroupDisplayNumber, type DisplayNumberFormatOptions, } from './utils/index';
33
+ export { createI18n, getDirection, getAvailableLanguages, pickInitialLanguage, type I18nConfig, type TranslationKeys, } from './i18n.js';
34
+ export { I18nProvider, useObjectTranslation, useI18nContext, LOCALE_STORAGE_KEY, readStoredLanguage, LOCALE_SEED_STORAGE_KEY, readCachedLanguageSeed, cacheLanguageSeed, resolveBootstrapLanguage, preloadBootstrapLocale, type BootstrapLocaleOptions, type I18nProviderProps, } from './provider.js';
35
+ export { createSafeTranslation, useSafeTranslate } from './useSafeTranslation.js';
36
+ export { builtinAggregateLabels, type SafeTranslate } from './builtinAggregateLabels.js';
37
+ export type { TranslateFn } from './translateFn.js';
38
+ export { useObjectLabel, useSafeFieldLabel } from './useObjectLabel.js';
39
+ export { isRTL, RTL_LANGUAGES } from './locales/rtl.js';
40
+ export { BUILT_IN_LANGUAGE_CODES, DEFAULT_BUILT_IN_LANGUAGE, getLoadedBuiltInLocales, isBuiltInLanguage, isBuiltInLocaleLoaded, loadBuiltInLocale, type LocaleCatalogue, } from './locales/registry.js';
41
+ export { default as en } from './locales/en.js';
42
+ export { formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, type DateFormatOptions, type CurrencyFormatOptions, type NumberFormatOptions, } from './utils/index.js';
43
+ export { resolvePlural, formatDateSpec, formatNumberSpec, applyLocaleConfig, type SpecPluralRule, type SpecDateFormat, type SpecNumberFormat, type SpecLocaleConfig, } from './utils/index.js';
44
+ export { isSpecTranslationData, transformSpecTranslations, type SpecTranslationData, } from './utils/index.js';
45
+ export { pickLocalized, setLocalized, clearLocalized } from './pickLocalized.js';
46
+ export { LocalizationProvider, useLocalization, type LocalizationValue } from './LocalizationContext.js';
47
+ export { resolveFieldCurrency } from './currency.js';
48
+ export { useDisplayLocale } from './useDisplayLocale.js';
49
+ export { formatDisplayNumber, shouldGroupDisplayNumber, type DisplayNumberFormatOptions, } from './utils/index.js';
package/dist/index.js CHANGED
@@ -31,46 +31,70 @@
31
31
  * @packageDocumentation
32
32
  */
33
33
  // Core i18n setup
34
- export { createI18n, getDirection, getAvailableLanguages } from './i18n';
34
+ export { createI18n, getDirection, getAvailableLanguages, pickInitialLanguage, } from './i18n.js';
35
35
  // React integration
36
36
  export { I18nProvider, useObjectTranslation, useI18nContext,
37
37
  // Language-preference persistence: the key the provider writes, and the
38
38
  // reader apps that bring their own i18next instance need to honour it.
39
39
  LOCALE_STORAGE_KEY, readStoredLanguage,
40
- // Tenant locale seed (objectui#4035) — a SEPARATE slot from the explicit
41
- // choice above, and never a substitute for it. An app that fetches its
42
- // tenant's server-side locale caches it here; the provider applies it at
40
+ // Locale seed (objectui#4035) — a SEPARATE slot from the explicit
41
+ // choice above, and never a substitute for it. An app that fetches the
42
+ // server's resolved locale for the last signed-in caller caches it here; the provider applies it at
43
43
  // bootstrap only when the user has chosen nothing themselves.
44
- LOCALE_SEED_STORAGE_KEY, readCachedLanguageSeed, cacheLanguageSeed, } from './provider';
44
+ LOCALE_SEED_STORAGE_KEY, readCachedLanguageSeed, cacheLanguageSeed,
45
+ // Resolve the boot language, and fetch its catalogue, BEFORE first render
46
+ // (objectui#7479). A host with an await before `createRoot().render()` spends
47
+ // it here and paints in the user's language; a host without one gets the
48
+ // documented `en`-while-loading render instead.
49
+ resolveBootstrapLanguage, preloadBootstrapLocale, } from './provider.js';
45
50
  // Safe translation hook factory
46
- export { createSafeTranslation, useSafeTranslate } from './useSafeTranslation';
51
+ export { createSafeTranslation, useSafeTranslate } from './useSafeTranslation.js';
52
+ // objectui#7258 — locale labels for the server's built-in default measures,
53
+ // handed to core's `buildChartSeries` as `builtinAggregateLabels`.
54
+ export { builtinAggregateLabels } from './builtinAggregateLabels.js';
47
55
  // Convention-based object/field label i18n
48
- export { useObjectLabel, useSafeFieldLabel } from './useObjectLabel';
49
- // Locale packs
50
- export { builtInLocales, isRTL, RTL_LANGUAGES } from './locales/index';
51
- export { default as en } from './locales/en';
52
- export { default as zh } from './locales/zh';
53
- export { default as ja } from './locales/ja';
54
- export { default as ko } from './locales/ko';
55
- export { default as de } from './locales/de';
56
- export { default as fr } from './locales/fr';
57
- export { default as es } from './locales/es';
58
- export { default as pt } from './locales/pt';
59
- export { default as ru } from './locales/ru';
60
- export { default as ar } from './locales/ar';
56
+ export { useObjectLabel, useSafeFieldLabel } from './useObjectLabel.js';
57
+ // Locale packs — ⛔ ONE catalogue is statically re-exported here, not ten
58
+ // (objectui#7479).
59
+ //
60
+ // This entry used to re-export all ten packs and `builtInLocales`, which made
61
+ // every one of them statically reachable from any import of this package. The
62
+ // console measured the result: ~450 KB gzipped of translation data in the EAGER
63
+ // closure, ten catalogues fetched before first paint for a viewer who reads
64
+ // one, and every new key paid for by every visitor in every language.
65
+ //
66
+ // ⛔ Do not restore a static re-export of a second catalogue here, and do not
67
+ // re-export `builtInLocales` from this entry. Both put all ten back:
68
+ // `builtInLocales` names every pack in one object literal, so nothing can shake
69
+ // it. `scripts/check-eager-locale-catalogues.mjs` fails the build if a
70
+ // non-active catalogue reappears in the console's eager closure, and
71
+ // `src/__tests__/entry-locale-shape-7479.test.ts` fails on the SOURCE shape so
72
+ // the reason is legible without a console build.
73
+ //
74
+ // What replaced them:
75
+ // - `en` stays — it is `fallbackLng`, the source of `TranslationKeys`, and
76
+ // the synchronous dictionary the app-shell splash renders from before i18n
77
+ // is usable. See `./locales/registry.js` for the full argument.
78
+ // - `loadBuiltInLocale(code)` fetches any of the ten on demand.
79
+ // - `BUILT_IN_LANGUAGE_CODES` enumerates them at zero payload.
80
+ // - `@object-ui/i18n/locales` is the explicit, published door to all ten for
81
+ // a caller that genuinely wants them all resident (the parity suites do).
82
+ export { isRTL, RTL_LANGUAGES } from './locales/rtl.js';
83
+ export { BUILT_IN_LANGUAGE_CODES, DEFAULT_BUILT_IN_LANGUAGE, getLoadedBuiltInLocales, isBuiltInLanguage, isBuiltInLocaleLoaded, loadBuiltInLocale, } from './locales/registry.js';
84
+ export { default as en } from './locales/en.js';
61
85
  // Formatting utilities
62
- export { formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, } from './utils/index';
86
+ export { formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, } from './utils/index.js';
63
87
  // Spec-aligned formatters (v2.0.7)
64
- export { resolvePlural, formatDateSpec, formatNumberSpec, applyLocaleConfig, } from './utils/index';
88
+ export { resolvePlural, formatDateSpec, formatNumberSpec, applyLocaleConfig, } from './utils/index.js';
65
89
  // Spec TranslationData → flat namespace transform (consumed by
66
90
  // `useObjectLabel`). Exposed so apps don't have to copy/paste — see
67
91
  // `apps/console/src/loadLanguage.ts` for the canonical consumer.
68
- export { isSpecTranslationData, transformSpecTranslations, } from './utils/index';
69
- export { pickLocalized } from './pickLocalized';
70
- export { LocalizationProvider, useLocalization } from './LocalizationContext';
71
- export { resolveFieldCurrency } from './currency';
92
+ export { isSpecTranslationData, transformSpecTranslations, } from './utils/index.js';
93
+ export { pickLocalized, setLocalized, clearLocalized } from './pickLocalized.js';
94
+ export { LocalizationProvider, useLocalization } from './LocalizationContext.js';
95
+ export { resolveFieldCurrency } from './currency.js';
72
96
  // The one number-display formatter (objectui#4033) and the locale it formats
73
97
  // in. Every field cell / widget / metric renderer goes through this pair —
74
98
  // see `utils/number-display.ts` for the grouping policy and its interim status.
75
- export { useDisplayLocale } from './useDisplayLocale';
76
- export { formatDisplayNumber, shouldGroupDisplayNumber, } from './utils/index';
99
+ export { useDisplayLocale } from './useDisplayLocale.js';
100
+ export { formatDisplayNumber, shouldGroupDisplayNumber, } from './utils/index.js';