@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.
@@ -1,443 +1,443 @@
1
- export interface TranslationNamespace {
2
- [key: string]: string | TranslationNamespace;
3
- }
4
-
5
- export interface TranslationData {
6
- [namespace: string]: TranslationNamespace;
7
- }
8
-
9
- export interface LanguageConfig {
10
- code: string;
11
- name: string;
12
- nativeName: string;
13
- tone?: 'emotional' | 'encouraging' | 'calm' | 'gentle' | 'formal' | 'technical' | 'informal';
14
- formality?: 'informal' | 'casual' | 'formal' | 'polite';
15
- }
16
-
17
- // 더 구체적인 설정 타입 정의
18
- export interface I18nConfig {
19
- defaultLanguage: string;
20
- fallbackLanguage?: string;
21
- supportedLanguages: LanguageConfig[];
22
- namespaces?: string[];
23
- loadTranslations: (language: string, namespace: string) => Promise<TranslationNamespace>;
24
- // SSR에서 전달된 초기 번역 데이터 (네트워크 요청 없이 사용)
25
- initialTranslations?: Record<string, Record<string, TranslationNamespace>>;
26
- // 개발 모드 설정
27
- debug?: boolean;
28
- // 번역 키가 없을 때의 동작
29
- missingKeyHandler?: (key: string, language: string, namespace: string) => string;
30
- // 번역 로딩 실패 시 동작
31
- errorHandler?: (error: Error, language: string, namespace: string) => void;
32
- // 캐시 설정
33
- cacheOptions?: {
34
- maxSize?: number;
35
- ttl?: number; // Time to live in milliseconds
36
- };
37
- // 성능 설정
38
- performanceOptions?: {
39
- preloadAll?: boolean;
40
- lazyLoad?: boolean;
41
- };
42
- // 에러 처리 설정
43
- errorHandling?: {
44
- recoveryStrategy?: ErrorRecoveryStrategy;
45
- logging?: ErrorLoggingConfig;
46
- userFriendlyMessages?: boolean;
47
- suppressErrors?: boolean;
48
- };
49
- // 자동 언어 전환 이벤트 처리 (withDefaultConfig용)
50
- autoLanguageSync?: boolean;
51
- }
52
-
53
- // 에러 타입 정의
54
- export interface TranslationError extends Error {
55
- code: 'MISSING_KEY' | 'LOAD_FAILED' | 'INVALID_KEY' | 'NETWORK_ERROR' | 'INITIALIZATION_ERROR' | 'VALIDATION_ERROR' | 'CACHE_ERROR' | 'FALLBACK_LOAD_FAILED' | 'INITIALIZATION_FAILED' | 'RETRY_FAILED';
56
- language?: string;
57
- namespace?: string;
58
- key?: string;
59
- originalError?: Error;
60
- retryCount?: number;
61
- maxRetries?: number;
62
- timestamp: number;
63
- context?: Record<string, unknown>;
64
- }
65
-
66
- // 에러 복구 전략
67
- export interface ErrorRecoveryStrategy {
68
- maxRetries: number;
69
- retryDelay: number; // milliseconds
70
- backoffMultiplier: number;
71
- shouldRetry: (error: TranslationError) => boolean;
72
- onRetry: (error: TranslationError, attempt: number) => void;
73
- onMaxRetriesExceeded: (error: TranslationError) => void;
74
- }
75
-
76
- // 에러 로깅 설정
77
- export interface ErrorLoggingConfig {
78
- enabled: boolean;
79
- level: 'error' | 'warn' | 'info' | 'debug';
80
- includeStack: boolean;
81
- includeContext: boolean;
82
- customLogger?: (error: TranslationError) => void;
83
- }
84
-
85
- // 사용자 친화적 에러 메시지
86
- export interface UserFriendlyError {
87
- code: string;
88
- message: string;
89
- suggestion?: string;
90
- action?: string;
91
- severity: 'low' | 'medium' | 'high' | 'critical';
92
- }
93
-
94
- // 캐시 엔트리 타입
95
- export interface CacheEntry {
96
- data: TranslationNamespace;
97
- timestamp: number;
98
- ttl: number;
99
- }
100
-
101
- // 로딩 상태 타입
102
- export interface LoadingState {
103
- isLoading: boolean;
104
- error: TranslationError | null;
105
- progress?: {
106
- loaded: number;
107
- total: number;
108
- };
109
- }
110
-
111
- // 번역 결과 타입
112
- export interface TranslationResult {
113
- text: string;
114
- language: string;
115
- namespace: string;
116
- key: string;
117
- isFallback: boolean;
118
- cacheHit: boolean;
119
- }
120
-
121
- export interface I18nContextType {
122
- currentLanguage: string;
123
- setLanguage: (language: string) => void;
124
- // hua-api 스타일의 간단한 번역 함수
125
- t: (key: string, language?: string) => string;
126
- // 파라미터가 있는 번역 함수
127
- tWithParams: (key: string, params?: TranslationParams, language?: string) => string;
128
- // 기존 비동기 번역 함수 (하위 호환성)
129
- tAsync: (key: string, params?: TranslationParams) => Promise<string>;
130
- // 기존 동기 번역 함수 (하위 호환성)
131
- tSync: (key: string, namespace?: string, params?: TranslationParams) => string;
132
- // 원시 값 가져오기 (배열, 객체 포함)
133
- getRawValue: (key: string, language?: string) => unknown;
134
- isLoading: boolean;
135
- error: TranslationError | null;
136
- supportedLanguages: LanguageConfig[];
137
- // 초기화 상태
138
- isInitialized: boolean;
139
- // 개발자 도구
140
- debug: {
141
- getCurrentLanguage: () => string;
142
- getSupportedLanguages: () => string[];
143
- getLoadedNamespaces: () => string[];
144
- getAllTranslations: () => Record<string, Record<string, unknown>>;
145
- isReady: () => boolean;
146
- getInitializationError: () => TranslationError | null;
147
- clearCache: () => void;
148
- reloadTranslations: () => Promise<void>;
149
- getCacheStats: () => {
150
- size: number;
151
- hits: number;
152
- misses: number;
153
- };
154
- };
155
- }
156
-
157
- export interface TranslationParams {
158
- [key: string]: string | number;
159
- }
160
-
161
- // 타입 안전한 번역 키 시스템 (단순화된 버전)
162
- export type TranslationKey<T> = T extends Record<string, unknown>
163
- ? {
164
- [K in keyof T]: T[K] extends string
165
- ? K
166
- : T[K] extends Record<string, unknown>
167
- ? `${K & string}.${TranslationKey<T[K]> & string}`
168
- : never;
169
- }[keyof T]
170
- : never;
171
-
172
- // 타입 안전한 번역 함수들
173
- export interface TypedI18nContextType<T extends TranslationData> extends Omit<I18nContextType, 't' | 'tWithParams' | 'tSync'> {
174
- // 타입 안전한 번역 함수
175
- t: <K extends TranslationKey<T>>(key: K, language?: string) => string;
176
- tWithParams: <K extends TranslationKey<T>>(key: K, params?: TranslationParams, language?: string) => string;
177
- tSync: <K extends TranslationKey<T>>(key: K, namespace?: string, params?: TranslationParams) => string;
178
- }
179
-
180
- // 간단한 번역 키 타입 (무한 재귀 방지)
181
- export type SimpleTranslationKey = string;
182
-
183
- // 고급 번역 키 타입 (제한된 깊이)
184
- export type TranslationKeyLegacy<T extends Record<string, unknown>, D extends number = 3> =
185
- [D] extends [never]
186
- ? never
187
- : T extends Record<string, unknown>
188
- ? {
189
- [K in keyof T]: T[K] extends string
190
- ? K
191
- : T[K] extends Record<string, unknown>
192
- ? `${K & string}.${TranslationKeyLegacy<T[K], Prev<D>> & string}`
193
- : never;
194
- }[keyof T]
195
- : never;
196
-
197
- type Prev<T extends number> = T extends 0 ? never : T extends 1 ? 0 : T extends 2 ? 1 : T extends 3 ? 2 : never;
198
-
199
- // 유틸리티 타입들
200
- export type ExtractTranslationKeys<T> = T extends Record<string, unknown>
201
- ? {
202
- [K in keyof T]: T[K] extends string
203
- ? K
204
- : T[K] extends Record<string, unknown>
205
- ? `${K & string}.${ExtractTranslationKeys<T[K]> & string}`
206
- : never;
207
- }[keyof T]
208
- : never;
209
-
210
- // 네임스페이스별 타입 정의를 위한 헬퍼
211
- export type NamespaceKeys<T extends TranslationData, N extends keyof T> = ExtractTranslationKeys<T[N]>;
212
-
213
- // 타입 안전한 번역 키 생성 헬퍼
214
- export const createTranslationKey = <T extends TranslationData, N extends keyof T, K extends NamespaceKeys<T, N>>(
215
- namespace: N,
216
- key: K
217
- ): `${N & string}.${K & string}` => `${String(namespace)}.${String(key)}` as `${N & string}.${K & string}`;
218
-
219
- // 타입 가드 함수들
220
- export function isTranslationNamespace(value: unknown): value is TranslationNamespace {
221
- return typeof value === 'object' && value !== null && !Array.isArray(value);
222
- }
223
-
224
- export function isLanguageConfig(value: unknown): value is LanguageConfig {
225
- return (
226
- typeof value === 'object' &&
227
- value !== null &&
228
- typeof (value as LanguageConfig).code === 'string' &&
229
- typeof (value as LanguageConfig).name === 'string' &&
230
- typeof (value as LanguageConfig).nativeName === 'string'
231
- );
232
- }
233
-
234
- export function isTranslationError(value: unknown): value is TranslationError {
235
- return (
236
- value instanceof Error &&
237
- typeof (value as TranslationError).code === 'string' &&
238
- ['MISSING_KEY', 'LOAD_FAILED', 'INVALID_KEY', 'NETWORK_ERROR', 'INITIALIZATION_ERROR'].includes(
239
- (value as TranslationError).code
240
- )
241
- );
242
- }
243
-
244
- // 설정 검증 함수
245
- export function validateI18nConfig(config: unknown): config is I18nConfig {
246
- if (!config || typeof config !== 'object') {
247
- return false;
248
- }
249
-
250
- const c = config as I18nConfig;
251
-
252
- return (
253
- typeof c.defaultLanguage === 'string' &&
254
- Array.isArray(c.supportedLanguages) &&
255
- c.supportedLanguages.every(isLanguageConfig) &&
256
- typeof c.loadTranslations === 'function'
257
- );
258
- }
259
-
260
- // 에러 처리 유틸리티 함수들
261
- export function createTranslationError(
262
- code: TranslationError['code'],
263
- message: string,
264
- originalError?: Error,
265
- context?: {
266
- language?: string;
267
- namespace?: string;
268
- key?: string;
269
- retryCount?: number;
270
- maxRetries?: number;
271
- }
272
- ): TranslationError {
273
- const error = new Error(message) as TranslationError;
274
- error.code = code;
275
- error.language = context?.language;
276
- error.namespace = context?.namespace;
277
- error.key = context?.key;
278
- error.originalError = originalError;
279
- error.retryCount = context?.retryCount || 0;
280
- error.maxRetries = context?.maxRetries || 3;
281
- error.timestamp = Date.now();
282
- error.name = 'TranslationError';
283
- return error;
284
- }
285
-
286
- // 사용자 친화적 에러 메시지 생성
287
- export function createUserFriendlyError(error: TranslationError): UserFriendlyError {
288
- const errorMessages: Record<TranslationError['code'], UserFriendlyError> = {
289
- MISSING_KEY: {
290
- code: 'MISSING_KEY',
291
- message: '번역 키를 찾을 수 없습니다',
292
- suggestion: '번역 파일에 해당 키가 있는지 확인해주세요',
293
- action: '번역 파일 업데이트',
294
- severity: 'low'
295
- },
296
- LOAD_FAILED: {
297
- code: 'LOAD_FAILED',
298
- message: '번역 파일을 불러오는데 실패했습니다',
299
- suggestion: '네트워크 연결과 파일 경로를 확인해주세요',
300
- action: '재시도',
301
- severity: 'medium'
302
- },
303
- INVALID_KEY: {
304
- code: 'INVALID_KEY',
305
- message: '잘못된 번역 키 형식입니다',
306
- suggestion: '키 형식을 "namespace.key" 형태로 입력해주세요',
307
- action: '키 형식 수정',
308
- severity: 'low'
309
- },
310
- NETWORK_ERROR: {
311
- code: 'NETWORK_ERROR',
312
- message: '네트워크 오류가 발생했습니다',
313
- suggestion: '인터넷 연결을 확인하고 다시 시도해주세요',
314
- action: '재시도',
315
- severity: 'high'
316
- },
317
- INITIALIZATION_ERROR: {
318
- code: 'INITIALIZATION_ERROR',
319
- message: '번역 시스템 초기화에 실패했습니다',
320
- suggestion: '설정을 확인하고 페이지를 새로고침해주세요',
321
- action: '페이지 새로고침',
322
- severity: 'critical'
323
- },
324
- VALIDATION_ERROR: {
325
- code: 'VALIDATION_ERROR',
326
- message: '설정 검증에 실패했습니다',
327
- suggestion: '번역 설정을 확인해주세요',
328
- action: '설정 수정',
329
- severity: 'medium'
330
- },
331
- CACHE_ERROR: {
332
- code: 'CACHE_ERROR',
333
- message: '캐시 처리 중 오류가 발생했습니다',
334
- suggestion: '캐시를 초기화하고 다시 시도해주세요',
335
- action: '캐시 초기화',
336
- severity: 'low'
337
- },
338
- FALLBACK_LOAD_FAILED: {
339
- code: 'FALLBACK_LOAD_FAILED',
340
- message: '폴백 언어 로딩에 실패했습니다',
341
- suggestion: '폴백 언어 파일을 확인해주세요',
342
- action: '폴백 언어 파일 수정',
343
- severity: 'medium'
344
- },
345
- INITIALIZATION_FAILED: {
346
- code: 'INITIALIZATION_FAILED',
347
- message: '초기화에 실패했습니다',
348
- suggestion: '설정을 확인하고 다시 시도해주세요',
349
- action: '설정 확인',
350
- severity: 'critical'
351
- },
352
- RETRY_FAILED: {
353
- code: 'RETRY_FAILED',
354
- message: '재시도에 실패했습니다',
355
- suggestion: '네트워크 연결을 확인해주세요',
356
- action: '재시도',
357
- severity: 'high'
358
- }
359
- };
360
-
361
- return errorMessages[error.code];
362
- }
363
-
364
- // 에러 복구 가능 여부 확인
365
- export function isRecoverableError(error: TranslationError): boolean {
366
- const recoverableCodes: TranslationError['code'][] = [
367
- 'LOAD_FAILED',
368
- 'NETWORK_ERROR',
369
- 'CACHE_ERROR'
370
- ];
371
-
372
- return recoverableCodes.includes(error.code) &&
373
- (error.retryCount || 0) < (error.maxRetries || 3);
374
- }
375
-
376
- // 기본 에러 복구 전략
377
- export const defaultErrorRecoveryStrategy: ErrorRecoveryStrategy = {
378
- maxRetries: 3,
379
- retryDelay: 1000,
380
- backoffMultiplier: 2,
381
- shouldRetry: isRecoverableError,
382
- onRetry: (error: TranslationError, attempt: number) => {
383
- console.warn(`Retrying translation operation (attempt ${attempt}/${error.maxRetries}):`, error.message);
384
- },
385
- onMaxRetriesExceeded: (error: TranslationError) => {
386
- console.error('Max retries exceeded for translation operation:', error.message);
387
- }
388
- };
389
-
390
- // 기본 에러 로깅 설정
391
- export const defaultErrorLoggingConfig: ErrorLoggingConfig = {
392
- enabled: true,
393
- level: 'error',
394
- includeStack: true,
395
- includeContext: true,
396
- customLogger: undefined
397
- };
398
-
399
- // 에러 로깅 함수
400
- export function logTranslationError(
401
- error: TranslationError,
402
- config: ErrorLoggingConfig = defaultErrorLoggingConfig
403
- ): void {
404
- if (!config.enabled) return;
405
-
406
- const logData: Record<string, unknown> = {
407
- code: error.code,
408
- message: error.message,
409
- timestamp: error.timestamp,
410
- retryCount: error.retryCount,
411
- maxRetries: error.maxRetries
412
- };
413
-
414
- if (config.includeContext) {
415
- logData.language = error.language;
416
- logData.namespace = error.namespace;
417
- logData.key = error.key;
418
- logData.context = error.context;
419
- }
420
-
421
- if (config.includeStack && error.stack) {
422
- logData.stack = error.stack;
423
- }
424
-
425
- if (config.customLogger) {
426
- config.customLogger(error);
427
- } else {
428
- switch (config.level) {
429
- case 'error':
430
- console.error('Translation Error:', logData);
431
- break;
432
- case 'warn':
433
- console.warn('Translation Warning:', logData);
434
- break;
435
- case 'info':
436
- console.info('Translation Info:', logData);
437
- break;
438
- case 'debug':
439
- console.debug('Translation Debug:', logData);
440
- break;
441
- }
442
- }
1
+ export interface TranslationNamespace {
2
+ [key: string]: string | TranslationNamespace;
3
+ }
4
+
5
+ export interface TranslationData {
6
+ [namespace: string]: TranslationNamespace;
7
+ }
8
+
9
+ export interface LanguageConfig {
10
+ code: string;
11
+ name: string;
12
+ nativeName: string;
13
+ tone?: 'emotional' | 'encouraging' | 'calm' | 'gentle' | 'formal' | 'technical' | 'informal';
14
+ formality?: 'informal' | 'casual' | 'formal' | 'polite';
15
+ }
16
+
17
+ // 더 구체적인 설정 타입 정의
18
+ export interface I18nConfig {
19
+ defaultLanguage: string;
20
+ fallbackLanguage?: string;
21
+ supportedLanguages: LanguageConfig[];
22
+ namespaces?: string[];
23
+ loadTranslations: (language: string, namespace: string) => Promise<TranslationNamespace>;
24
+ // SSR에서 전달된 초기 번역 데이터 (네트워크 요청 없이 사용)
25
+ initialTranslations?: Record<string, Record<string, TranslationNamespace>>;
26
+ // 개발 모드 설정
27
+ debug?: boolean;
28
+ // 번역 키가 없을 때의 동작
29
+ missingKeyHandler?: (key: string, language: string, namespace: string) => string;
30
+ // 번역 로딩 실패 시 동작
31
+ errorHandler?: (error: Error, language: string, namespace: string) => void;
32
+ // 캐시 설정
33
+ cacheOptions?: {
34
+ maxSize?: number;
35
+ ttl?: number; // Time to live in milliseconds
36
+ };
37
+ // 성능 설정
38
+ performanceOptions?: {
39
+ preloadAll?: boolean;
40
+ lazyLoad?: boolean;
41
+ };
42
+ // 에러 처리 설정
43
+ errorHandling?: {
44
+ recoveryStrategy?: ErrorRecoveryStrategy;
45
+ logging?: ErrorLoggingConfig;
46
+ userFriendlyMessages?: boolean;
47
+ suppressErrors?: boolean;
48
+ };
49
+ // 자동 언어 전환 이벤트 처리 (withDefaultConfig용)
50
+ autoLanguageSync?: boolean;
51
+ }
52
+
53
+ // 에러 타입 정의
54
+ export interface TranslationError extends Error {
55
+ code: 'MISSING_KEY' | 'LOAD_FAILED' | 'INVALID_KEY' | 'NETWORK_ERROR' | 'INITIALIZATION_ERROR' | 'VALIDATION_ERROR' | 'CACHE_ERROR' | 'FALLBACK_LOAD_FAILED' | 'INITIALIZATION_FAILED' | 'RETRY_FAILED';
56
+ language?: string;
57
+ namespace?: string;
58
+ key?: string;
59
+ originalError?: Error;
60
+ retryCount?: number;
61
+ maxRetries?: number;
62
+ timestamp: number;
63
+ context?: Record<string, unknown>;
64
+ }
65
+
66
+ // 에러 복구 전략
67
+ export interface ErrorRecoveryStrategy {
68
+ maxRetries: number;
69
+ retryDelay: number; // milliseconds
70
+ backoffMultiplier: number;
71
+ shouldRetry: (error: TranslationError) => boolean;
72
+ onRetry: (error: TranslationError, attempt: number) => void;
73
+ onMaxRetriesExceeded: (error: TranslationError) => void;
74
+ }
75
+
76
+ // 에러 로깅 설정
77
+ export interface ErrorLoggingConfig {
78
+ enabled: boolean;
79
+ level: 'error' | 'warn' | 'info' | 'debug';
80
+ includeStack: boolean;
81
+ includeContext: boolean;
82
+ customLogger?: (error: TranslationError) => void;
83
+ }
84
+
85
+ // 사용자 친화적 에러 메시지
86
+ export interface UserFriendlyError {
87
+ code: string;
88
+ message: string;
89
+ suggestion?: string;
90
+ action?: string;
91
+ severity: 'low' | 'medium' | 'high' | 'critical';
92
+ }
93
+
94
+ // 캐시 엔트리 타입
95
+ export interface CacheEntry {
96
+ data: TranslationNamespace;
97
+ timestamp: number;
98
+ ttl: number;
99
+ }
100
+
101
+ // 로딩 상태 타입
102
+ export interface LoadingState {
103
+ isLoading: boolean;
104
+ error: TranslationError | null;
105
+ progress?: {
106
+ loaded: number;
107
+ total: number;
108
+ };
109
+ }
110
+
111
+ // 번역 결과 타입
112
+ export interface TranslationResult {
113
+ text: string;
114
+ language: string;
115
+ namespace: string;
116
+ key: string;
117
+ isFallback: boolean;
118
+ cacheHit: boolean;
119
+ }
120
+
121
+ export interface I18nContextType {
122
+ currentLanguage: string;
123
+ setLanguage: (language: string) => void;
124
+ // hua-api 스타일의 간단한 번역 함수
125
+ t: (key: string, language?: string) => string;
126
+ // 파라미터가 있는 번역 함수
127
+ tWithParams: (key: string, params?: TranslationParams, language?: string) => string;
128
+ // 기존 비동기 번역 함수 (하위 호환성)
129
+ tAsync: (key: string, params?: TranslationParams) => Promise<string>;
130
+ // 기존 동기 번역 함수 (하위 호환성)
131
+ tSync: (key: string, namespace?: string, params?: TranslationParams) => string;
132
+ // 원시 값 가져오기 (배열, 객체 포함)
133
+ getRawValue: (key: string, language?: string) => unknown;
134
+ isLoading: boolean;
135
+ error: TranslationError | null;
136
+ supportedLanguages: LanguageConfig[];
137
+ // 초기화 상태
138
+ isInitialized: boolean;
139
+ // 개발자 도구
140
+ debug: {
141
+ getCurrentLanguage: () => string;
142
+ getSupportedLanguages: () => string[];
143
+ getLoadedNamespaces: () => string[];
144
+ getAllTranslations: () => Record<string, Record<string, unknown>>;
145
+ isReady: () => boolean;
146
+ getInitializationError: () => TranslationError | null;
147
+ clearCache: () => void;
148
+ reloadTranslations: () => Promise<void>;
149
+ getCacheStats: () => {
150
+ size: number;
151
+ hits: number;
152
+ misses: number;
153
+ };
154
+ };
155
+ }
156
+
157
+ export interface TranslationParams {
158
+ [key: string]: string | number;
159
+ }
160
+
161
+ // 타입 안전한 번역 키 시스템 (단순화된 버전)
162
+ export type TranslationKey<T> = T extends Record<string, unknown>
163
+ ? {
164
+ [K in keyof T]: T[K] extends string
165
+ ? K
166
+ : T[K] extends Record<string, unknown>
167
+ ? `${K & string}.${TranslationKey<T[K]> & string}`
168
+ : never;
169
+ }[keyof T]
170
+ : never;
171
+
172
+ // 타입 안전한 번역 함수들
173
+ export interface TypedI18nContextType<T extends TranslationData> extends Omit<I18nContextType, 't' | 'tWithParams' | 'tSync'> {
174
+ // 타입 안전한 번역 함수
175
+ t: <K extends TranslationKey<T>>(key: K, language?: string) => string;
176
+ tWithParams: <K extends TranslationKey<T>>(key: K, params?: TranslationParams, language?: string) => string;
177
+ tSync: <K extends TranslationKey<T>>(key: K, namespace?: string, params?: TranslationParams) => string;
178
+ }
179
+
180
+ // 간단한 번역 키 타입 (무한 재귀 방지)
181
+ export type SimpleTranslationKey = string;
182
+
183
+ // 고급 번역 키 타입 (제한된 깊이)
184
+ export type TranslationKeyLegacy<T extends Record<string, unknown>, D extends number = 3> =
185
+ [D] extends [never]
186
+ ? never
187
+ : T extends Record<string, unknown>
188
+ ? {
189
+ [K in keyof T]: T[K] extends string
190
+ ? K
191
+ : T[K] extends Record<string, unknown>
192
+ ? `${K & string}.${TranslationKeyLegacy<T[K], Prev<D>> & string}`
193
+ : never;
194
+ }[keyof T]
195
+ : never;
196
+
197
+ type Prev<T extends number> = T extends 0 ? never : T extends 1 ? 0 : T extends 2 ? 1 : T extends 3 ? 2 : never;
198
+
199
+ // 유틸리티 타입들
200
+ export type ExtractTranslationKeys<T> = T extends Record<string, unknown>
201
+ ? {
202
+ [K in keyof T]: T[K] extends string
203
+ ? K
204
+ : T[K] extends Record<string, unknown>
205
+ ? `${K & string}.${ExtractTranslationKeys<T[K]> & string}`
206
+ : never;
207
+ }[keyof T]
208
+ : never;
209
+
210
+ // 네임스페이스별 타입 정의를 위한 헬퍼
211
+ export type NamespaceKeys<T extends TranslationData, N extends keyof T> = ExtractTranslationKeys<T[N]>;
212
+
213
+ // 타입 안전한 번역 키 생성 헬퍼
214
+ export const createTranslationKey = <T extends TranslationData, N extends keyof T, K extends NamespaceKeys<T, N>>(
215
+ namespace: N,
216
+ key: K
217
+ ): `${N & string}.${K & string}` => `${String(namespace)}.${String(key)}` as `${N & string}.${K & string}`;
218
+
219
+ // 타입 가드 함수들
220
+ export function isTranslationNamespace(value: unknown): value is TranslationNamespace {
221
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
222
+ }
223
+
224
+ export function isLanguageConfig(value: unknown): value is LanguageConfig {
225
+ return (
226
+ typeof value === 'object' &&
227
+ value !== null &&
228
+ typeof (value as LanguageConfig).code === 'string' &&
229
+ typeof (value as LanguageConfig).name === 'string' &&
230
+ typeof (value as LanguageConfig).nativeName === 'string'
231
+ );
232
+ }
233
+
234
+ export function isTranslationError(value: unknown): value is TranslationError {
235
+ return (
236
+ value instanceof Error &&
237
+ typeof (value as TranslationError).code === 'string' &&
238
+ ['MISSING_KEY', 'LOAD_FAILED', 'INVALID_KEY', 'NETWORK_ERROR', 'INITIALIZATION_ERROR'].includes(
239
+ (value as TranslationError).code
240
+ )
241
+ );
242
+ }
243
+
244
+ // 설정 검증 함수
245
+ export function validateI18nConfig(config: unknown): config is I18nConfig {
246
+ if (!config || typeof config !== 'object') {
247
+ return false;
248
+ }
249
+
250
+ const c = config as I18nConfig;
251
+
252
+ return (
253
+ typeof c.defaultLanguage === 'string' &&
254
+ Array.isArray(c.supportedLanguages) &&
255
+ c.supportedLanguages.every(isLanguageConfig) &&
256
+ typeof c.loadTranslations === 'function'
257
+ );
258
+ }
259
+
260
+ // 에러 처리 유틸리티 함수들
261
+ export function createTranslationError(
262
+ code: TranslationError['code'],
263
+ message: string,
264
+ originalError?: Error,
265
+ context?: {
266
+ language?: string;
267
+ namespace?: string;
268
+ key?: string;
269
+ retryCount?: number;
270
+ maxRetries?: number;
271
+ }
272
+ ): TranslationError {
273
+ const error = new Error(message) as TranslationError;
274
+ error.code = code;
275
+ error.language = context?.language;
276
+ error.namespace = context?.namespace;
277
+ error.key = context?.key;
278
+ error.originalError = originalError;
279
+ error.retryCount = context?.retryCount || 0;
280
+ error.maxRetries = context?.maxRetries || 3;
281
+ error.timestamp = Date.now();
282
+ error.name = 'TranslationError';
283
+ return error;
284
+ }
285
+
286
+ // 사용자 친화적 에러 메시지 생성
287
+ export function createUserFriendlyError(error: TranslationError): UserFriendlyError {
288
+ const errorMessages: Record<TranslationError['code'], UserFriendlyError> = {
289
+ MISSING_KEY: {
290
+ code: 'MISSING_KEY',
291
+ message: '번역 키를 찾을 수 없습니다',
292
+ suggestion: '번역 파일에 해당 키가 있는지 확인해주세요',
293
+ action: '번역 파일 업데이트',
294
+ severity: 'low'
295
+ },
296
+ LOAD_FAILED: {
297
+ code: 'LOAD_FAILED',
298
+ message: '번역 파일을 불러오는데 실패했습니다',
299
+ suggestion: '네트워크 연결과 파일 경로를 확인해주세요',
300
+ action: '재시도',
301
+ severity: 'medium'
302
+ },
303
+ INVALID_KEY: {
304
+ code: 'INVALID_KEY',
305
+ message: '잘못된 번역 키 형식입니다',
306
+ suggestion: '키 형식을 "namespace.key" 형태로 입력해주세요',
307
+ action: '키 형식 수정',
308
+ severity: 'low'
309
+ },
310
+ NETWORK_ERROR: {
311
+ code: 'NETWORK_ERROR',
312
+ message: '네트워크 오류가 발생했습니다',
313
+ suggestion: '인터넷 연결을 확인하고 다시 시도해주세요',
314
+ action: '재시도',
315
+ severity: 'high'
316
+ },
317
+ INITIALIZATION_ERROR: {
318
+ code: 'INITIALIZATION_ERROR',
319
+ message: '번역 시스템 초기화에 실패했습니다',
320
+ suggestion: '설정을 확인하고 페이지를 새로고침해주세요',
321
+ action: '페이지 새로고침',
322
+ severity: 'critical'
323
+ },
324
+ VALIDATION_ERROR: {
325
+ code: 'VALIDATION_ERROR',
326
+ message: '설정 검증에 실패했습니다',
327
+ suggestion: '번역 설정을 확인해주세요',
328
+ action: '설정 수정',
329
+ severity: 'medium'
330
+ },
331
+ CACHE_ERROR: {
332
+ code: 'CACHE_ERROR',
333
+ message: '캐시 처리 중 오류가 발생했습니다',
334
+ suggestion: '캐시를 초기화하고 다시 시도해주세요',
335
+ action: '캐시 초기화',
336
+ severity: 'low'
337
+ },
338
+ FALLBACK_LOAD_FAILED: {
339
+ code: 'FALLBACK_LOAD_FAILED',
340
+ message: '폴백 언어 로딩에 실패했습니다',
341
+ suggestion: '폴백 언어 파일을 확인해주세요',
342
+ action: '폴백 언어 파일 수정',
343
+ severity: 'medium'
344
+ },
345
+ INITIALIZATION_FAILED: {
346
+ code: 'INITIALIZATION_FAILED',
347
+ message: '초기화에 실패했습니다',
348
+ suggestion: '설정을 확인하고 다시 시도해주세요',
349
+ action: '설정 확인',
350
+ severity: 'critical'
351
+ },
352
+ RETRY_FAILED: {
353
+ code: 'RETRY_FAILED',
354
+ message: '재시도에 실패했습니다',
355
+ suggestion: '네트워크 연결을 확인해주세요',
356
+ action: '재시도',
357
+ severity: 'high'
358
+ }
359
+ };
360
+
361
+ return errorMessages[error.code];
362
+ }
363
+
364
+ // 에러 복구 가능 여부 확인
365
+ export function isRecoverableError(error: TranslationError): boolean {
366
+ const recoverableCodes: TranslationError['code'][] = [
367
+ 'LOAD_FAILED',
368
+ 'NETWORK_ERROR',
369
+ 'CACHE_ERROR'
370
+ ];
371
+
372
+ return recoverableCodes.includes(error.code) &&
373
+ (error.retryCount || 0) < (error.maxRetries || 3);
374
+ }
375
+
376
+ // 기본 에러 복구 전략
377
+ export const defaultErrorRecoveryStrategy: ErrorRecoveryStrategy = {
378
+ maxRetries: 3,
379
+ retryDelay: 1000,
380
+ backoffMultiplier: 2,
381
+ shouldRetry: isRecoverableError,
382
+ onRetry: (error: TranslationError, attempt: number) => {
383
+ console.warn(`Retrying translation operation (attempt ${attempt}/${error.maxRetries}):`, error.message);
384
+ },
385
+ onMaxRetriesExceeded: (error: TranslationError) => {
386
+ console.error('Max retries exceeded for translation operation:', error.message);
387
+ }
388
+ };
389
+
390
+ // 기본 에러 로깅 설정
391
+ export const defaultErrorLoggingConfig: ErrorLoggingConfig = {
392
+ enabled: true,
393
+ level: 'error',
394
+ includeStack: true,
395
+ includeContext: true,
396
+ customLogger: undefined
397
+ };
398
+
399
+ // 에러 로깅 함수
400
+ export function logTranslationError(
401
+ error: TranslationError,
402
+ config: ErrorLoggingConfig = defaultErrorLoggingConfig
403
+ ): void {
404
+ if (!config.enabled) return;
405
+
406
+ const logData: Record<string, unknown> = {
407
+ code: error.code,
408
+ message: error.message,
409
+ timestamp: error.timestamp,
410
+ retryCount: error.retryCount,
411
+ maxRetries: error.maxRetries
412
+ };
413
+
414
+ if (config.includeContext) {
415
+ logData.language = error.language;
416
+ logData.namespace = error.namespace;
417
+ logData.key = error.key;
418
+ logData.context = error.context;
419
+ }
420
+
421
+ if (config.includeStack && error.stack) {
422
+ logData.stack = error.stack;
423
+ }
424
+
425
+ if (config.customLogger) {
426
+ config.customLogger(error);
427
+ } else {
428
+ switch (config.level) {
429
+ case 'error':
430
+ console.error('Translation Error:', logData);
431
+ break;
432
+ case 'warn':
433
+ console.warn('Translation Warning:', logData);
434
+ break;
435
+ case 'info':
436
+ console.info('Translation Info:', logData);
437
+ break;
438
+ case 'debug':
439
+ console.debug('Translation Debug:', logData);
440
+ break;
441
+ }
442
+ }
443
443
  }