@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.
- package/CHANGELOG.md +1817 -1
- package/README.md +26 -13
- package/dist/index.d.ts +3 -1
- package/dist/index.js +14 -1
- package/dist/locales/ar.d.ts +550 -107
- package/dist/locales/ar.js +646 -156
- package/dist/locales/de.d.ts +550 -107
- package/dist/locales/de.js +661 -178
- package/dist/locales/en.d.ts +586 -143
- package/dist/locales/en.js +851 -143
- package/dist/locales/es.d.ts +550 -107
- package/dist/locales/es.js +647 -160
- package/dist/locales/fr.d.ts +550 -107
- package/dist/locales/fr.js +640 -155
- package/dist/locales/index.d.ts +5587 -1157
- package/dist/locales/ja.d.ts +550 -107
- package/dist/locales/ja.js +632 -149
- package/dist/locales/ko.d.ts +550 -107
- package/dist/locales/ko.js +638 -156
- package/dist/locales/pt.d.ts +550 -107
- package/dist/locales/pt.js +639 -157
- package/dist/locales/ru.d.ts +550 -107
- package/dist/locales/ru.js +650 -156
- package/dist/locales/zh.d.ts +588 -145
- package/dist/locales/zh.js +629 -145
- package/dist/pickLocalized.d.ts +21 -0
- package/dist/pickLocalized.js +40 -7
- package/dist/provider.d.ts +125 -1
- package/dist/provider.js +380 -4
- package/dist/useDisplayLocale.d.ts +49 -0
- package/dist/useDisplayLocale.js +55 -0
- package/dist/useObjectLabel.d.ts +8 -2
- package/dist/useObjectLabel.js +28 -8
- package/dist/useSafeTranslation.js +67 -3
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/number-display.d.ts +32 -0
- package/dist/utils/number-display.js +32 -0
- package/dist/utils/spec-formatters.d.ts +39 -13
- package/dist/utils/spec-formatters.js +3 -3
- 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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/utils/index.js
CHANGED
|
@@ -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 -
|
|
9
|
+
* @object-ui/i18n - locale formatting helpers
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* -
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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 @
|
|
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
|
-
*
|
|
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 @
|
|
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
|
-
*
|
|
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
|
|
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 @
|
|
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 @
|
|
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
|
|
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.
|
|
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.
|
|
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
|
}
|