@object-ui/i18n 3.3.0 → 3.3.2

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 (53) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +21 -1
  3. package/dist/i18n.js +10 -1
  4. package/dist/locales/ar.d.ts +58 -0
  5. package/dist/locales/ar.js +58 -0
  6. package/dist/locales/de.d.ts +58 -0
  7. package/dist/locales/de.js +58 -0
  8. package/dist/locales/en.d.ts +78 -0
  9. package/dist/locales/en.js +78 -0
  10. package/dist/locales/es.d.ts +58 -0
  11. package/dist/locales/es.js +58 -0
  12. package/dist/locales/fr.d.ts +58 -0
  13. package/dist/locales/fr.js +58 -0
  14. package/dist/locales/index.d.ts +620 -0
  15. package/dist/locales/ja.d.ts +58 -0
  16. package/dist/locales/ja.js +58 -0
  17. package/dist/locales/ko.d.ts +58 -0
  18. package/dist/locales/ko.js +58 -0
  19. package/dist/locales/pt.d.ts +58 -0
  20. package/dist/locales/pt.js +58 -0
  21. package/dist/locales/ru.d.ts +58 -0
  22. package/dist/locales/ru.js +58 -0
  23. package/dist/locales/zh.d.ts +78 -0
  24. package/dist/locales/zh.js +78 -0
  25. package/dist/useObjectLabel.d.ts +46 -0
  26. package/dist/useObjectLabel.js +79 -12
  27. package/package.json +30 -7
  28. package/.turbo/turbo-build.log +0 -4
  29. package/src/__tests__/i18n.test.ts +0 -255
  30. package/src/__tests__/provider.test.tsx +0 -117
  31. package/src/__tests__/spec-formatters.test.ts +0 -208
  32. package/src/__tests__/useObjectLabel.test.tsx +0 -131
  33. package/src/i18n.ts +0 -98
  34. package/src/index.ts +0 -81
  35. package/src/locales/ar.ts +0 -742
  36. package/src/locales/de.ts +0 -755
  37. package/src/locales/en.ts +0 -836
  38. package/src/locales/es.ts +0 -741
  39. package/src/locales/fr.ts +0 -755
  40. package/src/locales/index.ts +0 -42
  41. package/src/locales/ja.ts +0 -752
  42. package/src/locales/ko.ts +0 -741
  43. package/src/locales/pt.ts +0 -741
  44. package/src/locales/ru.ts +0 -761
  45. package/src/locales/zh.ts +0 -835
  46. package/src/provider.tsx +0 -177
  47. package/src/useObjectLabel.ts +0 -122
  48. package/src/useSafeTranslation.ts +0 -49
  49. package/src/utils/formatting.ts +0 -136
  50. package/src/utils/index.ts +0 -21
  51. package/src/utils/spec-formatters.ts +0 -220
  52. package/tsconfig.json +0 -12
  53. package/tsconfig.tsbuildinfo +0 -1
@@ -1,117 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { renderHook, waitFor, act } from '@testing-library/react';
3
- import React from 'react';
4
- import { I18nProvider, useObjectTranslation, useI18nContext } from '../provider';
5
- import { createI18n } from '../i18n';
6
-
7
- describe('I18nProvider', () => {
8
- it('creates i18n instance from config', () => {
9
- const wrapper = ({ children }: { children: React.ReactNode }) =>
10
- React.createElement(I18nProvider, { config: { defaultLanguage: 'en', detectBrowserLanguage: false } }, children);
11
-
12
- const { result } = renderHook(() => useObjectTranslation(), { wrapper });
13
-
14
- expect(result.current.i18n).toBeDefined();
15
- expect(result.current.language).toBe('en');
16
- });
17
-
18
- it('accepts pre-created instance', () => {
19
- const instance = createI18n({ defaultLanguage: 'fr', detectBrowserLanguage: false });
20
-
21
- const wrapper = ({ children }: { children: React.ReactNode }) =>
22
- React.createElement(I18nProvider, { instance }, children);
23
-
24
- const { result } = renderHook(() => useObjectTranslation(), { wrapper });
25
-
26
- expect(result.current.i18n).toBeDefined();
27
- expect(result.current.language).toBe('fr');
28
- });
29
- });
30
-
31
- describe('useObjectTranslation', () => {
32
- const wrapper = ({ children }: { children: React.ReactNode }) =>
33
- React.createElement(I18nProvider, { config: { defaultLanguage: 'en', detectBrowserLanguage: false } }, children);
34
-
35
- it('returns t, language, changeLanguage, direction, i18n', () => {
36
- const { result } = renderHook(() => useObjectTranslation(), { wrapper });
37
-
38
- expect(result.current.t).toBeTypeOf('function');
39
- expect(result.current.language).toBeTypeOf('string');
40
- expect(result.current.changeLanguage).toBeTypeOf('function');
41
- expect(result.current.direction).toBeTypeOf('string');
42
- expect(result.current.i18n).toBeDefined();
43
- });
44
-
45
- it('translates keys correctly', () => {
46
- const { result } = renderHook(() => useObjectTranslation(), { wrapper });
47
-
48
- expect(result.current.t('common.save')).toBe('Save');
49
- expect(result.current.t('common.cancel')).toBe('Cancel');
50
- expect(result.current.t('common.delete')).toBe('Delete');
51
- });
52
-
53
- it('returns en as default language', () => {
54
- const { result } = renderHook(() => useObjectTranslation(), { wrapper });
55
-
56
- expect(result.current.language).toBe('en');
57
- });
58
-
59
- it('works with Chinese language', () => {
60
- const zhWrapper = ({ children }: { children: React.ReactNode }) =>
61
- React.createElement(I18nProvider, { config: { defaultLanguage: 'zh', detectBrowserLanguage: false } }, children);
62
-
63
- const { result } = renderHook(() => useObjectTranslation(), { wrapper: zhWrapper });
64
-
65
- expect(result.current.language).toBe('zh');
66
- expect(result.current.t('common.save')).toBe('保存');
67
- expect(result.current.t('common.cancel')).toBe('取消');
68
- });
69
-
70
- it('changeLanguage updates language', async () => {
71
- const { result } = renderHook(() => useObjectTranslation(), { wrapper });
72
-
73
- expect(result.current.language).toBe('en');
74
-
75
- await act(async () => {
76
- await result.current.changeLanguage('zh');
77
- });
78
-
79
- await waitFor(() => {
80
- expect(result.current.language).toBe('zh');
81
- });
82
- });
83
-
84
- it('returns RTL direction for Arabic', () => {
85
- const arWrapper = ({ children }: { children: React.ReactNode }) =>
86
- React.createElement(I18nProvider, { config: { defaultLanguage: 'ar', detectBrowserLanguage: false } }, children);
87
-
88
- const { result } = renderHook(() => useObjectTranslation(), { wrapper: arWrapper });
89
-
90
- expect(result.current.direction).toBe('rtl');
91
- });
92
- });
93
-
94
- describe('useI18nContext', () => {
95
- it('throws when used outside provider', () => {
96
- // Suppress console.error from React for the expected error
97
- const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
98
-
99
- expect(() => {
100
- renderHook(() => useI18nContext());
101
- }).toThrow('useI18nContext must be used within an I18nProvider');
102
-
103
- spy.mockRestore();
104
- });
105
-
106
- it('returns context inside provider', () => {
107
- const wrapper = ({ children }: { children: React.ReactNode }) =>
108
- React.createElement(I18nProvider, { config: { defaultLanguage: 'en', detectBrowserLanguage: false } }, children);
109
-
110
- const { result } = renderHook(() => useI18nContext(), { wrapper });
111
-
112
- expect(result.current.language).toBe('en');
113
- expect(result.current.changeLanguage).toBeTypeOf('function');
114
- expect(result.current.direction).toBe('ltr');
115
- expect(result.current.i18n).toBeDefined();
116
- });
117
- });
@@ -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,81 +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
- // Safe translation hook factory
41
- export { createSafeTranslation } from './useSafeTranslation';
42
-
43
- // Convention-based object/field label i18n
44
- export { useObjectLabel, useSafeFieldLabel } from './useObjectLabel';
45
-
46
- // Locale packs
47
- export { builtInLocales, isRTL, RTL_LANGUAGES } from './locales/index';
48
- export { default as en } from './locales/en';
49
- export { default as zh } from './locales/zh';
50
- export { default as ja } from './locales/ja';
51
- export { default as ko } from './locales/ko';
52
- export { default as de } from './locales/de';
53
- export { default as fr } from './locales/fr';
54
- export { default as es } from './locales/es';
55
- export { default as pt } from './locales/pt';
56
- export { default as ru } from './locales/ru';
57
- export { default as ar } from './locales/ar';
58
-
59
- // Formatting utilities
60
- export {
61
- formatDate,
62
- formatDateTime,
63
- formatRelativeTime,
64
- formatCurrency,
65
- formatNumber,
66
- type DateFormatOptions,
67
- type CurrencyFormatOptions,
68
- type NumberFormatOptions,
69
- } from './utils/index';
70
-
71
- // Spec-aligned formatters (v2.0.7)
72
- export {
73
- resolvePlural,
74
- formatDateSpec,
75
- formatNumberSpec,
76
- applyLocaleConfig,
77
- type SpecPluralRule,
78
- type SpecDateFormat,
79
- type SpecNumberFormat,
80
- type SpecLocaleConfig,
81
- } from './utils/index';