@hua-labs/i18n-core 1.0.0 → 1.1.0-alpha.0.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.
package/src/index.ts CHANGED
@@ -1,298 +1,298 @@
1
- /**
2
- * @hua-labs/i18n-core - 핵심 기능 전용 엔트리포인트
3
- *
4
- * 이 모듈은 기본적인 번역 기능만 필요한 경우 사용합니다.
5
- * 플러그인, 고급 기능, 디버깅 도구 없이 순수한 번역 기능만 제공합니다.
6
- */
7
-
8
- import React from 'react';
9
- import { I18nProvider, useI18n } from './hooks/useI18n';
10
- import { useTranslation, useLanguageChange } from './hooks/useTranslation';
11
- import { Translator, ssrTranslate, serverTranslate } from './core/translator';
12
- import { I18nConfig } from './types';
13
-
14
- // Window 객체 타입 확장
15
- declare global {
16
- interface Window {
17
- __I18N_DEBUG_MODE__?: boolean;
18
- __I18N_DEBUG_MISSING_KEYS__?: Record<string, string[]>;
19
- __I18N_DEBUG_ERRORS__?: Array<{
20
- timestamp: string;
21
- language: string;
22
- namespace: string;
23
- error: string;
24
- stack?: string;
25
- }>;
26
- __I18N_PERFORMANCE_DATA__?: Record<string, number[]>;
27
- __I18N_PERFORMANCE_ALERTS__?: Array<{
28
- id: string;
29
- type: 'warning' | 'error' | 'info';
30
- severity: 'low' | 'medium' | 'high' | 'critical';
31
- message: string;
32
- metric: string;
33
- value: number;
34
- threshold: number;
35
- timestamp: number;
36
- resolved: boolean;
37
- }>;
38
- __I18N_ANALYTICS_DATA__?: {
39
- missingKeys?: Set<string> | string[];
40
- performance?: {
41
- totalTime: number;
42
- averageTime: number;
43
- calls: number;
44
- };
45
- usage?: {
46
- keys?: Map<string, number> | Record<string, number>;
47
- languages?: Map<string, number> | Record<string, number>;
48
- namespaces?: Map<string, number> | Record<string, number>;
49
- };
50
- errors?: Array<{
51
- timestamp: number;
52
- error: string;
53
- context: string;
54
- }>;
55
- };
56
- }
57
- }
58
-
59
- // 기본 언어 설정
60
- const defaultLanguages = [
61
- { code: 'ko', name: 'Korean', nativeName: '한국어' },
62
- { code: 'en', name: 'English', nativeName: 'English' },
63
- ];
64
-
65
- /**
66
- * 핵심 기능용 설정 함수
67
- *
68
- * @example
69
- * ```tsx
70
- * // app/layout.tsx (Next.js App Router)
71
- * import { createCoreI18n } from '@hua-labs/i18n-core';
72
- *
73
- * export default function RootLayout({ children }) {
74
- * return (
75
- * <html>
76
- * <body>
77
- * {createCoreI18n({
78
- * defaultLanguage: 'ko',
79
- * fallbackLanguage: 'en',
80
- * namespaces: ['common', 'auth']
81
- * })({ children })}
82
- * </body>
83
- * </html>
84
- * );
85
- * }
86
- * ```
87
- */
88
- export function createCoreI18n(options?: {
89
- defaultLanguage?: string;
90
- fallbackLanguage?: string;
91
- namespaces?: string[];
92
- debug?: boolean;
93
- loadTranslations?: (language: string, namespace: string) => Promise<Record<string, string>>;
94
- /**
95
- * 번역 파일 로딩 방식 설정
96
- * - 'api': API route를 통해 동적 로드 (기본값, 권장)
97
- * - 'static': 정적 파일 경로에서 로드
98
- * - 'custom': loadTranslations 함수 사용
99
- */
100
- translationLoader?: 'api' | 'static' | 'custom';
101
- /**
102
- * API route 경로 (translationLoader가 'api'일 때 사용)
103
- * 기본값: '/api/translations'
104
- */
105
- translationApiPath?: string;
106
- /**
107
- * SSR에서 전달된 초기 번역 데이터 (네트워크 요청 없이 사용)
108
- * 형식: { [language]: { [namespace]: { [key]: value } } }
109
- */
110
- initialTranslations?: Record<string, Record<string, Record<string, string>>>;
111
- /**
112
- * 자동 언어 동기화 활성화 여부
113
- * 기본값: false (Zustand 어댑터 등 외부에서 직접 처리하는 경우)
114
- */
115
- autoLanguageSync?: boolean;
116
- }) {
117
- const {
118
- defaultLanguage = 'ko',
119
- fallbackLanguage = 'en',
120
- namespaces = ['common'],
121
- debug = false,
122
- loadTranslations,
123
- translationLoader = 'api',
124
- translationApiPath = '/api/translations',
125
- initialTranslations,
126
- autoLanguageSync = false // 기본값 false (Zustand 어댑터 등 외부에서 직접 처리)
127
- } = options || {};
128
-
129
- // API route 기반 로더 (기본값, 권장)
130
- const apiRouteLoader = async (language: string, namespace: string) => {
131
- try {
132
- // 클라이언트 사이드에서만 동적 로드
133
- if (typeof window !== 'undefined') {
134
- const apiUrl = `${translationApiPath}/${language}/${namespace}`;
135
- const response = await fetch(apiUrl);
136
-
137
- if (response.ok) {
138
- const data = await response.json();
139
- if (debug) {
140
- console.log(`✅ Loaded translation from API: ${language}/${namespace}`);
141
- }
142
- return data;
143
- } else if (response.status === 404) {
144
- if (debug) {
145
- console.warn(`⚠️ Translation not found in API: ${language}/${namespace}`);
146
- }
147
- }
148
- }
149
-
150
- // SSR 또는 API 실패 시 기본 번역 반환
151
- return getDefaultTranslations(language, namespace);
152
- } catch (error) {
153
- if (debug) {
154
- console.warn(`Failed to load translation from API: ${language}/${namespace}`, error);
155
- }
156
- return getDefaultTranslations(language, namespace);
157
- }
158
- };
159
-
160
- // 정적 파일 로더 (하위 호환성)
161
- const staticFileLoader = async (language: string, namespace: string) => {
162
- try {
163
- let data: Record<string, string> | null = null;
164
-
165
- // 클라이언트 사이드에서만 동적 로드 시도
166
- if (typeof window !== 'undefined') {
167
- const possiblePaths = [
168
- `/translations/${language}/${namespace}.json`,
169
- `../translations/${language}/${namespace}.json`,
170
- `./translations/${language}/${namespace}.json`,
171
- `translations/${language}/${namespace}.json`,
172
- `../../translations/${language}/${namespace}.json`,
173
- ];
174
-
175
- for (const path of possiblePaths) {
176
- try {
177
- const response = await fetch(path);
178
- if (response.ok) {
179
- data = await response.json();
180
- if (debug) {
181
- console.log(`✅ Loaded translation from static path: ${path}`);
182
- }
183
- break;
184
- }
185
- } catch (pathError) {
186
- continue;
187
- }
188
- }
189
- }
190
-
191
- if (data) {
192
- return data;
193
- }
194
-
195
- return getDefaultTranslations(language, namespace);
196
- } catch (error) {
197
- if (debug) {
198
- console.warn(`Failed to load translation file: ${language}/${namespace}.json`);
199
- }
200
- return getDefaultTranslations(language, namespace);
201
- }
202
- };
203
-
204
- // 기본 파일 로더 선택
205
- const defaultFileLoader = translationLoader === 'api'
206
- ? apiRouteLoader
207
- : translationLoader === 'static'
208
- ? staticFileLoader
209
- : loadTranslations || apiRouteLoader;
210
-
211
- const config: I18nConfig = {
212
- defaultLanguage,
213
- fallbackLanguage,
214
- supportedLanguages: defaultLanguages,
215
- namespaces,
216
- loadTranslations: translationLoader === 'custom' && loadTranslations
217
- ? loadTranslations
218
- : defaultFileLoader,
219
- initialTranslations, // SSR 번역 데이터 전달
220
- debug,
221
- missingKeyHandler: (key: string, language?: string, namespace?: string) => {
222
- if (debug) {
223
- console.warn(`Missing translation key: ${key}`);
224
-
225
- // Debug SDK와 연동하여 누락 키 추적
226
- if (typeof window !== 'undefined' && window.__I18N_DEBUG_MISSING_KEYS__) {
227
- const missingKeys = window.__I18N_DEBUG_MISSING_KEYS__;
228
- const keyPath = `${language || 'unknown'}:${namespace || 'unknown'}`;
229
- missingKeys[keyPath] = missingKeys[keyPath] || [];
230
- if (!missingKeys[keyPath].includes(key)) {
231
- missingKeys[keyPath].push(key);
232
- }
233
- }
234
-
235
- return `[MISSING: ${key}]`;
236
- }
237
- return key.split('.').pop() || key;
238
- },
239
- errorHandler: (error: unknown, language: string, namespace: string) => {
240
- if (debug) {
241
- console.error(`Translation error for ${language}:${namespace}:`, error);
242
- }
243
- },
244
- // autoLanguageSync는 기본적으로 false (Zustand 어댑터 등 외부에서 직접 처리하는 경우)
245
- // 필요시 options에서 명시적으로 true로 설정 가능
246
- autoLanguageSync: options?.autoLanguageSync ?? false
247
- };
248
-
249
- // Provider 컴포넌트 반환
250
- return function CoreI18nProvider({ children }: { children: React.ReactNode }) {
251
- return React.createElement(I18nProvider, { config, children });
252
- };
253
- }
254
-
255
- // 기본 번역 데이터는 공통 유틸리티에서 가져옴
256
- import { getDefaultTranslations } from './utils/default-translations';
257
-
258
- /**
259
- * 가장 기본적인 Provider (최소한의 설정)
260
- */
261
- export function CoreProvider({ children }: { children: React.ReactNode }) {
262
- return createCoreI18n()({ children });
263
- }
264
-
265
- /**
266
- * 언어별 Provider (언어만 지정)
267
- */
268
- export function createLanguageProvider(language: string) {
269
- return createCoreI18n({ defaultLanguage: language });
270
- }
271
-
272
- /**
273
- * 네임스페이스별 Provider (네임스페이스만 지정)
274
- */
275
- export function createNamespaceProvider(namespaces: string[]) {
276
- return createCoreI18n({ namespaces });
277
- }
278
-
279
- /**
280
- * 커스텀 로더 Provider (사용자 정의 번역 로더 사용)
281
- */
282
- export function createCustomLoaderProvider(
283
- loadTranslations: (language: string, namespace: string) => Promise<Record<string, string>>
284
- ) {
285
- return createCoreI18n({ loadTranslations });
286
- }
287
-
288
- // 핵심 훅들 export
289
- export { useTranslation, useLanguageChange, useI18n };
290
-
291
- // Provider export
292
- export { I18nProvider };
293
-
294
- // 핵심 클래스/함수들 export
295
- export { Translator, ssrTranslate, serverTranslate };
296
-
297
- // 타입 export
1
+ /**
2
+ * @hua-labs/i18n-core - 핵심 기능 전용 엔트리포인트
3
+ *
4
+ * 이 모듈은 기본적인 번역 기능만 필요한 경우 사용합니다.
5
+ * 플러그인, 고급 기능, 디버깅 도구 없이 순수한 번역 기능만 제공합니다.
6
+ */
7
+
8
+ import React from 'react';
9
+ import { I18nProvider, useI18n } from './hooks/useI18n';
10
+ import { useTranslation, useLanguageChange } from './hooks/useTranslation';
11
+ import { Translator, ssrTranslate, serverTranslate } from './core/translator';
12
+ import { I18nConfig } from './types';
13
+
14
+ // Window 객체 타입 확장
15
+ declare global {
16
+ interface Window {
17
+ __I18N_DEBUG_MODE__?: boolean;
18
+ __I18N_DEBUG_MISSING_KEYS__?: Record<string, string[]>;
19
+ __I18N_DEBUG_ERRORS__?: Array<{
20
+ timestamp: string;
21
+ language: string;
22
+ namespace: string;
23
+ error: string;
24
+ stack?: string;
25
+ }>;
26
+ __I18N_PERFORMANCE_DATA__?: Record<string, number[]>;
27
+ __I18N_PERFORMANCE_ALERTS__?: Array<{
28
+ id: string;
29
+ type: 'warning' | 'error' | 'info';
30
+ severity: 'low' | 'medium' | 'high' | 'critical';
31
+ message: string;
32
+ metric: string;
33
+ value: number;
34
+ threshold: number;
35
+ timestamp: number;
36
+ resolved: boolean;
37
+ }>;
38
+ __I18N_ANALYTICS_DATA__?: {
39
+ missingKeys?: Set<string> | string[];
40
+ performance?: {
41
+ totalTime: number;
42
+ averageTime: number;
43
+ calls: number;
44
+ };
45
+ usage?: {
46
+ keys?: Map<string, number> | Record<string, number>;
47
+ languages?: Map<string, number> | Record<string, number>;
48
+ namespaces?: Map<string, number> | Record<string, number>;
49
+ };
50
+ errors?: Array<{
51
+ timestamp: number;
52
+ error: string;
53
+ context: string;
54
+ }>;
55
+ };
56
+ }
57
+ }
58
+
59
+ // 기본 언어 설정
60
+ const defaultLanguages = [
61
+ { code: 'ko', name: 'Korean', nativeName: '한국어' },
62
+ { code: 'en', name: 'English', nativeName: 'English' },
63
+ ];
64
+
65
+ /**
66
+ * 핵심 기능용 설정 함수
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * // app/layout.tsx (Next.js App Router)
71
+ * import { createCoreI18n } from '@hua-labs/i18n-core';
72
+ *
73
+ * export default function RootLayout({ children }) {
74
+ * return (
75
+ * <html>
76
+ * <body>
77
+ * {createCoreI18n({
78
+ * defaultLanguage: 'ko',
79
+ * fallbackLanguage: 'en',
80
+ * namespaces: ['common', 'auth']
81
+ * })({ children })}
82
+ * </body>
83
+ * </html>
84
+ * );
85
+ * }
86
+ * ```
87
+ */
88
+ export function createCoreI18n(options?: {
89
+ defaultLanguage?: string;
90
+ fallbackLanguage?: string;
91
+ namespaces?: string[];
92
+ debug?: boolean;
93
+ loadTranslations?: (language: string, namespace: string) => Promise<Record<string, string>>;
94
+ /**
95
+ * 번역 파일 로딩 방식 설정
96
+ * - 'api': API route를 통해 동적 로드 (기본값, 권장)
97
+ * - 'static': 정적 파일 경로에서 로드
98
+ * - 'custom': loadTranslations 함수 사용
99
+ */
100
+ translationLoader?: 'api' | 'static' | 'custom';
101
+ /**
102
+ * API route 경로 (translationLoader가 'api'일 때 사용)
103
+ * 기본값: '/api/translations'
104
+ */
105
+ translationApiPath?: string;
106
+ /**
107
+ * SSR에서 전달된 초기 번역 데이터 (네트워크 요청 없이 사용)
108
+ * 형식: { [language]: { [namespace]: { [key]: value } } }
109
+ */
110
+ initialTranslations?: Record<string, Record<string, Record<string, string>>>;
111
+ /**
112
+ * 자동 언어 동기화 활성화 여부
113
+ * 기본값: false (Zustand 어댑터 등 외부에서 직접 처리하는 경우)
114
+ */
115
+ autoLanguageSync?: boolean;
116
+ }) {
117
+ const {
118
+ defaultLanguage = 'ko',
119
+ fallbackLanguage = 'en',
120
+ namespaces = ['common'],
121
+ debug = false,
122
+ loadTranslations,
123
+ translationLoader = 'api',
124
+ translationApiPath = '/api/translations',
125
+ initialTranslations,
126
+ autoLanguageSync = false // 기본값 false (Zustand 어댑터 등 외부에서 직접 처리)
127
+ } = options || {};
128
+
129
+ // API route 기반 로더 (기본값, 권장)
130
+ const apiRouteLoader = async (language: string, namespace: string) => {
131
+ try {
132
+ // 클라이언트 사이드에서만 동적 로드
133
+ if (typeof window !== 'undefined') {
134
+ const apiUrl = `${translationApiPath}/${language}/${namespace}`;
135
+ const response = await fetch(apiUrl);
136
+
137
+ if (response.ok) {
138
+ const data = await response.json();
139
+ if (debug) {
140
+ console.log(`✅ Loaded translation from API: ${language}/${namespace}`);
141
+ }
142
+ return data;
143
+ } else if (response.status === 404) {
144
+ if (debug) {
145
+ console.warn(`⚠️ Translation not found in API: ${language}/${namespace}`);
146
+ }
147
+ }
148
+ }
149
+
150
+ // SSR 또는 API 실패 시 기본 번역 반환
151
+ return getDefaultTranslations(language, namespace);
152
+ } catch (error) {
153
+ if (debug) {
154
+ console.warn(`Failed to load translation from API: ${language}/${namespace}`, error);
155
+ }
156
+ return getDefaultTranslations(language, namespace);
157
+ }
158
+ };
159
+
160
+ // 정적 파일 로더 (하위 호환성)
161
+ const staticFileLoader = async (language: string, namespace: string) => {
162
+ try {
163
+ let data: Record<string, string> | null = null;
164
+
165
+ // 클라이언트 사이드에서만 동적 로드 시도
166
+ if (typeof window !== 'undefined') {
167
+ const possiblePaths = [
168
+ `/translations/${language}/${namespace}.json`,
169
+ `../translations/${language}/${namespace}.json`,
170
+ `./translations/${language}/${namespace}.json`,
171
+ `translations/${language}/${namespace}.json`,
172
+ `../../translations/${language}/${namespace}.json`,
173
+ ];
174
+
175
+ for (const path of possiblePaths) {
176
+ try {
177
+ const response = await fetch(path);
178
+ if (response.ok) {
179
+ data = await response.json();
180
+ if (debug) {
181
+ console.log(`✅ Loaded translation from static path: ${path}`);
182
+ }
183
+ break;
184
+ }
185
+ } catch (pathError) {
186
+ continue;
187
+ }
188
+ }
189
+ }
190
+
191
+ if (data) {
192
+ return data;
193
+ }
194
+
195
+ return getDefaultTranslations(language, namespace);
196
+ } catch (error) {
197
+ if (debug) {
198
+ console.warn(`Failed to load translation file: ${language}/${namespace}.json`);
199
+ }
200
+ return getDefaultTranslations(language, namespace);
201
+ }
202
+ };
203
+
204
+ // 기본 파일 로더 선택
205
+ const defaultFileLoader = translationLoader === 'api'
206
+ ? apiRouteLoader
207
+ : translationLoader === 'static'
208
+ ? staticFileLoader
209
+ : loadTranslations || apiRouteLoader;
210
+
211
+ const config: I18nConfig = {
212
+ defaultLanguage,
213
+ fallbackLanguage,
214
+ supportedLanguages: defaultLanguages,
215
+ namespaces,
216
+ loadTranslations: translationLoader === 'custom' && loadTranslations
217
+ ? loadTranslations
218
+ : defaultFileLoader,
219
+ initialTranslations, // SSR 번역 데이터 전달
220
+ debug,
221
+ missingKeyHandler: (key: string, language?: string, namespace?: string) => {
222
+ if (debug) {
223
+ console.warn(`Missing translation key: ${key}`);
224
+
225
+ // Debug SDK와 연동하여 누락 키 추적
226
+ if (typeof window !== 'undefined' && window.__I18N_DEBUG_MISSING_KEYS__) {
227
+ const missingKeys = window.__I18N_DEBUG_MISSING_KEYS__;
228
+ const keyPath = `${language || 'unknown'}:${namespace || 'unknown'}`;
229
+ missingKeys[keyPath] = missingKeys[keyPath] || [];
230
+ if (!missingKeys[keyPath].includes(key)) {
231
+ missingKeys[keyPath].push(key);
232
+ }
233
+ }
234
+
235
+ return `[MISSING: ${key}]`;
236
+ }
237
+ return key.split('.').pop() || key;
238
+ },
239
+ errorHandler: (error: unknown, language: string, namespace: string) => {
240
+ if (debug) {
241
+ console.error(`Translation error for ${language}:${namespace}:`, error);
242
+ }
243
+ },
244
+ // autoLanguageSync는 기본적으로 false (Zustand 어댑터 등 외부에서 직접 처리하는 경우)
245
+ // 필요시 options에서 명시적으로 true로 설정 가능
246
+ autoLanguageSync: options?.autoLanguageSync ?? false
247
+ };
248
+
249
+ // Provider 컴포넌트 반환
250
+ return function CoreI18nProvider({ children }: { children: React.ReactNode }) {
251
+ return React.createElement(I18nProvider, { config, children });
252
+ };
253
+ }
254
+
255
+ // 기본 번역 데이터는 공통 유틸리티에서 가져옴
256
+ import { getDefaultTranslations } from './utils/default-translations';
257
+
258
+ /**
259
+ * 가장 기본적인 Provider (최소한의 설정)
260
+ */
261
+ export function CoreProvider({ children }: { children: React.ReactNode }) {
262
+ return createCoreI18n()({ children });
263
+ }
264
+
265
+ /**
266
+ * 언어별 Provider (언어만 지정)
267
+ */
268
+ export function createLanguageProvider(language: string) {
269
+ return createCoreI18n({ defaultLanguage: language });
270
+ }
271
+
272
+ /**
273
+ * 네임스페이스별 Provider (네임스페이스만 지정)
274
+ */
275
+ export function createNamespaceProvider(namespaces: string[]) {
276
+ return createCoreI18n({ namespaces });
277
+ }
278
+
279
+ /**
280
+ * 커스텀 로더 Provider (사용자 정의 번역 로더 사용)
281
+ */
282
+ export function createCustomLoaderProvider(
283
+ loadTranslations: (language: string, namespace: string) => Promise<Record<string, string>>
284
+ ) {
285
+ return createCoreI18n({ loadTranslations });
286
+ }
287
+
288
+ // 핵심 훅들 export
289
+ export { useTranslation, useLanguageChange, useI18n };
290
+
291
+ // Provider export
292
+ export { I18nProvider };
293
+
294
+ // 핵심 클래스/함수들 export
295
+ export { Translator, ssrTranslate, serverTranslate };
296
+
297
+ // 타입 export
298
298
  export type { I18nConfig };