@simplix-react/i18n 0.0.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.
@@ -0,0 +1,179 @@
1
+ import { i18n } from 'i18next';
2
+
3
+ type LocaleCode = string;
4
+ type TranslationValues = Record<string, string | number | boolean>;
5
+ declare const DATE_TIME_STYLES: {
6
+ readonly FULL: "full";
7
+ readonly LONG: "long";
8
+ readonly MEDIUM: "medium";
9
+ readonly SHORT: "short";
10
+ };
11
+ type DateTimeStyle = (typeof DATE_TIME_STYLES)[keyof typeof DATE_TIME_STYLES];
12
+ declare const NUMBER_FORMAT_STYLES: {
13
+ readonly DECIMAL: "decimal";
14
+ readonly CURRENCY: "currency";
15
+ readonly PERCENT: "percent";
16
+ readonly UNIT: "unit";
17
+ };
18
+ type NumberFormatStyle = (typeof NUMBER_FORMAT_STYLES)[keyof typeof NUMBER_FORMAT_STYLES];
19
+ declare const TEXT_DIRECTIONS: {
20
+ readonly LTR: "ltr";
21
+ readonly RTL: "rtl";
22
+ };
23
+ type TextDirection = (typeof TEXT_DIRECTIONS)[keyof typeof TEXT_DIRECTIONS];
24
+ interface PluralForms {
25
+ zero?: string;
26
+ one: string;
27
+ two?: string;
28
+ few?: string;
29
+ many?: string;
30
+ other: string;
31
+ }
32
+ interface DateTimeFormatOptions {
33
+ dateStyle?: DateTimeStyle;
34
+ timeStyle?: DateTimeStyle;
35
+ hour12?: boolean;
36
+ }
37
+ interface NumberFormatOptions {
38
+ style?: NumberFormatStyle;
39
+ currency?: string;
40
+ unit?: string;
41
+ minimumFractionDigits?: number;
42
+ maximumFractionDigits?: number;
43
+ }
44
+ interface LocaleInfo {
45
+ code: LocaleCode;
46
+ name: string;
47
+ englishName: string;
48
+ direction: TextDirection;
49
+ dateFormat: string;
50
+ timeFormat: string;
51
+ currency: string;
52
+ }
53
+ type TranslationNamespace = string;
54
+ declare const TRANSLATION_LOAD_STATES: {
55
+ readonly IDLE: "idle";
56
+ readonly LOADING: "loading";
57
+ readonly LOADED: "loaded";
58
+ readonly ERROR: "error";
59
+ };
60
+ type TranslationLoadState = (typeof TRANSLATION_LOAD_STATES)[keyof typeof TRANSLATION_LOAD_STATES];
61
+
62
+ interface II18nAdapter {
63
+ readonly id: string;
64
+ readonly name: string;
65
+ readonly locale: LocaleCode;
66
+ readonly fallbackLocale: LocaleCode;
67
+ readonly availableLocales: LocaleCode[];
68
+ initialize(defaultLocale?: LocaleCode): Promise<void>;
69
+ dispose(): Promise<void>;
70
+ setLocale(locale: LocaleCode): Promise<void>;
71
+ getLocaleInfo(locale: LocaleCode): LocaleInfo | null;
72
+ t(key: string, values?: TranslationValues): string;
73
+ tn(namespace: TranslationNamespace, key: string, values?: TranslationValues): string;
74
+ tp(key: string, count: number, values?: TranslationValues): string;
75
+ exists(key: string, namespace?: TranslationNamespace): boolean;
76
+ formatDate(date: Date, options?: DateTimeFormatOptions): string;
77
+ formatTime(date: Date, options?: DateTimeFormatOptions): string;
78
+ formatDateTime(date: Date, options?: DateTimeFormatOptions): string;
79
+ formatRelativeTime(date: Date): string;
80
+ formatNumber(value: number, options?: NumberFormatOptions): string;
81
+ formatCurrency(value: number, currency?: string): string;
82
+ loadTranslations(locale: LocaleCode, namespace: TranslationNamespace, translations: Record<string, string | PluralForms>): void;
83
+ getLoadState(locale: LocaleCode, namespace?: TranslationNamespace): TranslationLoadState;
84
+ onLocaleChange(handler: (locale: LocaleCode) => void): () => void;
85
+ }
86
+
87
+ interface LocaleConfig {
88
+ code: LocaleCode;
89
+ name: string;
90
+ englishName: string;
91
+ direction?: "ltr" | "rtl";
92
+ dateFormat?: string;
93
+ timeFormat?: string;
94
+ currency?: string;
95
+ }
96
+ type TranslationResources = Record<LocaleCode, Record<TranslationNamespace, Record<string, unknown>>>;
97
+ interface I18nextAdapterOptions {
98
+ defaultLocale?: LocaleCode;
99
+ fallbackLocale?: LocaleCode;
100
+ locales?: LocaleConfig[];
101
+ resources?: TranslationResources;
102
+ i18nextInstance?: i18n;
103
+ debug?: boolean;
104
+ }
105
+ declare class I18nextAdapter implements II18nAdapter {
106
+ readonly id = "i18next";
107
+ readonly name = "i18next Adapter";
108
+ private i18n;
109
+ private initialized;
110
+ private localeConfigs;
111
+ private defaultLocale;
112
+ private _fallbackLocale;
113
+ private resources;
114
+ private debug;
115
+ private localeChangeHandlers;
116
+ constructor(options?: I18nextAdapterOptions);
117
+ get locale(): LocaleCode;
118
+ get fallbackLocale(): LocaleCode;
119
+ get availableLocales(): LocaleCode[];
120
+ initialize(defaultLocale?: LocaleCode): Promise<void>;
121
+ dispose(): Promise<void>;
122
+ setLocale(locale: LocaleCode): Promise<void>;
123
+ getLocaleInfo(locale: LocaleCode): LocaleInfo | null;
124
+ t(key: string, values?: TranslationValues): string;
125
+ tn(namespace: TranslationNamespace, key: string, values?: TranslationValues): string;
126
+ tp(key: string, count: number, values?: TranslationValues): string;
127
+ exists(key: string, namespace?: TranslationNamespace): boolean;
128
+ formatDate(date: Date, options?: DateTimeFormatOptions): string;
129
+ formatTime(date: Date, options?: DateTimeFormatOptions): string;
130
+ formatDateTime(date: Date, options?: DateTimeFormatOptions): string;
131
+ formatRelativeTime(date: Date): string;
132
+ formatNumber(value: number, options?: NumberFormatOptions): string;
133
+ formatCurrency(value: number, currency?: string): string;
134
+ loadTranslations(locale: LocaleCode, namespace: TranslationNamespace, translations: Record<string, string | PluralForms>): void;
135
+ getLoadState(locale: LocaleCode, namespace?: TranslationNamespace): TranslationLoadState;
136
+ onLocaleChange(handler: (locale: LocaleCode) => void): () => void;
137
+ addResources(locale: LocaleCode, namespace: TranslationNamespace, resources: Record<string, unknown>): void;
138
+ getI18nextInstance(): i18n;
139
+ private notifyLocaleChange;
140
+ }
141
+
142
+ interface ComponentTranslations {
143
+ [locale: string]: () => Promise<{
144
+ default: Record<string, unknown>;
145
+ }>;
146
+ }
147
+ interface BuildModuleTranslationsOptions {
148
+ namespace: string;
149
+ locales: string[];
150
+ components: Record<string, ComponentTranslations>;
151
+ }
152
+ interface ModuleTranslations {
153
+ namespace: string;
154
+ locales: string[];
155
+ load: (locale: string) => Promise<Record<string, Record<string, unknown>>>;
156
+ }
157
+ declare function buildModuleTranslations(options: BuildModuleTranslationsOptions): ModuleTranslations;
158
+
159
+ interface CreateI18nConfigOptions {
160
+ defaultLocale?: LocaleCode;
161
+ fallbackLocale?: LocaleCode;
162
+ supportedLocales?: LocaleConfig[];
163
+ detection?: {
164
+ order: string[];
165
+ };
166
+ appTranslations?: Record<string, unknown>;
167
+ moduleTranslations?: ModuleTranslations[];
168
+ debug?: boolean;
169
+ }
170
+ interface I18nConfigResult {
171
+ adapter: I18nextAdapter;
172
+ i18nReady: Promise<void>;
173
+ }
174
+ declare function createI18nConfig(options: CreateI18nConfigOptions): I18nConfigResult;
175
+
176
+ declare const DEFAULT_LOCALES: LocaleConfig[];
177
+ declare const SUPPORTED_LOCALES: string[];
178
+
179
+ export { type BuildModuleTranslationsOptions, type ComponentTranslations, type CreateI18nConfigOptions, DATE_TIME_STYLES, DEFAULT_LOCALES, type DateTimeFormatOptions, type DateTimeStyle, type I18nConfigResult, I18nextAdapter, type I18nextAdapterOptions, type II18nAdapter, type LocaleCode, type LocaleConfig, type LocaleInfo, type ModuleTranslations, NUMBER_FORMAT_STYLES, type NumberFormatOptions, type NumberFormatStyle, type PluralForms, SUPPORTED_LOCALES, TEXT_DIRECTIONS, TRANSLATION_LOAD_STATES, type TextDirection, type TranslationLoadState, type TranslationNamespace, type TranslationResources, type TranslationValues, buildModuleTranslations, createI18nConfig };
package/dist/index.js ADDED
@@ -0,0 +1,367 @@
1
+ import i18next from 'i18next';
2
+
3
+ // src/types.ts
4
+ var DATE_TIME_STYLES = {
5
+ FULL: "full",
6
+ LONG: "long",
7
+ MEDIUM: "medium",
8
+ SHORT: "short"
9
+ };
10
+ var NUMBER_FORMAT_STYLES = {
11
+ DECIMAL: "decimal",
12
+ CURRENCY: "currency",
13
+ PERCENT: "percent",
14
+ UNIT: "unit"
15
+ };
16
+ var TEXT_DIRECTIONS = {
17
+ LTR: "ltr",
18
+ RTL: "rtl"
19
+ };
20
+ var TRANSLATION_LOAD_STATES = {
21
+ IDLE: "idle",
22
+ LOADING: "loading",
23
+ LOADED: "loaded",
24
+ ERROR: "error"
25
+ };
26
+ var BUILTIN_LOCALES = [
27
+ {
28
+ code: "ko",
29
+ name: "\uD55C\uAD6D\uC5B4",
30
+ englishName: "Korean",
31
+ direction: "ltr",
32
+ dateFormat: "yyyy-MM-dd",
33
+ timeFormat: "HH:mm:ss",
34
+ currency: "KRW"
35
+ },
36
+ {
37
+ code: "en",
38
+ name: "English",
39
+ englishName: "English",
40
+ direction: "ltr",
41
+ dateFormat: "MM/dd/yyyy",
42
+ timeFormat: "h:mm:ss a",
43
+ currency: "USD"
44
+ },
45
+ {
46
+ code: "ja",
47
+ name: "\u65E5\u672C\u8A9E",
48
+ englishName: "Japanese",
49
+ direction: "ltr",
50
+ dateFormat: "yyyy/MM/dd",
51
+ timeFormat: "HH:mm:ss",
52
+ currency: "JPY"
53
+ }
54
+ ];
55
+ var I18nextAdapter = class {
56
+ id = "i18next";
57
+ name = "i18next Adapter";
58
+ i18n;
59
+ initialized = false;
60
+ localeConfigs;
61
+ defaultLocale;
62
+ _fallbackLocale;
63
+ resources;
64
+ debug;
65
+ localeChangeHandlers = /* @__PURE__ */ new Set();
66
+ constructor(options = {}) {
67
+ this.defaultLocale = options.defaultLocale ?? "en";
68
+ this._fallbackLocale = options.fallbackLocale ?? "en";
69
+ this.resources = options.resources ?? {};
70
+ this.debug = options.debug ?? false;
71
+ this.i18n = options.i18nextInstance ?? i18next.createInstance();
72
+ const locales = options.locales ?? BUILTIN_LOCALES;
73
+ this.localeConfigs = new Map(locales.map((l) => [l.code, l]));
74
+ }
75
+ get locale() {
76
+ return this.i18n.language ?? this.defaultLocale;
77
+ }
78
+ get fallbackLocale() {
79
+ return this._fallbackLocale;
80
+ }
81
+ get availableLocales() {
82
+ return Array.from(this.localeConfigs.keys());
83
+ }
84
+ async initialize(defaultLocale) {
85
+ if (this.initialized) return;
86
+ const isAlreadyInitialized = this.i18n.isInitialized;
87
+ if (!isAlreadyInitialized) {
88
+ const locale = defaultLocale ?? this.defaultLocale;
89
+ await this.i18n.init({
90
+ lng: locale,
91
+ fallbackLng: this._fallbackLocale,
92
+ resources: this.resources,
93
+ interpolation: {
94
+ escapeValue: false
95
+ },
96
+ debug: this.debug,
97
+ returnNull: false,
98
+ returnEmptyString: false
99
+ });
100
+ }
101
+ this.i18n.on("languageChanged", (lng) => {
102
+ this.notifyLocaleChange(lng);
103
+ });
104
+ this.initialized = true;
105
+ }
106
+ async dispose() {
107
+ this.localeChangeHandlers.clear();
108
+ this.initialized = false;
109
+ }
110
+ async setLocale(locale) {
111
+ if (!this.localeConfigs.has(locale)) {
112
+ throw new Error(`Unsupported locale: ${locale}`);
113
+ }
114
+ await this.i18n.changeLanguage(locale);
115
+ }
116
+ getLocaleInfo(locale) {
117
+ const config = this.localeConfigs.get(locale);
118
+ if (!config) return null;
119
+ return {
120
+ code: config.code,
121
+ name: config.name,
122
+ englishName: config.englishName,
123
+ direction: config.direction ?? "ltr",
124
+ dateFormat: config.dateFormat ?? "yyyy-MM-dd",
125
+ timeFormat: config.timeFormat ?? "HH:mm:ss",
126
+ currency: config.currency ?? "USD"
127
+ };
128
+ }
129
+ t(key, values) {
130
+ return this.i18n.t(key, values) ?? key;
131
+ }
132
+ tn(namespace, key, values) {
133
+ return this.i18n.t(`${namespace}:${key}`, values) ?? key;
134
+ }
135
+ tp(key, count, values) {
136
+ return this.i18n.t(key, { count, ...values }) ?? key;
137
+ }
138
+ exists(key, namespace) {
139
+ const fullKey = namespace ? `${namespace}:${key}` : key;
140
+ return this.i18n.exists(fullKey);
141
+ }
142
+ formatDate(date, options) {
143
+ const locale = this.locale;
144
+ const intlOptions = {};
145
+ if (options?.dateStyle) {
146
+ intlOptions.dateStyle = options.dateStyle;
147
+ } else {
148
+ intlOptions.year = "numeric";
149
+ intlOptions.month = "2-digit";
150
+ intlOptions.day = "2-digit";
151
+ }
152
+ return new Intl.DateTimeFormat(locale, intlOptions).format(date);
153
+ }
154
+ formatTime(date, options) {
155
+ const locale = this.locale;
156
+ const intlOptions = {};
157
+ if (options?.timeStyle) {
158
+ intlOptions.timeStyle = options.timeStyle;
159
+ } else {
160
+ intlOptions.hour = "2-digit";
161
+ intlOptions.minute = "2-digit";
162
+ intlOptions.second = "2-digit";
163
+ }
164
+ if (options?.hour12 !== void 0) {
165
+ intlOptions.hour12 = options.hour12;
166
+ }
167
+ return new Intl.DateTimeFormat(locale, intlOptions).format(date);
168
+ }
169
+ formatDateTime(date, options) {
170
+ const locale = this.locale;
171
+ const intlOptions = {};
172
+ if (options?.dateStyle) {
173
+ intlOptions.dateStyle = options.dateStyle;
174
+ }
175
+ if (options?.timeStyle) {
176
+ intlOptions.timeStyle = options.timeStyle;
177
+ }
178
+ if (options?.hour12 !== void 0) {
179
+ intlOptions.hour12 = options.hour12;
180
+ }
181
+ if (!options?.dateStyle && !options?.timeStyle) {
182
+ intlOptions.dateStyle = "medium";
183
+ intlOptions.timeStyle = "medium";
184
+ }
185
+ return new Intl.DateTimeFormat(locale, intlOptions).format(date);
186
+ }
187
+ formatRelativeTime(date) {
188
+ const locale = this.locale;
189
+ const now = /* @__PURE__ */ new Date();
190
+ const diffMs = date.getTime() - now.getTime();
191
+ const diffSec = Math.round(diffMs / 1e3);
192
+ const diffMin = Math.round(diffSec / 60);
193
+ const diffHour = Math.round(diffMin / 60);
194
+ const diffDay = Math.round(diffHour / 24);
195
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
196
+ if (Math.abs(diffSec) < 60) {
197
+ return rtf.format(diffSec, "second");
198
+ } else if (Math.abs(diffMin) < 60) {
199
+ return rtf.format(diffMin, "minute");
200
+ } else if (Math.abs(diffHour) < 24) {
201
+ return rtf.format(diffHour, "hour");
202
+ } else {
203
+ return rtf.format(diffDay, "day");
204
+ }
205
+ }
206
+ formatNumber(value, options) {
207
+ const locale = this.locale;
208
+ const intlOptions = {};
209
+ if (options?.style) {
210
+ intlOptions.style = options.style;
211
+ }
212
+ if (options?.currency) {
213
+ intlOptions.currency = options.currency;
214
+ }
215
+ if (options?.unit) {
216
+ intlOptions.unit = options.unit;
217
+ }
218
+ if (options?.minimumFractionDigits !== void 0) {
219
+ intlOptions.minimumFractionDigits = options.minimumFractionDigits;
220
+ }
221
+ if (options?.maximumFractionDigits !== void 0) {
222
+ intlOptions.maximumFractionDigits = options.maximumFractionDigits;
223
+ }
224
+ return new Intl.NumberFormat(locale, intlOptions).format(value);
225
+ }
226
+ formatCurrency(value, currency) {
227
+ const locale = this.locale;
228
+ const localeInfo = this.getLocaleInfo(locale);
229
+ const currencyCode = currency ?? localeInfo?.currency ?? "USD";
230
+ return new Intl.NumberFormat(locale, {
231
+ style: "currency",
232
+ currency: currencyCode
233
+ }).format(value);
234
+ }
235
+ loadTranslations(locale, namespace, translations) {
236
+ this.i18n.addResourceBundle(locale, namespace, translations, true, true);
237
+ }
238
+ getLoadState(locale, namespace) {
239
+ if (!this.initialized) return "idle";
240
+ const hasLocale = this.i18n.hasResourceBundle(
241
+ locale,
242
+ namespace ?? "translation"
243
+ );
244
+ return hasLocale ? "loaded" : "idle";
245
+ }
246
+ onLocaleChange(handler) {
247
+ this.localeChangeHandlers.add(handler);
248
+ return () => {
249
+ this.localeChangeHandlers.delete(handler);
250
+ };
251
+ }
252
+ addResources(locale, namespace, resources) {
253
+ this.i18n.addResourceBundle(locale, namespace, resources, true, true);
254
+ }
255
+ getI18nextInstance() {
256
+ return this.i18n;
257
+ }
258
+ notifyLocaleChange(locale) {
259
+ for (const handler of this.localeChangeHandlers) {
260
+ try {
261
+ handler(locale);
262
+ } catch (error) {
263
+ console.error("Error in locale change handler:", error);
264
+ }
265
+ }
266
+ }
267
+ };
268
+
269
+ // src/module-translations.ts
270
+ function buildModuleTranslations(options) {
271
+ return {
272
+ namespace: options.namespace,
273
+ locales: options.locales,
274
+ async load(locale) {
275
+ const result = {};
276
+ const entries = Object.entries(options.components);
277
+ const loadPromises = entries.map(async ([componentPath, loaders]) => {
278
+ const loader = loaders[locale];
279
+ if (!loader) return;
280
+ const mod = await loader();
281
+ const data = mod.default ?? mod;
282
+ result[componentPath] = data;
283
+ });
284
+ await Promise.all(loadPromises);
285
+ return result;
286
+ }
287
+ };
288
+ }
289
+
290
+ // src/utils/locale-config.ts
291
+ var DEFAULT_LOCALES = [
292
+ {
293
+ code: "ko",
294
+ name: "\uD55C\uAD6D\uC5B4",
295
+ englishName: "Korean",
296
+ direction: "ltr",
297
+ dateFormat: "yyyy-MM-dd",
298
+ timeFormat: "HH:mm:ss",
299
+ currency: "KRW"
300
+ },
301
+ {
302
+ code: "en",
303
+ name: "English",
304
+ englishName: "English",
305
+ direction: "ltr",
306
+ dateFormat: "MM/dd/yyyy",
307
+ timeFormat: "h:mm:ss a",
308
+ currency: "USD"
309
+ },
310
+ {
311
+ code: "ja",
312
+ name: "\u65E5\u672C\u8A9E",
313
+ englishName: "Japanese",
314
+ direction: "ltr",
315
+ dateFormat: "yyyy/MM/dd",
316
+ timeFormat: "HH:mm:ss",
317
+ currency: "JPY"
318
+ }
319
+ ];
320
+ var SUPPORTED_LOCALES = DEFAULT_LOCALES.map((l) => l.code);
321
+
322
+ // src/create-i18n-config.ts
323
+ function createI18nConfig(options) {
324
+ const {
325
+ defaultLocale = "en",
326
+ fallbackLocale = "en",
327
+ supportedLocales = DEFAULT_LOCALES,
328
+ moduleTranslations = [],
329
+ debug = false
330
+ } = options;
331
+ const resources = {};
332
+ if (options.appTranslations) {
333
+ for (const [filePath, content] of Object.entries(
334
+ options.appTranslations
335
+ )) {
336
+ const match = filePath.match(/\/locales\/(.+?)\/(\w+)\.json$/);
337
+ if (!match) continue;
338
+ const namespace = match[1];
339
+ const locale = match[2];
340
+ if (!resources[locale]) resources[locale] = {};
341
+ const mod = content;
342
+ resources[locale][namespace] = mod.default ?? mod;
343
+ }
344
+ }
345
+ const adapter = new I18nextAdapter({
346
+ defaultLocale,
347
+ fallbackLocale,
348
+ locales: supportedLocales,
349
+ resources,
350
+ debug
351
+ });
352
+ const i18nReady = (async () => {
353
+ await adapter.initialize(defaultLocale);
354
+ for (const mod of moduleTranslations) {
355
+ for (const locale of mod.locales) {
356
+ const translations = await mod.load(locale);
357
+ for (const [componentPath, data] of Object.entries(translations)) {
358
+ const namespace = `${mod.namespace}/${componentPath}`;
359
+ adapter.addResources(locale, namespace, data);
360
+ }
361
+ }
362
+ }
363
+ })();
364
+ return { adapter, i18nReady };
365
+ }
366
+
367
+ export { DATE_TIME_STYLES, DEFAULT_LOCALES, I18nextAdapter, NUMBER_FORMAT_STYLES, SUPPORTED_LOCALES, TEXT_DIRECTIONS, TRANSLATION_LOAD_STATES, buildModuleTranslations, createI18nConfig };
@@ -0,0 +1,105 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ type LocaleCode = string;
5
+ type TranslationValues = Record<string, string | number | boolean>;
6
+ declare const DATE_TIME_STYLES: {
7
+ readonly FULL: "full";
8
+ readonly LONG: "long";
9
+ readonly MEDIUM: "medium";
10
+ readonly SHORT: "short";
11
+ };
12
+ type DateTimeStyle = (typeof DATE_TIME_STYLES)[keyof typeof DATE_TIME_STYLES];
13
+ declare const NUMBER_FORMAT_STYLES: {
14
+ readonly DECIMAL: "decimal";
15
+ readonly CURRENCY: "currency";
16
+ readonly PERCENT: "percent";
17
+ readonly UNIT: "unit";
18
+ };
19
+ type NumberFormatStyle = (typeof NUMBER_FORMAT_STYLES)[keyof typeof NUMBER_FORMAT_STYLES];
20
+ declare const TEXT_DIRECTIONS: {
21
+ readonly LTR: "ltr";
22
+ readonly RTL: "rtl";
23
+ };
24
+ type TextDirection = (typeof TEXT_DIRECTIONS)[keyof typeof TEXT_DIRECTIONS];
25
+ interface PluralForms {
26
+ zero?: string;
27
+ one: string;
28
+ two?: string;
29
+ few?: string;
30
+ many?: string;
31
+ other: string;
32
+ }
33
+ interface DateTimeFormatOptions {
34
+ dateStyle?: DateTimeStyle;
35
+ timeStyle?: DateTimeStyle;
36
+ hour12?: boolean;
37
+ }
38
+ interface NumberFormatOptions {
39
+ style?: NumberFormatStyle;
40
+ currency?: string;
41
+ unit?: string;
42
+ minimumFractionDigits?: number;
43
+ maximumFractionDigits?: number;
44
+ }
45
+ interface LocaleInfo {
46
+ code: LocaleCode;
47
+ name: string;
48
+ englishName: string;
49
+ direction: TextDirection;
50
+ dateFormat: string;
51
+ timeFormat: string;
52
+ currency: string;
53
+ }
54
+ type TranslationNamespace = string;
55
+ declare const TRANSLATION_LOAD_STATES: {
56
+ readonly IDLE: "idle";
57
+ readonly LOADING: "loading";
58
+ readonly LOADED: "loaded";
59
+ readonly ERROR: "error";
60
+ };
61
+ type TranslationLoadState = (typeof TRANSLATION_LOAD_STATES)[keyof typeof TRANSLATION_LOAD_STATES];
62
+
63
+ interface II18nAdapter {
64
+ readonly id: string;
65
+ readonly name: string;
66
+ readonly locale: LocaleCode;
67
+ readonly fallbackLocale: LocaleCode;
68
+ readonly availableLocales: LocaleCode[];
69
+ initialize(defaultLocale?: LocaleCode): Promise<void>;
70
+ dispose(): Promise<void>;
71
+ setLocale(locale: LocaleCode): Promise<void>;
72
+ getLocaleInfo(locale: LocaleCode): LocaleInfo | null;
73
+ t(key: string, values?: TranslationValues): string;
74
+ tn(namespace: TranslationNamespace, key: string, values?: TranslationValues): string;
75
+ tp(key: string, count: number, values?: TranslationValues): string;
76
+ exists(key: string, namespace?: TranslationNamespace): boolean;
77
+ formatDate(date: Date, options?: DateTimeFormatOptions): string;
78
+ formatTime(date: Date, options?: DateTimeFormatOptions): string;
79
+ formatDateTime(date: Date, options?: DateTimeFormatOptions): string;
80
+ formatRelativeTime(date: Date): string;
81
+ formatNumber(value: number, options?: NumberFormatOptions): string;
82
+ formatCurrency(value: number, currency?: string): string;
83
+ loadTranslations(locale: LocaleCode, namespace: TranslationNamespace, translations: Record<string, string | PluralForms>): void;
84
+ getLoadState(locale: LocaleCode, namespace?: TranslationNamespace): TranslationLoadState;
85
+ onLocaleChange(handler: (locale: LocaleCode) => void): () => void;
86
+ }
87
+
88
+ interface I18nProviderProps {
89
+ children: ReactNode;
90
+ adapter: II18nAdapter;
91
+ }
92
+ declare function I18nProvider({ children, adapter }: I18nProviderProps): react_jsx_runtime.JSX.Element;
93
+ declare function useI18nAdapter(): II18nAdapter | null;
94
+
95
+ type TranslateFunction = (key: string, values?: TranslationValues) => string;
96
+ interface UseTranslationReturn {
97
+ t: TranslateFunction;
98
+ locale: LocaleCode;
99
+ exists: (key: string) => boolean;
100
+ }
101
+ declare function useTranslation(namespace: string): UseTranslationReturn;
102
+ declare function useI18n(): II18nAdapter | null;
103
+ declare function useLocale(): LocaleCode;
104
+
105
+ export { I18nProvider, type I18nProviderProps, type TranslateFunction, type UseTranslationReturn, useI18n, useI18nAdapter, useLocale, useTranslation };
package/dist/react.js ADDED
@@ -0,0 +1,68 @@
1
+ import { createContext, useContext, useCallback, useSyncExternalStore, useMemo } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/react/i18n-provider.tsx
5
+ var I18nContext = createContext(null);
6
+ function I18nProvider({ children, adapter }) {
7
+ return /* @__PURE__ */ jsx(I18nContext.Provider, { value: adapter, children });
8
+ }
9
+ function useI18nAdapter() {
10
+ return useContext(I18nContext);
11
+ }
12
+ function useTranslation(namespace) {
13
+ const i18n = useI18nAdapter();
14
+ const subscribe = useCallback(
15
+ (onStoreChange) => {
16
+ if (!i18n) return () => {
17
+ };
18
+ return i18n.onLocaleChange(onStoreChange);
19
+ },
20
+ [i18n]
21
+ );
22
+ const getSnapshot = useCallback(() => {
23
+ return i18n?.locale ?? "en";
24
+ }, [i18n]);
25
+ const locale = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
26
+ const t = useCallback(
27
+ (key, values) => {
28
+ if (!i18n) return key;
29
+ return i18n.tn(namespace, key, values);
30
+ },
31
+ [i18n, namespace]
32
+ );
33
+ const exists = useCallback(
34
+ (key) => {
35
+ if (!i18n) return false;
36
+ return i18n.exists(key, namespace);
37
+ },
38
+ [i18n, namespace]
39
+ );
40
+ return useMemo(
41
+ () => ({
42
+ t,
43
+ locale,
44
+ exists
45
+ }),
46
+ [t, locale, exists]
47
+ );
48
+ }
49
+ function useI18n() {
50
+ return useI18nAdapter();
51
+ }
52
+ function useLocale() {
53
+ const i18n = useI18nAdapter();
54
+ const subscribe = useCallback(
55
+ (onStoreChange) => {
56
+ if (!i18n) return () => {
57
+ };
58
+ return i18n.onLocaleChange(onStoreChange);
59
+ },
60
+ [i18n]
61
+ );
62
+ const getSnapshot = useCallback(() => {
63
+ return i18n?.locale ?? "en";
64
+ }, [i18n]);
65
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
66
+ }
67
+
68
+ export { I18nProvider, useI18n, useI18nAdapter, useLocale, useTranslation };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@simplix-react/i18n",
3
+ "version": "0.0.1",
4
+ "description": "Internationalization framework with i18next adapter",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ },
11
+ "./react": {
12
+ "types": "./dist/react.d.ts",
13
+ "import": "./dist/react.js"
14
+ }
15
+ },
16
+ "files": ["dist"],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "dev": "tsup --watch",
20
+ "typecheck": "tsc --noEmit",
21
+ "lint": "eslint src",
22
+ "test": "vitest run --passWithNoTests",
23
+ "clean": "rm -rf dist .turbo"
24
+ },
25
+ "peerDependencies": {
26
+ "i18next": ">=25.0.0",
27
+ "react": ">=18.0.0",
28
+ "react-i18next": ">=16.0.0"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "i18next": { "optional": true },
32
+ "react": { "optional": true },
33
+ "react-i18next": { "optional": true }
34
+ },
35
+ "devDependencies": {
36
+ "@simplix-react/config-typescript": "workspace:*",
37
+ "@types/react": "^19.0.0",
38
+ "eslint": "^9.39.2",
39
+ "i18next": "^25.8.0",
40
+ "react": "^19.0.0",
41
+ "react-i18next": "^16.0.0",
42
+ "tsup": "^8.5.1",
43
+ "typescript": "^5.9.3",
44
+ "vitest": "^3.0.0"
45
+ }
46
+ }