Skip to content

Commit d2d9cde

Browse files
Copilothotlong
andcommitted
feat(types): add resolveI18nLabel protocol utility and I18nLabel type re-export
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 4a0ce33 commit d2d9cde

File tree

3 files changed

+91
-0
lines changed

3 files changed

+91
-0
lines changed

ROADMAP.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,4 +1251,12 @@ ObjectQL implements the **data layer compiler** portion of the ObjectStack proto
12511251

12521252
---
12531253

1254+
## Cross-Repo Notes
1255+
1256+
| Target Repo | Issue | Description |
1257+
|---|---|---|
1258+
| `objectstack-ai/objectui` | `@object-ui/plugin-designer` build failure | Published `@object-ui/react@3.0.3` is missing `resolveI18nLabel` export (source has it, npm dist does not). Either republish `@object-ui/react` or update `plugin-designer` to import `resolveI18nLabel` from `@objectql/types` which now exports it ([PR #411](https://github.com/objectstack-ai/objectql/pull/411)). |
1259+
1260+
---
1261+
12541262
> **Historical Reference:** The core refactoring design document is archived at `docs/DESIGN_CORE_REFACTOR.md` (Status: ✅ Completed — [PR #373](https://github.com/objectstack-ai/objectql/pull/373)).

packages/foundation/types/src/util.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,34 @@
1010
*/
1111

1212
import { ObjectConfig, FieldConfig, FieldType, IntrospectedSchema } from './index';
13+
import { UI } from '@objectstack/spec';
14+
import { z } from 'zod';
15+
16+
/**
17+
* Re-export I18nLabel protocol type from @objectstack/spec.
18+
*
19+
* I18nLabel is a union of plain string or structured i18n object { key, defaultValue?, params? }.
20+
* Used across the ecosystem for internationalized labels.
21+
*/
22+
export type I18nLabel = z.infer<typeof UI.I18nLabelSchema>;
23+
24+
/**
25+
* Resolves an I18nLabel to a plain string.
26+
*
27+
* I18nLabel can be either a string or an object { key, defaultValue?, params? }.
28+
* When it's an object, returns the defaultValue or the key as fallback.
29+
* When it's undefined or null, returns undefined.
30+
*
31+
* @param label - The I18nLabel value to resolve
32+
* @returns The resolved plain string, or undefined if the input is nullish
33+
*/
34+
export function resolveI18nLabel(
35+
label: string | { key: string; defaultValue?: string; params?: Record<string, unknown> } | undefined | null
36+
): string | undefined {
37+
if (label === undefined || label === null) return undefined;
38+
if (typeof label === 'string') return label;
39+
return label.defaultValue || label.key;
40+
}
1341

1442
export function toTitleCase(str: string): string {
1543
return str
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* ObjectQL
3+
* Copyright (c) 2026-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+
import { resolveI18nLabel } from '../src/util';
10+
11+
describe('resolveI18nLabel', () => {
12+
it('should return undefined for undefined input', () => {
13+
expect(resolveI18nLabel(undefined)).toBeUndefined();
14+
});
15+
16+
it('should return undefined for null input', () => {
17+
expect(resolveI18nLabel(null)).toBeUndefined();
18+
});
19+
20+
it('should return the string as-is for plain string input', () => {
21+
expect(resolveI18nLabel('Hello')).toBe('Hello');
22+
});
23+
24+
it('should return empty string for empty string input', () => {
25+
expect(resolveI18nLabel('')).toBe('');
26+
});
27+
28+
it('should return defaultValue from i18n object when available', () => {
29+
expect(resolveI18nLabel({
30+
key: 'views.task_list.label',
31+
defaultValue: 'Task List',
32+
})).toBe('Task List');
33+
});
34+
35+
it('should return key as fallback when defaultValue is not provided', () => {
36+
expect(resolveI18nLabel({
37+
key: 'views.task_list.label',
38+
})).toBe('views.task_list.label');
39+
});
40+
41+
it('should return key as fallback when defaultValue is empty string', () => {
42+
expect(resolveI18nLabel({
43+
key: 'views.task_list.label',
44+
defaultValue: '',
45+
})).toBe('views.task_list.label');
46+
});
47+
48+
it('should handle i18n object with params', () => {
49+
expect(resolveI18nLabel({
50+
key: 'items.count',
51+
defaultValue: '{count} items',
52+
params: { count: 5 },
53+
})).toBe('{count} items');
54+
});
55+
});

0 commit comments

Comments
 (0)