@object-ui/i18n 3.1.5 → 3.3.1
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 +6 -0
- package/README.md +21 -1
- package/dist/i18n.js +10 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/locales/ar.d.ts +260 -1
- package/dist/locales/ar.js +260 -1
- package/dist/locales/de.d.ts +260 -1
- package/dist/locales/de.js +269 -1
- package/dist/locales/en.d.ts +279 -0
- package/dist/locales/en.js +289 -0
- package/dist/locales/es.d.ts +260 -1
- package/dist/locales/es.js +260 -1
- package/dist/locales/fr.d.ts +260 -1
- package/dist/locales/fr.js +269 -1
- package/dist/locales/index.d.ts +2661 -9
- package/dist/locales/ja.d.ts +271 -1
- package/dist/locales/ja.js +271 -1
- package/dist/locales/ko.d.ts +260 -1
- package/dist/locales/ko.js +260 -1
- package/dist/locales/pt.d.ts +260 -1
- package/dist/locales/pt.js +260 -1
- package/dist/locales/ru.d.ts +271 -1
- package/dist/locales/ru.js +280 -1
- package/dist/locales/zh.d.ts +280 -1
- package/dist/locales/zh.js +290 -1
- package/dist/useObjectLabel.d.ts +46 -0
- package/dist/useObjectLabel.js +79 -12
- package/dist/useSafeTranslation.d.ts +5 -0
- package/dist/useSafeTranslation.js +46 -0
- package/package.json +31 -8
- package/.turbo/turbo-build.log +0 -4
- package/src/__tests__/i18n.test.ts +0 -228
- package/src/__tests__/provider.test.tsx +0 -117
- package/src/__tests__/spec-formatters.test.ts +0 -208
- package/src/__tests__/useObjectLabel.test.tsx +0 -131
- package/src/i18n.ts +0 -98
- package/src/index.ts +0 -78
- package/src/locales/ar.ts +0 -541
- package/src/locales/de.ts +0 -545
- package/src/locales/en.ts +0 -625
- package/src/locales/es.ts +0 -540
- package/src/locales/fr.ts +0 -545
- package/src/locales/index.ts +0 -42
- package/src/locales/ja.ts +0 -540
- package/src/locales/ko.ts +0 -540
- package/src/locales/pt.ts +0 -540
- package/src/locales/ru.ts +0 -540
- package/src/locales/zh.ts +0 -624
- package/src/provider.tsx +0 -177
- package/src/useObjectLabel.ts +0 -122
- package/src/utils/formatting.ts +0 -136
- package/src/utils/index.ts +0 -21
- package/src/utils/spec-formatters.ts +0 -220
- package/tsconfig.json +0 -12
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
resolvePlural,
|
|
4
|
-
formatDateSpec,
|
|
5
|
-
formatNumberSpec,
|
|
6
|
-
applyLocaleConfig,
|
|
7
|
-
type SpecPluralRule,
|
|
8
|
-
} from '../utils/spec-formatters';
|
|
9
|
-
|
|
10
|
-
// ============================================================================
|
|
11
|
-
// PluralRuleSchema Consumer Tests
|
|
12
|
-
// ============================================================================
|
|
13
|
-
|
|
14
|
-
describe('resolvePlural', () => {
|
|
15
|
-
const rule: SpecPluralRule = {
|
|
16
|
-
key: 'items.count',
|
|
17
|
-
zero: 'No items',
|
|
18
|
-
one: '{count} item',
|
|
19
|
-
other: '{count} items',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
it('should resolve zero form', () => {
|
|
23
|
-
// English 'other' is used for 0 by Intl.PluralRules, but 'zero' is explicitly checked
|
|
24
|
-
// In English, 0 maps to "other" category, but we fall back to rule.other
|
|
25
|
-
const result = resolvePlural(rule, 0, 'en');
|
|
26
|
-
expect(result).toBe('0 items');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should resolve one form', () => {
|
|
30
|
-
expect(resolvePlural(rule, 1, 'en')).toBe('1 item');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should resolve other form for plural', () => {
|
|
34
|
-
expect(resolvePlural(rule, 5, 'en')).toBe('5 items');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should fallback to other when form is not defined', () => {
|
|
38
|
-
const simpleRule: SpecPluralRule = {
|
|
39
|
-
key: 'items',
|
|
40
|
-
other: '{count} things',
|
|
41
|
-
};
|
|
42
|
-
expect(resolvePlural(simpleRule, 3, 'en')).toBe('3 things');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should work with different locales', () => {
|
|
46
|
-
const ruleWithTwo: SpecPluralRule = {
|
|
47
|
-
key: 'items',
|
|
48
|
-
one: '{count} element',
|
|
49
|
-
two: '{count} éléments (dual)',
|
|
50
|
-
other: '{count} éléments',
|
|
51
|
-
};
|
|
52
|
-
// Arabic has a 'two' plural category
|
|
53
|
-
expect(resolvePlural(ruleWithTwo, 2, 'ar')).toBe('2 éléments (dual)');
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// ============================================================================
|
|
58
|
-
// DateFormatSchema Consumer Tests
|
|
59
|
-
// ============================================================================
|
|
60
|
-
|
|
61
|
-
describe('formatDateSpec', () => {
|
|
62
|
-
it('should format date with dateStyle', () => {
|
|
63
|
-
const date = new Date('2026-02-11T12:00:00Z');
|
|
64
|
-
const result = formatDateSpec(date, { dateStyle: 'short' }, 'en-US');
|
|
65
|
-
expect(result).toBeTruthy();
|
|
66
|
-
expect(typeof result).toBe('string');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should return string for invalid date', () => {
|
|
70
|
-
expect(formatDateSpec('invalid-date', { dateStyle: 'medium' })).toBe('invalid-date');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should handle timeZone', () => {
|
|
74
|
-
const date = new Date('2026-02-11T12:00:00Z');
|
|
75
|
-
const result = formatDateSpec(date, {
|
|
76
|
-
dateStyle: 'medium',
|
|
77
|
-
timeZone: 'America/New_York',
|
|
78
|
-
}, 'en-US');
|
|
79
|
-
expect(result).toBeTruthy();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should handle hour12 option', () => {
|
|
83
|
-
const date = new Date('2026-02-11T15:30:00Z');
|
|
84
|
-
const result12 = formatDateSpec(date, { timeStyle: 'short', hour12: true }, 'en-US');
|
|
85
|
-
const result24 = formatDateSpec(date, { timeStyle: 'short', hour12: false }, 'en-US');
|
|
86
|
-
expect(result12).toBeTruthy();
|
|
87
|
-
expect(result24).toBeTruthy();
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// ============================================================================
|
|
92
|
-
// NumberFormatSchema Consumer Tests
|
|
93
|
-
// ============================================================================
|
|
94
|
-
|
|
95
|
-
describe('formatNumberSpec', () => {
|
|
96
|
-
it('should format decimal numbers', () => {
|
|
97
|
-
const result = formatNumberSpec(1234.56, { style: 'decimal' }, 'en-US');
|
|
98
|
-
expect(result).toContain('1,234');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should format currency', () => {
|
|
102
|
-
const result = formatNumberSpec(42.99, {
|
|
103
|
-
style: 'currency',
|
|
104
|
-
currency: 'USD',
|
|
105
|
-
}, 'en-US');
|
|
106
|
-
expect(result).toContain('$');
|
|
107
|
-
expect(result).toContain('42.99');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should format percentage', () => {
|
|
111
|
-
const result = formatNumberSpec(0.75, { style: 'percent' }, 'en-US');
|
|
112
|
-
expect(result).toContain('75');
|
|
113
|
-
expect(result).toContain('%');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should respect fraction digits', () => {
|
|
117
|
-
const result = formatNumberSpec(3.14159, {
|
|
118
|
-
style: 'decimal',
|
|
119
|
-
maximumFractionDigits: 2,
|
|
120
|
-
}, 'en-US');
|
|
121
|
-
expect(result).toBe('3.14');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should respect useGrouping', () => {
|
|
125
|
-
const withGrouping = formatNumberSpec(1234567, {
|
|
126
|
-
style: 'decimal',
|
|
127
|
-
useGrouping: true,
|
|
128
|
-
}, 'en-US');
|
|
129
|
-
const withoutGrouping = formatNumberSpec(1234567, {
|
|
130
|
-
style: 'decimal',
|
|
131
|
-
useGrouping: false,
|
|
132
|
-
}, 'en-US');
|
|
133
|
-
expect(withGrouping).toContain(',');
|
|
134
|
-
expect(withoutGrouping).not.toContain(',');
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// ============================================================================
|
|
139
|
-
// LocaleConfigSchema Consumer Tests
|
|
140
|
-
// ============================================================================
|
|
141
|
-
|
|
142
|
-
describe('applyLocaleConfig', () => {
|
|
143
|
-
it('should create bound formatting functions', () => {
|
|
144
|
-
const locale = applyLocaleConfig({
|
|
145
|
-
code: 'en-US',
|
|
146
|
-
direction: 'ltr',
|
|
147
|
-
numberFormat: { style: 'decimal', useGrouping: true },
|
|
148
|
-
dateFormat: { dateStyle: 'medium' },
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
expect(locale.code).toBe('en-US');
|
|
152
|
-
expect(locale.direction).toBe('ltr');
|
|
153
|
-
expect(locale.fallbackChain).toEqual([]);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('should format dates using configured locale', () => {
|
|
157
|
-
const locale = applyLocaleConfig({
|
|
158
|
-
code: 'en-US',
|
|
159
|
-
dateFormat: { dateStyle: 'short' },
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const result = locale.formatDate(new Date('2026-02-11'));
|
|
163
|
-
expect(result).toBeTruthy();
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should format numbers using configured locale', () => {
|
|
167
|
-
const locale = applyLocaleConfig({
|
|
168
|
-
code: 'en-US',
|
|
169
|
-
numberFormat: { style: 'decimal', maximumFractionDigits: 2 },
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
const result = locale.formatNumber(1234.5678);
|
|
173
|
-
expect(result).toBeTruthy();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('should allow overrides in formatting calls', () => {
|
|
177
|
-
const locale = applyLocaleConfig({
|
|
178
|
-
code: 'en-US',
|
|
179
|
-
numberFormat: { style: 'decimal' },
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const result = locale.formatNumber(42.99, {
|
|
183
|
-
style: 'currency',
|
|
184
|
-
currency: 'EUR',
|
|
185
|
-
});
|
|
186
|
-
expect(result).toContain('€');
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('should resolve plurals with configured locale', () => {
|
|
190
|
-
const locale = applyLocaleConfig({
|
|
191
|
-
code: 'en',
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
const rule: SpecPluralRule = {
|
|
195
|
-
key: 'items',
|
|
196
|
-
one: '{count} item',
|
|
197
|
-
other: '{count} items',
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
expect(locale.resolvePlural(rule, 1)).toBe('1 item');
|
|
201
|
-
expect(locale.resolvePlural(rule, 5)).toBe('5 items');
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('should default direction to ltr', () => {
|
|
205
|
-
const locale = applyLocaleConfig({ code: 'en' });
|
|
206
|
-
expect(locale.direction).toBe('ltr');
|
|
207
|
-
});
|
|
208
|
-
});
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { renderHook } from '@testing-library/react';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { I18nProvider } from '../provider';
|
|
5
|
-
import { createI18n } from '../i18n';
|
|
6
|
-
import { useObjectLabel } from '../useObjectLabel';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Create a wrapper with custom translations to simulate CRM locale bundles.
|
|
10
|
-
*/
|
|
11
|
-
function createWrapper(lang: string, translations?: Record<string, unknown>) {
|
|
12
|
-
const instance = createI18n({ defaultLanguage: lang, detectBrowserLanguage: false });
|
|
13
|
-
if (translations) {
|
|
14
|
-
instance.addResourceBundle(lang, 'translation', translations, true, true);
|
|
15
|
-
}
|
|
16
|
-
return ({ children }: { children: React.ReactNode }) =>
|
|
17
|
-
React.createElement(I18nProvider, { instance }, children);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe('useObjectLabel', () => {
|
|
21
|
-
describe('objectLabel', () => {
|
|
22
|
-
it('returns the plain label when no translation exists', () => {
|
|
23
|
-
const wrapper = createWrapper('en');
|
|
24
|
-
const { result } = renderHook(() => useObjectLabel(), { wrapper });
|
|
25
|
-
|
|
26
|
-
const label = result.current.objectLabel({ name: 'order_item', label: 'Order Item' });
|
|
27
|
-
expect(label).toBe('Order Item');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('returns the translated label when a translation exists', () => {
|
|
31
|
-
const wrapper = createWrapper('zh', {
|
|
32
|
-
crm: { objects: { order_item: { label: '订单明细' } } },
|
|
33
|
-
});
|
|
34
|
-
const { result } = renderHook(() => useObjectLabel(), { wrapper });
|
|
35
|
-
|
|
36
|
-
const label = result.current.objectLabel({ name: 'order_item', label: 'Order Item' });
|
|
37
|
-
expect(label).toBe('订单明细');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('falls back to plain label when translation key returns empty string', () => {
|
|
41
|
-
const wrapper = createWrapper('zh', {
|
|
42
|
-
crm: { objects: { order_item: { label: '' } } },
|
|
43
|
-
});
|
|
44
|
-
const { result } = renderHook(() => useObjectLabel(), { wrapper });
|
|
45
|
-
|
|
46
|
-
const label = result.current.objectLabel({ name: 'order_item', label: 'Order Item' });
|
|
47
|
-
expect(label).toBe('Order Item');
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('objectDescription', () => {
|
|
52
|
-
it('returns undefined when objectDef has no description', () => {
|
|
53
|
-
const wrapper = createWrapper('en');
|
|
54
|
-
const { result } = renderHook(() => useObjectLabel(), { wrapper });
|
|
55
|
-
|
|
56
|
-
const desc = result.current.objectDescription({ name: 'order_item' });
|
|
57
|
-
expect(desc).toBeUndefined();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('returns the plain description when no translation exists', () => {
|
|
61
|
-
const wrapper = createWrapper('en');
|
|
62
|
-
const { result } = renderHook(() => useObjectLabel(), { wrapper });
|
|
63
|
-
|
|
64
|
-
const desc = result.current.objectDescription({ name: 'order_item', description: 'Line items in an order' });
|
|
65
|
-
expect(desc).toBe('Line items in an order');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('returns the translated description when a translation exists', () => {
|
|
69
|
-
const wrapper = createWrapper('zh', {
|
|
70
|
-
crm: { objects: { order_item: { description: '订单中的商品条目' } } },
|
|
71
|
-
});
|
|
72
|
-
const { result } = renderHook(() => useObjectLabel(), { wrapper });
|
|
73
|
-
|
|
74
|
-
const desc = result.current.objectDescription({ name: 'order_item', description: 'Line items in an order' });
|
|
75
|
-
expect(desc).toBe('订单中的商品条目');
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
describe('fieldLabel', () => {
|
|
80
|
-
it('returns the fallback label when no translation exists', () => {
|
|
81
|
-
const wrapper = createWrapper('en');
|
|
82
|
-
const { result } = renderHook(() => useObjectLabel(), { wrapper });
|
|
83
|
-
|
|
84
|
-
const label = result.current.fieldLabel('order_item', 'name', 'Line Item');
|
|
85
|
-
expect(label).toBe('Line Item');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('returns the translated label when a translation exists', () => {
|
|
89
|
-
const wrapper = createWrapper('zh', {
|
|
90
|
-
crm: { fields: { order_item: { name: '订单项' } } },
|
|
91
|
-
});
|
|
92
|
-
const { result } = renderHook(() => useObjectLabel(), { wrapper });
|
|
93
|
-
|
|
94
|
-
const label = result.current.fieldLabel('order_item', 'name', 'Line Item');
|
|
95
|
-
expect(label).toBe('订单项');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('falls back to plain label when translation key returns empty string', () => {
|
|
99
|
-
const wrapper = createWrapper('zh', {
|
|
100
|
-
crm: { fields: { order_item: { name: '' } } },
|
|
101
|
-
});
|
|
102
|
-
const { result } = renderHook(() => useObjectLabel(), { wrapper });
|
|
103
|
-
|
|
104
|
-
const label = result.current.fieldLabel('order_item', 'name', 'Line Item');
|
|
105
|
-
expect(label).toBe('Line Item');
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe('multiple objects', () => {
|
|
110
|
-
it('resolves labels for different objects independently', () => {
|
|
111
|
-
const wrapper = createWrapper('zh', {
|
|
112
|
-
crm: {
|
|
113
|
-
objects: {
|
|
114
|
-
contact: { label: '联系人' },
|
|
115
|
-
opportunity: { label: '商机' },
|
|
116
|
-
},
|
|
117
|
-
fields: {
|
|
118
|
-
contact: { email: '邮箱' },
|
|
119
|
-
opportunity: { stage: '阶段' },
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
const { result } = renderHook(() => useObjectLabel(), { wrapper });
|
|
124
|
-
|
|
125
|
-
expect(result.current.objectLabel({ name: 'contact', label: 'Contact' })).toBe('联系人');
|
|
126
|
-
expect(result.current.objectLabel({ name: 'opportunity', label: 'Opportunity' })).toBe('商机');
|
|
127
|
-
expect(result.current.fieldLabel('contact', 'email', 'Email')).toBe('邮箱');
|
|
128
|
-
expect(result.current.fieldLabel('opportunity', 'stage', 'Stage')).toBe('阶段');
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
});
|
package/src/i18n.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @object-ui/i18n - Core i18n configuration and initialization
|
|
3
|
-
*
|
|
4
|
-
* Wraps i18next with Object UI defaults and built-in locale support.
|
|
5
|
-
*/
|
|
6
|
-
import i18next, { type i18n as I18nInstance } from 'i18next';
|
|
7
|
-
import { builtInLocales, isRTL } from './locales/index';
|
|
8
|
-
import type { TranslationKeys } from './locales/en';
|
|
9
|
-
|
|
10
|
-
export interface I18nConfig {
|
|
11
|
-
/** Default language (default: 'en') */
|
|
12
|
-
defaultLanguage?: string;
|
|
13
|
-
/** Fallback language (default: 'en') */
|
|
14
|
-
fallbackLanguage?: string;
|
|
15
|
-
/** Additional translation resources to merge with built-in locales */
|
|
16
|
-
resources?: Record<string, Record<string, unknown>>;
|
|
17
|
-
/** Whether to detect browser language automatically (default: true) */
|
|
18
|
-
detectBrowserLanguage?: boolean;
|
|
19
|
-
/** i18next interpolation options */
|
|
20
|
-
interpolation?: {
|
|
21
|
-
escapeValue?: boolean;
|
|
22
|
-
prefix?: string;
|
|
23
|
-
suffix?: string;
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Create and initialize an i18next instance with Object UI defaults
|
|
29
|
-
*/
|
|
30
|
-
export function createI18n(config: I18nConfig = {}): I18nInstance {
|
|
31
|
-
const {
|
|
32
|
-
defaultLanguage = 'en',
|
|
33
|
-
fallbackLanguage = 'en',
|
|
34
|
-
resources = {},
|
|
35
|
-
detectBrowserLanguage = true,
|
|
36
|
-
interpolation,
|
|
37
|
-
} = config;
|
|
38
|
-
|
|
39
|
-
// Merge built-in locales with user-provided resources
|
|
40
|
-
const mergedResources: Record<string, { translation: Record<string, unknown> }> = {};
|
|
41
|
-
|
|
42
|
-
for (const [lang, translations] of Object.entries(builtInLocales)) {
|
|
43
|
-
mergedResources[lang] = {
|
|
44
|
-
translation: {
|
|
45
|
-
...translations,
|
|
46
|
-
...(resources[lang] || {}),
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Add any additional languages from resources not in built-in locales
|
|
52
|
-
for (const [lang, translations] of Object.entries(resources)) {
|
|
53
|
-
if (!mergedResources[lang]) {
|
|
54
|
-
mergedResources[lang] = { translation: translations as Record<string, unknown> };
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Detect browser language if enabled
|
|
59
|
-
let lng = defaultLanguage;
|
|
60
|
-
if (detectBrowserLanguage && typeof navigator !== 'undefined') {
|
|
61
|
-
const browserLang = navigator.language?.split('-')[0];
|
|
62
|
-
if (browserLang && mergedResources[browserLang]) {
|
|
63
|
-
lng = browserLang;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const instance = i18next.createInstance();
|
|
68
|
-
|
|
69
|
-
instance.init({
|
|
70
|
-
lng,
|
|
71
|
-
fallbackLng: fallbackLanguage,
|
|
72
|
-
resources: mergedResources,
|
|
73
|
-
interpolation: {
|
|
74
|
-
escapeValue: false, // React already escapes
|
|
75
|
-
...interpolation,
|
|
76
|
-
},
|
|
77
|
-
returnNull: false,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
return instance;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Get the text direction for the current language
|
|
85
|
-
*/
|
|
86
|
-
export function getDirection(lang: string): 'ltr' | 'rtl' {
|
|
87
|
-
return isRTL(lang) ? 'rtl' : 'ltr';
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Get available languages from an i18n instance
|
|
92
|
-
*/
|
|
93
|
-
export function getAvailableLanguages(instance: I18nInstance): string[] {
|
|
94
|
-
const resources = instance.options.resources;
|
|
95
|
-
return resources ? Object.keys(resources) : [];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export type { TranslationKeys };
|
package/src/index.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @object-ui/i18n
|
|
3
|
-
*
|
|
4
|
-
* Internationalization (i18n) support for Object UI.
|
|
5
|
-
* Provides 10+ built-in language packs, RTL support, and date/currency formatting.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```tsx
|
|
9
|
-
* import { I18nProvider, useObjectTranslation } from '@object-ui/i18n';
|
|
10
|
-
*
|
|
11
|
-
* function App() {
|
|
12
|
-
* return (
|
|
13
|
-
* <I18nProvider config={{ defaultLanguage: 'zh' }}>
|
|
14
|
-
* <MyComponent />
|
|
15
|
-
* </I18nProvider>
|
|
16
|
-
* );
|
|
17
|
-
* }
|
|
18
|
-
*
|
|
19
|
-
* function MyComponent() {
|
|
20
|
-
* const { t, language, changeLanguage, direction } = useObjectTranslation();
|
|
21
|
-
* return (
|
|
22
|
-
* <div dir={direction}>
|
|
23
|
-
* <h1>{t('common.save')}</h1>
|
|
24
|
-
* <button onClick={() => changeLanguage('en')}>English</button>
|
|
25
|
-
* <button onClick={() => changeLanguage('zh')}>中文</button>
|
|
26
|
-
* </div>
|
|
27
|
-
* );
|
|
28
|
-
* }
|
|
29
|
-
* ```
|
|
30
|
-
*
|
|
31
|
-
* @packageDocumentation
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
// Core i18n setup
|
|
35
|
-
export { createI18n, getDirection, getAvailableLanguages, type I18nConfig, type TranslationKeys } from './i18n';
|
|
36
|
-
|
|
37
|
-
// React integration
|
|
38
|
-
export { I18nProvider, useObjectTranslation, useI18nContext, type I18nProviderProps } from './provider';
|
|
39
|
-
|
|
40
|
-
// Convention-based object/field label i18n
|
|
41
|
-
export { useObjectLabel, useSafeFieldLabel } from './useObjectLabel';
|
|
42
|
-
|
|
43
|
-
// Locale packs
|
|
44
|
-
export { builtInLocales, isRTL, RTL_LANGUAGES } from './locales/index';
|
|
45
|
-
export { default as en } from './locales/en';
|
|
46
|
-
export { default as zh } from './locales/zh';
|
|
47
|
-
export { default as ja } from './locales/ja';
|
|
48
|
-
export { default as ko } from './locales/ko';
|
|
49
|
-
export { default as de } from './locales/de';
|
|
50
|
-
export { default as fr } from './locales/fr';
|
|
51
|
-
export { default as es } from './locales/es';
|
|
52
|
-
export { default as pt } from './locales/pt';
|
|
53
|
-
export { default as ru } from './locales/ru';
|
|
54
|
-
export { default as ar } from './locales/ar';
|
|
55
|
-
|
|
56
|
-
// Formatting utilities
|
|
57
|
-
export {
|
|
58
|
-
formatDate,
|
|
59
|
-
formatDateTime,
|
|
60
|
-
formatRelativeTime,
|
|
61
|
-
formatCurrency,
|
|
62
|
-
formatNumber,
|
|
63
|
-
type DateFormatOptions,
|
|
64
|
-
type CurrencyFormatOptions,
|
|
65
|
-
type NumberFormatOptions,
|
|
66
|
-
} from './utils/index';
|
|
67
|
-
|
|
68
|
-
// Spec-aligned formatters (v2.0.7)
|
|
69
|
-
export {
|
|
70
|
-
resolvePlural,
|
|
71
|
-
formatDateSpec,
|
|
72
|
-
formatNumberSpec,
|
|
73
|
-
applyLocaleConfig,
|
|
74
|
-
type SpecPluralRule,
|
|
75
|
-
type SpecDateFormat,
|
|
76
|
-
type SpecNumberFormat,
|
|
77
|
-
type SpecLocaleConfig,
|
|
78
|
-
} from './utils/index';
|