@object-ui/i18n 16.1.0 → 17.2.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 +1197 -0
- package/dist/locales/ar.d.ts +841 -2
- package/dist/locales/ar.js +846 -5
- package/dist/locales/de.d.ts +839 -2
- package/dist/locales/de.js +844 -5
- package/dist/locales/en.d.ts +396 -6
- package/dist/locales/en.js +413 -8
- package/dist/locales/es.d.ts +839 -2
- package/dist/locales/es.js +844 -5
- package/dist/locales/fr.d.ts +841 -2
- package/dist/locales/fr.js +846 -5
- package/dist/locales/index.d.ts +7433 -27
- package/dist/locales/ja.d.ts +839 -2
- package/dist/locales/ja.js +844 -5
- package/dist/locales/ko.d.ts +839 -2
- package/dist/locales/ko.js +844 -5
- package/dist/locales/pt.d.ts +841 -2
- package/dist/locales/pt.js +846 -5
- package/dist/locales/ru.d.ts +841 -2
- package/dist/locales/ru.js +846 -5
- package/dist/locales/zh.d.ts +317 -5
- package/dist/locales/zh.js +319 -5
- package/dist/useObjectLabel.d.ts +24 -12
- package/dist/useObjectLabel.js +71 -45
- package/dist/useSafeTranslation.d.ts +2 -0
- package/dist/useSafeTranslation.js +7 -2
- package/package.json +3 -3
package/dist/useObjectLabel.js
CHANGED
|
@@ -57,6 +57,11 @@ export function useObjectLabel() {
|
|
|
57
57
|
* Returns top-level keys (outside built-in Object UI keys) that contain
|
|
58
58
|
* an `objects`, `fields`, or `apps` sub-key — e.g. "crm" when resources
|
|
59
59
|
* include crm.objects.* or crm.apps.*.
|
|
60
|
+
*
|
|
61
|
+
* `globalActions` is included so an app bundle whose only translated scope
|
|
62
|
+
* is global actions (no object/field entries) is still discovered — its
|
|
63
|
+
* `globalActions.<action>.*` overlays would otherwise be unreachable
|
|
64
|
+
* (objectui#3372).
|
|
60
65
|
*/
|
|
61
66
|
const getAppNamespaces = () => {
|
|
62
67
|
if (!i18n || typeof i18n.getResourceBundle !== 'function')
|
|
@@ -67,7 +72,8 @@ export function useObjectLabel() {
|
|
|
67
72
|
return [];
|
|
68
73
|
return Object.keys(bundle).filter((key) => !BUILTIN_KEYS.has(key) && bundle[key] && typeof bundle[key] === 'object'
|
|
69
74
|
&& (bundle[key].objects || bundle[key].fields || bundle[key].apps
|
|
70
|
-
|| bundle[key].dashboards || bundle[key].pages || bundle[key].reports
|
|
75
|
+
|| bundle[key].dashboards || bundle[key].pages || bundle[key].reports
|
|
76
|
+
|| bundle[key].globalActions));
|
|
71
77
|
};
|
|
72
78
|
/**
|
|
73
79
|
* Strip an ObjectStack namespace prefix (e.g. `crm__lead` → `lead`) so that
|
|
@@ -111,6 +117,30 @@ export function useObjectLabel() {
|
|
|
111
117
|
? [`objects.${objectName}.${tail}`, `objects.${base}.${tail}`]
|
|
112
118
|
: [`objects.${objectName}.${tail}`];
|
|
113
119
|
};
|
|
120
|
+
/**
|
|
121
|
+
* Build suffix candidates for an action-scoped key, mirroring the canonical
|
|
122
|
+
* `@objectstack/spec` resolver (`lookupActionField` in `system/i18n-resolver`).
|
|
123
|
+
*
|
|
124
|
+
* When the action is object-scoped, the object key
|
|
125
|
+
* (`objects.<obj>._actions.<action>.<tail>`) wins, but the global key
|
|
126
|
+
* (`globalActions.<action>.<tail>`) is appended as a fallback so a
|
|
127
|
+
* **globalAction surfaced on an object's action bar** still picks up its
|
|
128
|
+
* `globalActions.<action>.*` overlay when no object-scoped translation
|
|
129
|
+
* exists (objectui#3372). Without this fallback, a globalAction rendered on
|
|
130
|
+
* a record-detail action bar — where the caller passes `objectDef.name` for
|
|
131
|
+
* every action — misses `objects.<obj>._actions.<action>.label` and leaks the
|
|
132
|
+
* English metadata literal.
|
|
133
|
+
*
|
|
134
|
+
* Object-less actions (`objectName` omitted) resolve straight to the global
|
|
135
|
+
* namespace, as before. Object precedence is preserved: the global key is
|
|
136
|
+
* only consulted after every object-scoped candidate misses.
|
|
137
|
+
*/
|
|
138
|
+
const actionSuffixes = (objectName, actionName, tail) => {
|
|
139
|
+
const globalSuffix = `globalActions.${actionName}.${tail}`;
|
|
140
|
+
return objectName
|
|
141
|
+
? [...objectSuffixes(objectName, `_actions.${actionName}.${tail}`), globalSuffix]
|
|
142
|
+
: [globalSuffix];
|
|
143
|
+
};
|
|
114
144
|
const fieldSuffixes = (objectName, fieldName) => {
|
|
115
145
|
const base = stripNamespace(objectName);
|
|
116
146
|
return base !== objectName
|
|
@@ -155,6 +185,23 @@ export function useObjectLabel() {
|
|
|
155
185
|
? [`reports.${reportName}.${tail}`, `reports.${base}.${tail}`]
|
|
156
186
|
: [`reports.${reportName}.${tail}`];
|
|
157
187
|
};
|
|
188
|
+
/**
|
|
189
|
+
* Build suffix candidates for a list-view scoped key.
|
|
190
|
+
*
|
|
191
|
+
* The runtime uses qualified view ids (`<objectName>.<viewName>`) in URLs
|
|
192
|
+
* and metadata records, while translation bundles are keyed by the authored
|
|
193
|
+
* view name under `_views`. Resolve the unqualified name first so every
|
|
194
|
+
* surface can pass its canonical id without leaking the metadata fallback.
|
|
195
|
+
*/
|
|
196
|
+
const viewSuffixes = (objectName, viewName, tail) => {
|
|
197
|
+
const objectNames = [objectName, stripNamespace(objectName)];
|
|
198
|
+
const matchedPrefix = objectNames
|
|
199
|
+
.map((name) => `${name}.`)
|
|
200
|
+
.find((prefix) => viewName.startsWith(prefix));
|
|
201
|
+
const shortViewName = matchedPrefix ? viewName.slice(matchedPrefix.length) : viewName;
|
|
202
|
+
const viewNames = shortViewName === viewName ? [viewName] : [shortViewName, viewName];
|
|
203
|
+
return viewNames.flatMap((name) => objectSuffixes(objectName, `_views.${name}.${tail}`));
|
|
204
|
+
};
|
|
158
205
|
return {
|
|
159
206
|
/**
|
|
160
207
|
* Resolve translated object label, falling back to objectDef.label.
|
|
@@ -264,14 +311,14 @@ export function useObjectLabel() {
|
|
|
264
311
|
* Resolve translated list-view label.
|
|
265
312
|
* Convention (per @objectstack/spec): `{ns}.objects.{objectName}._views.{viewName}.label`.
|
|
266
313
|
*/
|
|
267
|
-
viewLabel: (objectName, viewName, fallback) => resolve(
|
|
314
|
+
viewLabel: (objectName, viewName, fallback) => resolve(viewSuffixes(objectName, viewName, 'label'), fallback),
|
|
268
315
|
/**
|
|
269
316
|
* Resolve translated list-view description.
|
|
270
317
|
* Convention: `{ns}.objects.{objectName}._views.{viewName}.description`.
|
|
271
318
|
*/
|
|
272
319
|
viewDescription: (objectName, viewName, fallback) => {
|
|
273
320
|
const fb = fallback ?? '';
|
|
274
|
-
const resolved = resolve(
|
|
321
|
+
const resolved = resolve(viewSuffixes(objectName, viewName, 'description'), fb);
|
|
275
322
|
return resolved || undefined;
|
|
276
323
|
},
|
|
277
324
|
/**
|
|
@@ -284,10 +331,10 @@ export function useObjectLabel() {
|
|
|
284
331
|
if (!fallback)
|
|
285
332
|
return undefined;
|
|
286
333
|
const title = fallback.title
|
|
287
|
-
? resolve(
|
|
334
|
+
? resolve(viewSuffixes(objectName, viewName, 'emptyState.title'), fallback.title)
|
|
288
335
|
: fallback.title;
|
|
289
336
|
const message = fallback.message
|
|
290
|
-
? resolve(
|
|
337
|
+
? resolve(viewSuffixes(objectName, viewName, 'emptyState.message'), fallback.message)
|
|
291
338
|
: fallback.message;
|
|
292
339
|
return { ...fallback, title, message };
|
|
293
340
|
},
|
|
@@ -298,38 +345,32 @@ export function useObjectLabel() {
|
|
|
298
345
|
sectionLabel: (objectName, sectionName, fallback) => resolve(objectSuffixes(objectName, `_sections.${sectionName}.label`), fallback),
|
|
299
346
|
/**
|
|
300
347
|
* Resolve translated action label.
|
|
301
|
-
* Convention: `{ns}.objects.{objectName}._actions.{actionName}.label
|
|
302
|
-
*
|
|
348
|
+
* Convention: `{ns}.objects.{objectName}._actions.{actionName}.label`,
|
|
349
|
+
* falling back to `{ns}.globalActions.{actionName}.label` — both when
|
|
350
|
+
* objectName is omitted AND when the object-scoped key misses (so a
|
|
351
|
+
* globalAction surfaced on a record-detail action bar still resolves;
|
|
352
|
+
* objectui#3372).
|
|
303
353
|
*/
|
|
304
|
-
actionLabel: (objectName, actionName, fallback) =>
|
|
305
|
-
if (objectName) {
|
|
306
|
-
return resolve(objectSuffixes(objectName, `_actions.${actionName}.label`), fallback);
|
|
307
|
-
}
|
|
308
|
-
return resolve(`globalActions.${actionName}.label`, fallback);
|
|
309
|
-
},
|
|
354
|
+
actionLabel: (objectName, actionName, fallback) => resolve(actionSuffixes(objectName, actionName, 'label'), fallback),
|
|
310
355
|
/**
|
|
311
356
|
* Resolve translated action confirmation prompt.
|
|
312
|
-
* Convention: `{ns}.objects.{objectName}._actions.{actionName}.confirmText
|
|
357
|
+
* Convention: `{ns}.objects.{objectName}._actions.{actionName}.confirmText`,
|
|
358
|
+
* falling back to `{ns}.globalActions.{actionName}.confirmText`.
|
|
313
359
|
* Returns undefined when no translation and no fallback exist.
|
|
314
360
|
*/
|
|
315
361
|
actionConfirm: (objectName, actionName, fallback) => {
|
|
316
362
|
const fb = fallback ?? '';
|
|
317
|
-
const
|
|
318
|
-
? objectSuffixes(objectName, `_actions.${actionName}.confirmText`)
|
|
319
|
-
: `globalActions.${actionName}.confirmText`;
|
|
320
|
-
const resolved = resolve(suffixes, fb);
|
|
363
|
+
const resolved = resolve(actionSuffixes(objectName, actionName, 'confirmText'), fb);
|
|
321
364
|
return resolved || undefined;
|
|
322
365
|
},
|
|
323
366
|
/**
|
|
324
367
|
* Resolve translated action success message.
|
|
325
|
-
* Convention: `{ns}.objects.{objectName}._actions.{actionName}.successMessage
|
|
368
|
+
* Convention: `{ns}.objects.{objectName}._actions.{actionName}.successMessage`,
|
|
369
|
+
* falling back to `{ns}.globalActions.{actionName}.successMessage`.
|
|
326
370
|
*/
|
|
327
371
|
actionSuccess: (objectName, actionName, fallback) => {
|
|
328
372
|
const fb = fallback ?? '';
|
|
329
|
-
const
|
|
330
|
-
? objectSuffixes(objectName, `_actions.${actionName}.successMessage`)
|
|
331
|
-
: `globalActions.${actionName}.successMessage`;
|
|
332
|
-
const resolved = resolve(suffixes, fb);
|
|
373
|
+
const resolved = resolve(actionSuffixes(objectName, actionName, 'successMessage'), fb);
|
|
333
374
|
return resolved || undefined;
|
|
334
375
|
},
|
|
335
376
|
/**
|
|
@@ -343,18 +384,16 @@ export function useObjectLabel() {
|
|
|
343
384
|
const fb = fallback ?? '';
|
|
344
385
|
if (!actionName)
|
|
345
386
|
return fb || undefined;
|
|
346
|
-
const
|
|
347
|
-
? objectSuffixes(objectName, `_actions.${actionName}.description`)
|
|
348
|
-
: `globalActions.${actionName}.description`;
|
|
349
|
-
const resolved = resolve(suffixes, fb);
|
|
387
|
+
const resolved = resolve(actionSuffixes(objectName, actionName, 'description'), fb);
|
|
350
388
|
return resolved || undefined;
|
|
351
389
|
},
|
|
352
390
|
/**
|
|
353
391
|
* Resolve a translated copy of an action's post-success RESULT DIALOG
|
|
354
392
|
* (`title` / `description` / `acknowledge` + per-field labels).
|
|
355
393
|
* Convention: `{ns}.objects.{objectName}._actions.{actionName}.resultDialog.*`,
|
|
356
|
-
* falling back to `{ns}.globalActions.{actionName}.resultDialog.*` when
|
|
357
|
-
* objectName is omitted, then to the
|
|
394
|
+
* falling back to `{ns}.globalActions.{actionName}.resultDialog.*` (when
|
|
395
|
+
* objectName is omitted OR the object-scoped key misses), then to the
|
|
396
|
+
* metadata's literal strings.
|
|
358
397
|
*
|
|
359
398
|
* The `fields` translation node is keyed by the LITERAL result-field path
|
|
360
399
|
* from the action metadata (may contain dots, e.g. `"user.email"`), so it
|
|
@@ -364,12 +403,7 @@ export function useObjectLabel() {
|
|
|
364
403
|
actionResultDialog: (objectName, actionName, spec) => {
|
|
365
404
|
if (!spec || !actionName)
|
|
366
405
|
return spec;
|
|
367
|
-
const suffixesFor = (attr) => {
|
|
368
|
-
const tail = `_actions.${actionName}.resultDialog.${attr}`;
|
|
369
|
-
return objectName
|
|
370
|
-
? objectSuffixes(objectName, tail)
|
|
371
|
-
: [`globalActions.${actionName}.resultDialog.${attr}`];
|
|
372
|
-
};
|
|
406
|
+
const suffixesFor = (attr) => actionSuffixes(objectName, actionName, `resultDialog.${attr}`);
|
|
373
407
|
const textFor = (attr) => {
|
|
374
408
|
const resolved = resolve(suffixesFor(attr), spec[attr] ?? '');
|
|
375
409
|
return resolved || spec[attr];
|
|
@@ -420,11 +454,7 @@ export function useObjectLabel() {
|
|
|
420
454
|
const fb = fallback ?? '';
|
|
421
455
|
if (!actionName || !paramName)
|
|
422
456
|
return fb || undefined;
|
|
423
|
-
const
|
|
424
|
-
const suffixes = objectName
|
|
425
|
-
? objectSuffixes(objectName, suffix)
|
|
426
|
-
: `globalActions.${actionName}.params.${paramName}.${attr}`;
|
|
427
|
-
const resolved = resolve(suffixes, fb);
|
|
457
|
+
const resolved = resolve(actionSuffixes(objectName, actionName, `params.${paramName}.${attr}`), fb);
|
|
428
458
|
return resolved || undefined;
|
|
429
459
|
},
|
|
430
460
|
/**
|
|
@@ -435,11 +465,7 @@ export function useObjectLabel() {
|
|
|
435
465
|
actionParamOptionLabel: (objectName, actionName, paramName, optionValue, fallback) => {
|
|
436
466
|
if (!actionName || !paramName)
|
|
437
467
|
return fallback;
|
|
438
|
-
|
|
439
|
-
const suffixes = objectName
|
|
440
|
-
? objectSuffixes(objectName, suffix)
|
|
441
|
-
: `globalActions.${actionName}.params.${paramName}.options.${optionValue}`;
|
|
442
|
-
return resolve(suffixes, fallback);
|
|
468
|
+
return resolve(actionSuffixes(objectName, actionName, `params.${paramName}.options.${optionValue}`), fallback);
|
|
443
469
|
},
|
|
444
470
|
};
|
|
445
471
|
}, [t, i18n]);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export declare function createSafeTranslation(defaults: Record<string, string>, testKey: string): () => {
|
|
2
2
|
t: (key: string, options?: Record<string, unknown>) => string;
|
|
3
|
+
language: string;
|
|
3
4
|
} | {
|
|
4
5
|
t: import("i18next").TFunction<string, undefined>;
|
|
6
|
+
language: string;
|
|
5
7
|
};
|
|
6
8
|
/**
|
|
7
9
|
* Per-call graceful translate hook for plugin renderers.
|
|
@@ -32,10 +32,15 @@ export function createSafeTranslation(defaults, testKey) {
|
|
|
32
32
|
// "translations not configured" fallback.
|
|
33
33
|
const result = useObjectTranslation();
|
|
34
34
|
const testValue = result.t(testKey);
|
|
35
|
+
// `language` is surfaced so consumers that localize dates/numbers
|
|
36
|
+
// alongside their copy (data-table, gantt) don't have to call
|
|
37
|
+
// `useObjectTranslation` a second time just to read it. With no provider
|
|
38
|
+
// it is whatever react-i18next reports, which callers may treat as
|
|
39
|
+
// "follow the runtime default".
|
|
35
40
|
if (testValue === testKey) {
|
|
36
|
-
return { t: fallbackT };
|
|
41
|
+
return { t: fallbackT, language: result.language };
|
|
37
42
|
}
|
|
38
|
-
return { t: result.t };
|
|
43
|
+
return { t: result.t, language: result.language };
|
|
39
44
|
};
|
|
40
45
|
}
|
|
41
46
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/i18n",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "17.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"i18next": "^26.3.6",
|
|
34
|
-
"react-i18next": "^17.0.
|
|
34
|
+
"react-i18next": "^17.0.11"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": "^18.0.0 || ^19.0.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/react": "19.2.17",
|
|
41
|
-
"react": "19.2.
|
|
41
|
+
"react": "19.2.8",
|
|
42
42
|
"typescript": "^6.0.3",
|
|
43
43
|
"vitest": "^4.1.10"
|
|
44
44
|
},
|