@object-ui/i18n 17.2.0 → 17.5.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 (41) hide show
  1. package/CHANGELOG.md +1817 -1
  2. package/README.md +26 -13
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +14 -1
  5. package/dist/locales/ar.d.ts +550 -107
  6. package/dist/locales/ar.js +646 -156
  7. package/dist/locales/de.d.ts +550 -107
  8. package/dist/locales/de.js +661 -178
  9. package/dist/locales/en.d.ts +586 -143
  10. package/dist/locales/en.js +851 -143
  11. package/dist/locales/es.d.ts +550 -107
  12. package/dist/locales/es.js +647 -160
  13. package/dist/locales/fr.d.ts +550 -107
  14. package/dist/locales/fr.js +640 -155
  15. package/dist/locales/index.d.ts +5587 -1157
  16. package/dist/locales/ja.d.ts +550 -107
  17. package/dist/locales/ja.js +632 -149
  18. package/dist/locales/ko.d.ts +550 -107
  19. package/dist/locales/ko.js +638 -156
  20. package/dist/locales/pt.d.ts +550 -107
  21. package/dist/locales/pt.js +639 -157
  22. package/dist/locales/ru.d.ts +550 -107
  23. package/dist/locales/ru.js +650 -156
  24. package/dist/locales/zh.d.ts +588 -145
  25. package/dist/locales/zh.js +629 -145
  26. package/dist/pickLocalized.d.ts +21 -0
  27. package/dist/pickLocalized.js +40 -7
  28. package/dist/provider.d.ts +125 -1
  29. package/dist/provider.js +380 -4
  30. package/dist/useDisplayLocale.d.ts +49 -0
  31. package/dist/useDisplayLocale.js +55 -0
  32. package/dist/useObjectLabel.d.ts +8 -2
  33. package/dist/useObjectLabel.js +28 -8
  34. package/dist/useSafeTranslation.js +67 -3
  35. package/dist/utils/index.d.ts +1 -0
  36. package/dist/utils/index.js +1 -0
  37. package/dist/utils/number-display.d.ts +32 -0
  38. package/dist/utils/number-display.js +32 -0
  39. package/dist/utils/spec-formatters.d.ts +39 -13
  40. package/dist/utils/spec-formatters.js +3 -3
  41. package/package.json +5 -4
@@ -3,21 +3,85 @@
3
3
  *
4
4
  * When no I18nProvider is available (e.g., in tests or standalone usage),
5
5
  * this hook falls back to the provided default translations instead of
6
- * returning raw i18n keys.
6
+ * returning raw i18n keys — and, for a key the `defaults` map does not carry,
7
+ * to the call site's own inline `t(key, { defaultValue })` (objectui#3865).
8
+ * The lookup order is i18next's, so a provider-less host and a provider-mounted
9
+ * one render the same string: `defaults[key]` (the pack value's stand-in here)
10
+ * -> `defaultValue` -> the key.
7
11
  *
8
12
  * @param defaults - Fallback English translations keyed by i18n key
9
13
  * @param testKey - A key to test if i18n is properly configured (must be in defaults)
10
14
  */
11
15
  import { useObjectTranslation } from './provider';
16
+ /**
17
+ * The i18next option that names the string to use when the lookup misses.
18
+ *
19
+ * It is a LOOKUP CONTROL, not interpolation data (objectui#3865): it chooses
20
+ * which string is rendered, so it must never also be spliced into a
21
+ * `{{defaultValue}}` hole in the string it chose — or in any other.
22
+ */
23
+ const DEFAULT_VALUE_OPTION = 'defaultValue';
12
24
  export function createSafeTranslation(defaults, testKey) {
13
25
  // Factory-level fallback: one stable reference per defaults map, so
14
26
  // downstream useMemo/useCallback deps don't invalidate every render in
15
27
  // the no-translations case.
16
28
  const fallbackT = (key, options) => {
17
- let value = defaults[key] || key;
29
+ // Lookup order: defaults table -> inline `defaultValue` -> the key itself
30
+ // (objectui#3865). The table is the pack value's stand-in on this path, so
31
+ // it takes the pack's position in i18next's own order — verified against a
32
+ // real i18next 26.3.6 instance configured the way `createI18n` configures
33
+ // it: `t('anchor', { defaultValue: 'INLINE' })` is the pack's `'Anchor
34
+ // value'`, `t('missing', { defaultValue: 'INLINE' })` is `'INLINE'`, and
35
+ // `t('missing')` is `'missing'`.
36
+ //
37
+ // Before this, the inline default was DEAD on both paths for every
38
+ // component behind this factory: the provider path never reaches it
39
+ // (the pack value always wins), and this path did not read it at all —
40
+ // it rendered the raw key and then treated `defaultValue` as one more
41
+ // interpolation variable. A census over all 26 `createSafeTranslation`
42
+ // hooks measured 27 keys whose table lacks them, 21 of those carrying an
43
+ // inline default that used to be dropped (objectui#3865; the fact is
44
+ // recorded on objectui#3810, whose option C rests on it).
45
+ //
46
+ // Non-strings are ignored rather than coerced — this function returns a
47
+ // `string`, and i18next itself declines a `null` default. `||` (not `??`)
48
+ // keeps the whole chain consistent with the pre-existing `defaults[key] ||`
49
+ // step: an empty string at any position falls through to the next.
50
+ const inlineDefault = options?.[DEFAULT_VALUE_OPTION];
51
+ let value = defaults[key] || (typeof inlineDefault === 'string' ? inlineDefault : '') || key;
18
52
  if (options) {
19
53
  for (const [k, v] of Object.entries(options)) {
20
- value = value.replace(`{{${k}}}`, String(v));
54
+ // Reserved: see DEFAULT_VALUE_OPTION. Skipped whatever its type, so an
55
+ // ignored non-string default cannot re-enter through this loop.
56
+ //
57
+ // This is the one deliberate divergence from i18next on this path, and
58
+ // it is unreachable with today's strings: i18next passes the whole
59
+ // options object to its interpolator, so it WOULD render
60
+ // `'Fallback: {{defaultValue}}'` as `'Fallback: INLINE'` (measured on
61
+ // 26.3.6) — but no value in this repo spells that hole (zero hits for
62
+ // `{{defaultValue` across packages/apps/examples), and splicing a
63
+ // fallback string into a hole named after itself has no sensible
64
+ // reading. The alternative — letting a call site's fallback text leak
65
+ // into an unrelated table value — is the worse of the two.
66
+ if (k === DEFAULT_VALUE_OPTION)
67
+ continue;
68
+ // `split(needle).join(value)` — deliberately not `replace`, and not
69
+ // `replaceAll` either (objectui#3418). This path must agree with
70
+ // i18next, which serves the *provider* path; any divergence is a
71
+ // silent fork that only shows up on provider-less hosts, where we are
72
+ // least likely to see it:
73
+ // 1. `replace` with a string needle substitutes only the FIRST
74
+ // occurrence. i18next substitutes every one, so a sentence that
75
+ // repeats a placeholder ("Selected {{count}} of {{count}} items"
76
+ // — natural in many locales, and often required by RTL /
77
+ // agglutinative word order) leaked literal braces to users.
78
+ // 2. `replace` AND `replaceAll` both interpret `$&`, `` $` ``, `$'`
79
+ // and `$$` in the *replacement* string. i18next does not. Values
80
+ // here are runtime data — record labels, search terms — so this
81
+ // one is reachable today, unlike (1).
82
+ // split/join is literal on both sides, which is exactly i18next's
83
+ // behaviour, and needs no regex escaping of the placeholder name.
84
+ value = value.split(`{{${k}}}`).join(String(v));
21
85
  }
22
86
  }
23
87
  return value;
@@ -1,3 +1,4 @@
1
1
  export { formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, type DateFormatOptions, type CurrencyFormatOptions, type NumberFormatOptions, } from './formatting';
2
2
  export { resolvePlural, formatDateSpec, formatNumberSpec, applyLocaleConfig, type SpecPluralRule, type SpecDateFormat, type SpecNumberFormat, type SpecLocaleConfig, } from './spec-formatters';
3
3
  export { isSpecTranslationData, transformSpecTranslations, type SpecTranslationData, } from './spec-translations';
4
+ export { formatDisplayNumber, shouldGroupDisplayNumber, type DisplayNumberFormatOptions, } from './number-display';
@@ -1,3 +1,4 @@
1
1
  export { formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, } from './formatting';
2
2
  export { resolvePlural, formatDateSpec, formatNumberSpec, applyLocaleConfig, } from './spec-formatters';
3
3
  export { isSpecTranslationData, transformSpecTranslations, } from './spec-translations';
4
+ export { formatDisplayNumber, shouldGroupDisplayNumber, } from './number-display';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * RE-EXPORT ONLY — the implementation moved to `@object-ui/core`
10
+ * (`utils/number-display.ts`) in objectui#4576. Read it there; nothing is
11
+ * redeclared here, so there is no second copy to drift.
12
+ *
13
+ * Why it moved: `@object-ui/core`'s `dataset-format.ts` needs this exact
14
+ * policy, and it could not import it while it lived here — `core` is the
15
+ * React-free engine and this package depends on `i18next`/`react-i18next` and
16
+ * peer-depends on React. So `formatMeasure` kept a parallel `Intl`
17
+ * implementation, and the two drifted apart in the one place `Intl` and a
18
+ * hand-built string disagree: a German session read `1.234,5 %` from a list
19
+ * cell beside `1.234,5%` from a dashboard measure (objectui#4576). The function
20
+ * is pure, so the boundary was never a property of the code — only of where the
21
+ * code sat. Moving it DOWN removed the obstacle; this package gained a
22
+ * dependency on `@object-ui/core` (no cycle: `core` imports nothing from here).
23
+ *
24
+ * This file survives as a re-export rather than being deleted so that BOTH
25
+ * historical import paths keep working byte-compatibly — the package entry
26
+ * (`@object-ui/i18n`, used by `fields`, `components` and `plugin-dashboard`)
27
+ * and the relative `../utils/number-display` this package's own suite uses.
28
+ * `number-display.reexport-identity.test.ts` pins that the two paths resolve to
29
+ * the SAME function object and the same type, so a future edit cannot quietly
30
+ * reintroduce a copy.
31
+ */
32
+ export { formatDisplayNumber, shouldGroupDisplayNumber, type DisplayNumberFormatOptions, } from '@object-ui/core';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * RE-EXPORT ONLY — the implementation moved to `@object-ui/core`
10
+ * (`utils/number-display.ts`) in objectui#4576. Read it there; nothing is
11
+ * redeclared here, so there is no second copy to drift.
12
+ *
13
+ * Why it moved: `@object-ui/core`'s `dataset-format.ts` needs this exact
14
+ * policy, and it could not import it while it lived here — `core` is the
15
+ * React-free engine and this package depends on `i18next`/`react-i18next` and
16
+ * peer-depends on React. So `formatMeasure` kept a parallel `Intl`
17
+ * implementation, and the two drifted apart in the one place `Intl` and a
18
+ * hand-built string disagree: a German session read `1.234,5 %` from a list
19
+ * cell beside `1.234,5%` from a dashboard measure (objectui#4576). The function
20
+ * is pure, so the boundary was never a property of the code — only of where the
21
+ * code sat. Moving it DOWN removed the obstacle; this package gained a
22
+ * dependency on `@object-ui/core` (no cycle: `core` imports nothing from here).
23
+ *
24
+ * This file survives as a re-export rather than being deleted so that BOTH
25
+ * historical import paths keep working byte-compatibly — the package entry
26
+ * (`@object-ui/i18n`, used by `fields`, `components` and `plugin-dashboard`)
27
+ * and the relative `../utils/number-display` this package's own suite uses.
28
+ * `number-display.reexport-identity.test.ts` pins that the two paths resolve to
29
+ * the SAME function object and the same type, so a future edit cannot quietly
30
+ * reintroduce a copy.
31
+ */
32
+ export { formatDisplayNumber, shouldGroupDisplayNumber, } from '@object-ui/core';
@@ -6,18 +6,33 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  /**
9
- * @object-ui/i18n - Spec-aligned i18n utilities
9
+ * @object-ui/i18n - locale formatting helpers
10
10
  *
11
- * Runtime consumers for @objectstack/spec v2.0.7 i18n types:
12
- * - PluralRuleSchema → resolvePlural()
13
- * - DateFormatSchema → formatDateSpec()
14
- * - NumberFormatSchema → formatNumberSpec()
15
- * - LocaleConfigSchema → applyLocaleConfig()
11
+ * ## Provenance of the four shapes below (objectui#4597)
12
+ *
13
+ * They were authored against `@objectstack/spec/ui`, which exported
14
+ * `PluralRuleSchema`, `DateFormatSchema`, `NumberFormatSchema` and
15
+ * `LocaleConfigSchema` up to and including 17.0.0-rc.5. The pinned
16
+ * 17.0.0-rc.6 removed all four under ADR-0049 enforce-or-remove
17
+ * (objectstack#5055, maintainer ruling 2026-08-06): they carried no key on any
18
+ * authorable shape and nothing ever parsed them, so the vocabulary was retired
19
+ * rather than given a door. The key sets here were faithful to the retired
20
+ * schemas — this is a retirement, not a drift.
21
+ *
22
+ * Nothing in the pinned protocol models locale formatting today, so the
23
+ * interfaces below are declarations this package owns outright, not a view onto
24
+ * a protocol type. The retirement note records that localisation returns as
25
+ * authorable metadata through a new ADR — "the formatter that reads a
26
+ * LocaleConfig first, the vocabulary second" — so re-derive these at that point
27
+ * instead of assuming they still track anything upstream.
16
28
  *
17
29
  * @module spec-formatters
18
30
  */
19
31
  /**
20
- * Spec-aligned PluralRule (mirrors @objectstack/spec PluralRuleSchema).
32
+ * Plural forms for a single translation key, in CLDR categories.
33
+ *
34
+ * Local shape — authored against the protocol's `PluralRuleSchema`, retired in
35
+ * 17.0.0-rc.6 (see the module doc), so there is nothing upstream to derive from.
21
36
  */
22
37
  export interface SpecPluralRule {
23
38
  /** Translation key */
@@ -54,7 +69,10 @@ export interface SpecPluralRule {
54
69
  */
55
70
  export declare function resolvePlural(rule: SpecPluralRule, count: number, locale?: string): string;
56
71
  /**
57
- * Spec-aligned DateFormat (mirrors @objectstack/spec DateFormatSchema).
72
+ * Date/time formatting options passed through to `Intl.DateTimeFormat`.
73
+ *
74
+ * Local shape — authored against the protocol's `DateFormatSchema`, retired in
75
+ * 17.0.0-rc.6 (see the module doc), so there is nothing upstream to derive from.
58
76
  */
59
77
  export interface SpecDateFormat {
60
78
  dateStyle?: 'full' | 'long' | 'medium' | 'short';
@@ -63,7 +81,7 @@ export interface SpecDateFormat {
63
81
  hour12?: boolean;
64
82
  }
65
83
  /**
66
- * Format a date using @objectstack/spec DateFormatSchema configuration.
84
+ * Format a date using a {@link SpecDateFormat} configuration.
67
85
  *
68
86
  * @example
69
87
  * ```ts
@@ -76,7 +94,11 @@ export interface SpecDateFormat {
76
94
  */
77
95
  export declare function formatDateSpec(date: Date | string | number, format: SpecDateFormat, locale?: string): string;
78
96
  /**
79
- * Spec-aligned NumberFormat (mirrors @objectstack/spec NumberFormatSchema).
97
+ * Number formatting options passed through to `Intl.NumberFormat`.
98
+ *
99
+ * Local shape — authored against the protocol's `NumberFormatSchema`, retired
100
+ * in 17.0.0-rc.6 (see the module doc), so there is nothing upstream to derive
101
+ * from.
80
102
  */
81
103
  export interface SpecNumberFormat {
82
104
  style?: 'currency' | 'percent' | 'decimal' | 'unit';
@@ -87,7 +109,7 @@ export interface SpecNumberFormat {
87
109
  useGrouping?: boolean;
88
110
  }
89
111
  /**
90
- * Format a number using @objectstack/spec NumberFormatSchema configuration.
112
+ * Format a number using a {@link SpecNumberFormat} configuration.
91
113
  *
92
114
  * @example
93
115
  * ```ts
@@ -100,7 +122,11 @@ export interface SpecNumberFormat {
100
122
  */
101
123
  export declare function formatNumberSpec(value: number, format: SpecNumberFormat, locale?: string): string;
102
124
  /**
103
- * Spec-aligned LocaleConfig (mirrors @objectstack/spec LocaleConfigSchema).
125
+ * One locale's formatting defaults, resolved by `applyLocaleConfig()`.
126
+ *
127
+ * Local shape — authored against the protocol's `LocaleConfigSchema`, retired
128
+ * in 17.0.0-rc.6 (see the module doc), so there is nothing upstream to derive
129
+ * from.
104
130
  */
105
131
  export interface SpecLocaleConfig {
106
132
  /** BCP 47 language code (e.g., 'en-US', 'zh-CN') */
@@ -115,7 +141,7 @@ export interface SpecLocaleConfig {
115
141
  dateFormat?: SpecDateFormat;
116
142
  }
117
143
  /**
118
- * Apply a LocaleConfigSchema to configure i18n formatting defaults.
144
+ * Apply a {@link SpecLocaleConfig} to configure i18n formatting defaults.
119
145
  * Returns resolved formatting functions bound to the locale config.
120
146
  *
121
147
  * @example
@@ -36,7 +36,7 @@ export function resolvePlural(rule, count, locale = 'en') {
36
36
  return template.replace(/\{count\}/g, String(count));
37
37
  }
38
38
  /**
39
- * Format a date using @objectstack/spec DateFormatSchema configuration.
39
+ * Format a date using a {@link SpecDateFormat} configuration.
40
40
  *
41
41
  * @example
42
42
  * ```ts
@@ -63,7 +63,7 @@ export function formatDateSpec(date, format, locale = 'en') {
63
63
  return new Intl.DateTimeFormat(locale, options).format(d);
64
64
  }
65
65
  /**
66
- * Format a number using @objectstack/spec NumberFormatSchema configuration.
66
+ * Format a number using a {@link SpecNumberFormat} configuration.
67
67
  *
68
68
  * @example
69
69
  * ```ts
@@ -91,7 +91,7 @@ export function formatNumberSpec(value, format, locale = 'en') {
91
91
  return new Intl.NumberFormat(locale, options).format(value);
92
92
  }
93
93
  /**
94
- * Apply a LocaleConfigSchema to configure i18n formatting defaults.
94
+ * Apply a {@link SpecLocaleConfig} to configure i18n formatting defaults.
95
95
  * Returns resolved formatting functions bound to the locale config.
96
96
  *
97
97
  * @example
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/i18n",
3
- "version": "17.2.0",
3
+ "version": "17.5.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",
@@ -31,13 +31,14 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "i18next": "^26.3.6",
34
- "react-i18next": "^17.0.11"
34
+ "react-i18next": "^17.0.11",
35
+ "@object-ui/core": "17.5.0"
35
36
  },
36
37
  "peerDependencies": {
37
38
  "react": "^18.0.0 || ^19.0.0"
38
39
  },
39
40
  "devDependencies": {
40
- "@types/react": "19.2.17",
41
+ "@types/react": "19.2.18",
41
42
  "react": "19.2.8",
42
43
  "typescript": "^6.0.3",
43
44
  "vitest": "^4.1.10"
@@ -68,7 +69,7 @@
68
69
  "scripts": {
69
70
  "build": "tsc",
70
71
  "test": "vitest run",
71
- "type-check": "tsc --noEmit",
72
+ "type-check": "tsc --noEmit && tsc -p tsconfig.test.json",
72
73
  "lint": "eslint ."
73
74
  }
74
75
  }