@hua-labs/i18n-core 2.1.0 → 2.2.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.
@@ -1,7 +1,7 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // Plural (ICU / Intl.PluralRules)
3
3
  // ---------------------------------------------------------------------------
4
- export type PluralCategory = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';
4
+ export type PluralCategory = "zero" | "one" | "two" | "few" | "many" | "other";
5
5
 
6
6
  export interface PluralValue {
7
7
  zero?: string;
@@ -12,20 +12,95 @@ export interface PluralValue {
12
12
  other: string; // 필수
13
13
  }
14
14
 
15
- const PLURAL_CATEGORIES = new Set<string>(['zero', 'one', 'two', 'few', 'many', 'other']);
15
+ const PLURAL_CATEGORIES = new Set<string>([
16
+ "zero",
17
+ "one",
18
+ "two",
19
+ "few",
20
+ "many",
21
+ "other",
22
+ ]);
16
23
 
17
24
  export function isPluralValue(value: unknown): value is PluralValue {
18
- if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
25
+ if (typeof value !== "object" || value === null || Array.isArray(value))
26
+ return false;
19
27
  const obj = value as Record<string, unknown>;
20
28
  const keys = Object.keys(obj);
21
29
  return (
22
30
  keys.length > 0 &&
23
- keys.every(k => PLURAL_CATEGORIES.has(k)) &&
24
- Object.values(obj).every(v => typeof v === 'string') &&
25
- typeof obj.other === 'string'
31
+ keys.every((k) => PLURAL_CATEGORIES.has(k)) &&
32
+ Object.values(obj).every((v) => typeof v === "string") &&
33
+ typeof obj.other === "string"
26
34
  );
27
35
  }
28
36
 
37
+ // ---------------------------------------------------------------------------
38
+ // Platform Adapter (cross-platform: Web / React Native / Flutter bridge)
39
+ // ---------------------------------------------------------------------------
40
+
41
+ /**
42
+ * 플랫폼별 i18n 어댑터 인터페이스.
43
+ *
44
+ * Web(기본), React Native, Flutter 브릿지 등 환경에 맞는 구현체를 주입.
45
+ * I18nConfig.platformAdapter로 전달하면 useI18n이 자동으로 사용한다.
46
+ */
47
+ export interface I18nPlatformAdapter {
48
+ /** 디바이스/브라우저 언어 코드 (e.g. 'ko', 'en'). 감지 불가 시 undefined */
49
+ getDeviceLanguage(): string | undefined;
50
+
51
+ /** 시스템 언어 변경 이벤트 구독. 해제 함수 반환 */
52
+ onLanguageChange(cb: (lang: string) => void): () => void;
53
+ }
54
+
55
+ /**
56
+ * Web 기본 어댑터.
57
+ * navigator.language + CustomEvent 기반. SSR에서는 안전하게 no-op.
58
+ */
59
+ export const webPlatformAdapter: I18nPlatformAdapter = {
60
+ getDeviceLanguage() {
61
+ if (
62
+ typeof globalThis !== "undefined" &&
63
+ typeof navigator !== "undefined" &&
64
+ navigator.language
65
+ ) {
66
+ return navigator.language.slice(0, 2).toLowerCase();
67
+ }
68
+ return undefined;
69
+ },
70
+ onLanguageChange(cb) {
71
+ if (
72
+ typeof globalThis === "undefined" ||
73
+ typeof window === "undefined" ||
74
+ typeof CustomEvent === "undefined"
75
+ ) {
76
+ return () => {};
77
+ }
78
+ const handler = (e: Event) => {
79
+ const lang = (e as CustomEvent).detail;
80
+ if (typeof lang === "string") cb(lang);
81
+ };
82
+ window.addEventListener("huaI18nLanguageChange", handler);
83
+ window.addEventListener("i18nLanguageChanged", handler);
84
+ return () => {
85
+ window.removeEventListener("huaI18nLanguageChange", handler);
86
+ window.removeEventListener("i18nLanguageChanged", handler);
87
+ };
88
+ },
89
+ };
90
+
91
+ /**
92
+ * Headless 어댑터 (SSR / 테스트 / Flutter 브릿지용).
93
+ * 언어 감지·이벤트 없이 config.defaultLanguage만 사용.
94
+ */
95
+ export const headlessPlatformAdapter: I18nPlatformAdapter = {
96
+ getDeviceLanguage() {
97
+ return undefined;
98
+ },
99
+ onLanguageChange() {
100
+ return () => {};
101
+ },
102
+ };
103
+
29
104
  // ---------------------------------------------------------------------------
30
105
  // Translation namespace
31
106
  // ---------------------------------------------------------------------------
@@ -41,8 +116,15 @@ export interface LanguageConfig {
41
116
  code: string;
42
117
  name: string;
43
118
  nativeName: string;
44
- tone?: 'emotional' | 'encouraging' | 'calm' | 'gentle' | 'formal' | 'technical' | 'informal';
45
- formality?: 'informal' | 'casual' | 'formal' | 'polite';
119
+ tone?:
120
+ | "emotional"
121
+ | "encouraging"
122
+ | "calm"
123
+ | "gentle"
124
+ | "formal"
125
+ | "technical"
126
+ | "informal";
127
+ formality?: "informal" | "casual" | "formal" | "polite";
46
128
  }
47
129
 
48
130
  // 더 구체적인 설정 타입 정의
@@ -51,13 +133,20 @@ export interface I18nConfig {
51
133
  fallbackLanguage?: string;
52
134
  supportedLanguages: LanguageConfig[];
53
135
  namespaces?: string[];
54
- loadTranslations: (language: string, namespace: string) => Promise<TranslationNamespace>;
136
+ loadTranslations: (
137
+ language: string,
138
+ namespace: string,
139
+ ) => Promise<TranslationNamespace>;
55
140
  // SSR에서 전달된 초기 번역 데이터 (네트워크 요청 없이 사용)
56
141
  initialTranslations?: Record<string, Record<string, TranslationNamespace>>;
57
142
  // 개발 모드 설정
58
143
  debug?: boolean;
59
144
  // 번역 키가 없을 때의 동작
60
- missingKeyHandler?: (key: string, language: string, namespace: string) => string;
145
+ missingKeyHandler?: (
146
+ key: string,
147
+ language: string,
148
+ namespace: string,
149
+ ) => string;
61
150
  // 번역 로딩 실패 시 동작
62
151
  errorHandler?: (error: Error, language: string, namespace: string) => void;
63
152
  // 캐시 설정
@@ -79,11 +168,23 @@ export interface I18nConfig {
79
168
  };
80
169
  // 자동 언어 전환 이벤트 처리 (withDefaultConfig용)
81
170
  autoLanguageSync?: boolean;
171
+ // 플랫폼 어댑터 (기본: webPlatformAdapter)
172
+ platformAdapter?: I18nPlatformAdapter;
82
173
  }
83
174
 
84
175
  // 에러 타입 정의
85
176
  export interface TranslationError extends Error {
86
- code: 'MISSING_KEY' | 'LOAD_FAILED' | 'INVALID_KEY' | 'NETWORK_ERROR' | 'INITIALIZATION_ERROR' | 'VALIDATION_ERROR' | 'CACHE_ERROR' | 'FALLBACK_LOAD_FAILED' | 'INITIALIZATION_FAILED' | 'RETRY_FAILED';
177
+ code:
178
+ | "MISSING_KEY"
179
+ | "LOAD_FAILED"
180
+ | "INVALID_KEY"
181
+ | "NETWORK_ERROR"
182
+ | "INITIALIZATION_ERROR"
183
+ | "VALIDATION_ERROR"
184
+ | "CACHE_ERROR"
185
+ | "FALLBACK_LOAD_FAILED"
186
+ | "INITIALIZATION_FAILED"
187
+ | "RETRY_FAILED";
87
188
  language?: string;
88
189
  namespace?: string;
89
190
  key?: string;
@@ -107,7 +208,7 @@ export interface ErrorRecoveryStrategy {
107
208
  // 에러 로깅 설정
108
209
  export interface ErrorLoggingConfig {
109
210
  enabled: boolean;
110
- level: 'error' | 'warn' | 'info' | 'debug';
211
+ level: "error" | "warn" | "info" | "debug";
111
212
  includeStack: boolean;
112
213
  includeContext: boolean;
113
214
  customLogger?: (error: TranslationError) => void;
@@ -119,7 +220,7 @@ export interface UserFriendlyError {
119
220
  message: string;
120
221
  suggestion?: string;
121
222
  action?: string;
122
- severity: 'low' | 'medium' | 'high' | 'critical';
223
+ severity: "low" | "medium" | "high" | "critical";
123
224
  }
124
225
 
125
226
  // 캐시 엔트리 타입
@@ -153,15 +254,28 @@ export interface I18nContextType {
153
254
  currentLanguage: string;
154
255
  setLanguage: (language: string) => void | Promise<void>;
155
256
  // 통합 번역 함수: t(key), t(key, language), t(key, params), t(key, params, language)
156
- t: (key: ResolveStringKey, paramsOrLang?: TranslationParams | string, language?: string) => string;
257
+ t: (
258
+ key: ResolveStringKey,
259
+ paramsOrLang?: TranslationParams | string,
260
+ language?: string,
261
+ ) => string;
157
262
  // 복수형 번역 함수: tPlural(key, count), tPlural(key, count, params), tPlural(key, count, params, language)
158
- tPlural: (key: ResolvePluralKey, count: number, params?: Record<string, unknown>, language?: string) => string;
159
- // 기존 비동기 번역 함수 (하위 호환성)
263
+ tPlural: (
264
+ key: ResolvePluralKey,
265
+ count: number,
266
+ params?: Record<string, unknown>,
267
+ language?: string,
268
+ ) => string;
269
+ /** @deprecated Use `t()` instead. Will be removed in a future major version. */
160
270
  tAsync: (key: string, params?: TranslationParams) => Promise<string>;
161
- // 기존 동기 번역 함수 (하위 호환성)
162
- tSync: (key: string, namespace?: string, params?: TranslationParams) => string;
163
- // 원시 값 가져오기 (배열, 객체 포함)
164
- getRawValue: (key: string, language?: string) => unknown;
271
+ /** @deprecated Use `t()` instead. Will be removed in a future major version. */
272
+ tSync: (
273
+ key: string,
274
+ namespace?: string,
275
+ params?: TranslationParams,
276
+ ) => string;
277
+ // 원시 값 가져오기 (배열, 객체 포함) — 제네릭으로 타입 캐스팅 가능
278
+ getRawValue: <T = unknown>(key: string, language?: string) => T | undefined;
165
279
  // 배열 번역 값 가져오기 (타입 안전)
166
280
  tArray: (key: ResolveArrayKey, language?: string) => string[];
167
281
  isLoading: boolean;
@@ -187,6 +301,18 @@ export interface I18nContextType {
187
301
  };
188
302
  }
189
303
 
304
+ /**
305
+ * Parameters for the `t()` function.
306
+ *
307
+ * `defaultValue` is a reserved key: when translation lookup fails entirely,
308
+ * the string provided here is returned instead of empty string (production)
309
+ * or the raw key (debug). Interpolation variables still work:
310
+ *
311
+ * ```ts
312
+ * t('missing:key', { defaultValue: 'Hello {{name}}', name: 'World' })
313
+ * // → 'Hello World'
314
+ * ```
315
+ */
190
316
  export interface TranslationParams {
191
317
  [key: string]: string | number;
192
318
  }
@@ -206,115 +332,162 @@ export interface TranslationParams {
206
332
  *
207
333
  * augmentation이 없으면 string으로 fallback (breaking 없음).
208
334
  */
209
-
335
+
210
336
  export interface TypedTranslationKeys {}
211
337
 
212
338
  /** augmentation 시 좁혀진 타입, 미설정 시 string */
213
- export type ResolveStringKey = TypedTranslationKeys extends { stringKey: infer K } ? K & string : string;
214
- export type ResolveArrayKey = TypedTranslationKeys extends { arrayKey: infer K } ? K & string : string;
215
- export type ResolvePluralKey = TypedTranslationKeys extends { pluralKey: infer K } ? K & string : string;
339
+ export type ResolveStringKey = TypedTranslationKeys extends {
340
+ stringKey: infer K;
341
+ }
342
+ ? K & string
343
+ : string;
344
+ export type ResolveArrayKey = TypedTranslationKeys extends { arrayKey: infer K }
345
+ ? K & string
346
+ : string;
347
+ export type ResolvePluralKey = TypedTranslationKeys extends {
348
+ pluralKey: infer K;
349
+ }
350
+ ? K & string
351
+ : string;
216
352
 
217
353
  // 타입 안전한 번역 키 시스템 (단순화된 버전)
218
- export type TranslationKey<T> = T extends Record<string, unknown>
219
- ? {
220
- [K in keyof T]: T[K] extends string
221
- ? K
222
- : T[K] extends Record<string, unknown>
223
- ? `${K & string}.${TranslationKey<T[K]> & string}`
224
- : never;
225
- }[keyof T]
226
- : never;
354
+ export type TranslationKey<T> =
355
+ T extends Record<string, unknown>
356
+ ? {
357
+ [K in keyof T]: T[K] extends string
358
+ ? K
359
+ : T[K] extends Record<string, unknown>
360
+ ? `${K & string}.${TranslationKey<T[K]> & string}`
361
+ : never;
362
+ }[keyof T]
363
+ : never;
227
364
 
228
365
  // 타입 안전한 번역 함수들
229
- export interface TypedI18nContextType<T extends TranslationData> extends Omit<I18nContextType, 't' | 'tSync'> {
366
+ export interface TypedI18nContextType<T extends TranslationData> extends Omit<
367
+ I18nContextType,
368
+ "t" | "tSync"
369
+ > {
230
370
  // 타입 안전한 번역 함수
231
- t: <K extends TranslationKey<T>>(key: K, paramsOrLang?: TranslationParams | string, language?: string) => string;
232
- tSync: <K extends TranslationKey<T>>(key: K, namespace?: string, params?: TranslationParams) => string;
371
+ t: <K extends TranslationKey<T>>(
372
+ key: K,
373
+ paramsOrLang?: TranslationParams | string,
374
+ language?: string,
375
+ ) => string;
376
+ tSync: <K extends TranslationKey<T>>(
377
+ key: K,
378
+ namespace?: string,
379
+ params?: TranslationParams,
380
+ ) => string;
233
381
  }
234
382
 
235
383
  // 간단한 번역 키 타입 (무한 재귀 방지)
236
384
  export type SimpleTranslationKey = string;
237
385
 
238
386
  // 고급 번역 키 타입 (제한된 깊이)
239
- export type TranslationKeyLegacy<T extends Record<string, unknown>, D extends number = 3> =
240
- [D] extends [never]
241
- ? never
242
- : T extends Record<string, unknown>
243
- ? {
244
- [K in keyof T]: T[K] extends string
245
- ? K
246
- : T[K] extends Record<string, unknown>
247
- ? `${K & string}.${TranslationKeyLegacy<T[K], Prev<D>> & string}`
248
- : never;
249
- }[keyof T]
250
- : never;
251
-
252
- type Prev<T extends number> = T extends 0 ? never : T extends 1 ? 0 : T extends 2 ? 1 : T extends 3 ? 2 : never;
387
+ export type TranslationKeyLegacy<
388
+ T extends Record<string, unknown>,
389
+ D extends number = 3,
390
+ > = [D] extends [never]
391
+ ? never
392
+ : T extends Record<string, unknown>
393
+ ? {
394
+ [K in keyof T]: T[K] extends string
395
+ ? K
396
+ : T[K] extends Record<string, unknown>
397
+ ? `${K & string}.${TranslationKeyLegacy<T[K], Prev<D>> & string}`
398
+ : never;
399
+ }[keyof T]
400
+ : never;
401
+
402
+ type Prev<T extends number> = T extends 0
403
+ ? never
404
+ : T extends 1
405
+ ? 0
406
+ : T extends 2
407
+ ? 1
408
+ : T extends 3
409
+ ? 2
410
+ : never;
253
411
 
254
412
  // 유틸리티 타입들
255
- export type ExtractTranslationKeys<T> = T extends Record<string, unknown>
256
- ? {
257
- [K in keyof T]: T[K] extends string
258
- ? K
259
- : T[K] extends Record<string, unknown>
260
- ? `${K & string}.${ExtractTranslationKeys<T[K]> & string}`
261
- : never;
262
- }[keyof T]
263
- : never;
413
+ export type ExtractTranslationKeys<T> =
414
+ T extends Record<string, unknown>
415
+ ? {
416
+ [K in keyof T]: T[K] extends string
417
+ ? K
418
+ : T[K] extends Record<string, unknown>
419
+ ? `${K & string}.${ExtractTranslationKeys<T[K]> & string}`
420
+ : never;
421
+ }[keyof T]
422
+ : never;
264
423
 
265
424
  // 네임스페이스별 타입 정의를 위한 헬퍼
266
- export type NamespaceKeys<T extends TranslationData, N extends keyof T> = ExtractTranslationKeys<T[N]>;
425
+ export type NamespaceKeys<
426
+ T extends TranslationData,
427
+ N extends keyof T,
428
+ > = ExtractTranslationKeys<T[N]>;
267
429
 
268
430
  // 타입 안전한 번역 키 생성 헬퍼
269
- export const createTranslationKey = <T extends TranslationData, N extends keyof T, K extends NamespaceKeys<T, N>>(
431
+ export const createTranslationKey = <
432
+ T extends TranslationData,
433
+ N extends keyof T,
434
+ K extends NamespaceKeys<T, N>,
435
+ >(
270
436
  namespace: N,
271
- key: K
272
- ): `${N & string}.${K & string}` => `${String(namespace)}.${String(key)}` as `${N & string}.${K & string}`;
437
+ key: K,
438
+ ): `${N & string}.${K & string}` =>
439
+ `${String(namespace)}.${String(key)}` as `${N & string}.${K & string}`;
273
440
 
274
441
  // 타입 가드 함수들
275
- export function isTranslationNamespace(value: unknown): value is TranslationNamespace {
276
- return typeof value === 'object' && value !== null && !Array.isArray(value);
442
+ export function isTranslationNamespace(
443
+ value: unknown,
444
+ ): value is TranslationNamespace {
445
+ return typeof value === "object" && value !== null && !Array.isArray(value);
277
446
  }
278
447
 
279
448
  export function isLanguageConfig(value: unknown): value is LanguageConfig {
280
449
  return (
281
- typeof value === 'object' &&
450
+ typeof value === "object" &&
282
451
  value !== null &&
283
- typeof (value as LanguageConfig).code === 'string' &&
284
- typeof (value as LanguageConfig).name === 'string' &&
285
- typeof (value as LanguageConfig).nativeName === 'string'
452
+ typeof (value as LanguageConfig).code === "string" &&
453
+ typeof (value as LanguageConfig).name === "string" &&
454
+ typeof (value as LanguageConfig).nativeName === "string"
286
455
  );
287
456
  }
288
457
 
289
458
  export function isTranslationError(value: unknown): value is TranslationError {
290
459
  return (
291
460
  value instanceof Error &&
292
- typeof (value as TranslationError).code === 'string' &&
293
- ['MISSING_KEY', 'LOAD_FAILED', 'INVALID_KEY', 'NETWORK_ERROR', 'INITIALIZATION_ERROR'].includes(
294
- (value as TranslationError).code
295
- )
461
+ typeof (value as TranslationError).code === "string" &&
462
+ [
463
+ "MISSING_KEY",
464
+ "LOAD_FAILED",
465
+ "INVALID_KEY",
466
+ "NETWORK_ERROR",
467
+ "INITIALIZATION_ERROR",
468
+ ].includes((value as TranslationError).code)
296
469
  );
297
470
  }
298
471
 
299
472
  // 설정 검증 함수
300
473
  export function validateI18nConfig(config: unknown): config is I18nConfig {
301
- if (!config || typeof config !== 'object') {
474
+ if (!config || typeof config !== "object") {
302
475
  return false;
303
476
  }
304
477
 
305
478
  const c = config as I18nConfig;
306
-
479
+
307
480
  return (
308
- typeof c.defaultLanguage === 'string' &&
481
+ typeof c.defaultLanguage === "string" &&
309
482
  Array.isArray(c.supportedLanguages) &&
310
483
  c.supportedLanguages.every(isLanguageConfig) &&
311
- typeof c.loadTranslations === 'function'
484
+ typeof c.loadTranslations === "function"
312
485
  );
313
486
  }
314
487
 
315
488
  // 에러 처리 유틸리티 함수들
316
489
  export function createTranslationError(
317
- code: TranslationError['code'],
490
+ code: TranslationError["code"],
318
491
  message: string,
319
492
  originalError?: Error,
320
493
  context?: {
@@ -323,7 +496,7 @@ export function createTranslationError(
323
496
  key?: string;
324
497
  retryCount?: number;
325
498
  maxRetries?: number;
326
- }
499
+ },
327
500
  ): TranslationError {
328
501
  const error = new Error(message) as TranslationError;
329
502
  error.code = code;
@@ -334,83 +507,85 @@ export function createTranslationError(
334
507
  error.retryCount = context?.retryCount || 0;
335
508
  error.maxRetries = context?.maxRetries || 3;
336
509
  error.timestamp = Date.now();
337
- error.name = 'TranslationError';
510
+ error.name = "TranslationError";
338
511
  return error;
339
512
  }
340
513
 
341
514
  // 사용자 친화적 에러 메시지 생성
342
- export function createUserFriendlyError(error: TranslationError): UserFriendlyError {
343
- const errorMessages: Record<TranslationError['code'], UserFriendlyError> = {
515
+ export function createUserFriendlyError(
516
+ error: TranslationError,
517
+ ): UserFriendlyError {
518
+ const errorMessages: Record<TranslationError["code"], UserFriendlyError> = {
344
519
  MISSING_KEY: {
345
- code: 'MISSING_KEY',
346
- message: '번역 키를 찾을 수 없습니다',
347
- suggestion: '번역 파일에 해당 키가 있는지 확인해주세요',
348
- action: '번역 파일 업데이트',
349
- severity: 'low'
520
+ code: "MISSING_KEY",
521
+ message: "번역 키를 찾을 수 없습니다",
522
+ suggestion: "번역 파일에 해당 키가 있는지 확인해주세요",
523
+ action: "번역 파일 업데이트",
524
+ severity: "low",
350
525
  },
351
526
  LOAD_FAILED: {
352
- code: 'LOAD_FAILED',
353
- message: '번역 파일을 불러오는데 실패했습니다',
354
- suggestion: '네트워크 연결과 파일 경로를 확인해주세요',
355
- action: '재시도',
356
- severity: 'medium'
527
+ code: "LOAD_FAILED",
528
+ message: "번역 파일을 불러오는데 실패했습니다",
529
+ suggestion: "네트워크 연결과 파일 경로를 확인해주세요",
530
+ action: "재시도",
531
+ severity: "medium",
357
532
  },
358
533
  INVALID_KEY: {
359
- code: 'INVALID_KEY',
360
- message: '잘못된 번역 키 형식입니다',
534
+ code: "INVALID_KEY",
535
+ message: "잘못된 번역 키 형식입니다",
361
536
  suggestion: '키 형식을 "namespace.key" 형태로 입력해주세요',
362
- action: '키 형식 수정',
363
- severity: 'low'
537
+ action: "키 형식 수정",
538
+ severity: "low",
364
539
  },
365
540
  NETWORK_ERROR: {
366
- code: 'NETWORK_ERROR',
367
- message: '네트워크 오류가 발생했습니다',
368
- suggestion: '인터넷 연결을 확인하고 다시 시도해주세요',
369
- action: '재시도',
370
- severity: 'high'
541
+ code: "NETWORK_ERROR",
542
+ message: "네트워크 오류가 발생했습니다",
543
+ suggestion: "인터넷 연결을 확인하고 다시 시도해주세요",
544
+ action: "재시도",
545
+ severity: "high",
371
546
  },
372
547
  INITIALIZATION_ERROR: {
373
- code: 'INITIALIZATION_ERROR',
374
- message: '번역 시스템 초기화에 실패했습니다',
375
- suggestion: '설정을 확인하고 페이지를 새로고침해주세요',
376
- action: '페이지 새로고침',
377
- severity: 'critical'
548
+ code: "INITIALIZATION_ERROR",
549
+ message: "번역 시스템 초기화에 실패했습니다",
550
+ suggestion: "설정을 확인하고 페이지를 새로고침해주세요",
551
+ action: "페이지 새로고침",
552
+ severity: "critical",
378
553
  },
379
554
  VALIDATION_ERROR: {
380
- code: 'VALIDATION_ERROR',
381
- message: '설정 검증에 실패했습니다',
382
- suggestion: '번역 설정을 확인해주세요',
383
- action: '설정 수정',
384
- severity: 'medium'
555
+ code: "VALIDATION_ERROR",
556
+ message: "설정 검증에 실패했습니다",
557
+ suggestion: "번역 설정을 확인해주세요",
558
+ action: "설정 수정",
559
+ severity: "medium",
385
560
  },
386
561
  CACHE_ERROR: {
387
- code: 'CACHE_ERROR',
388
- message: '캐시 처리 중 오류가 발생했습니다',
389
- suggestion: '캐시를 초기화하고 다시 시도해주세요',
390
- action: '캐시 초기화',
391
- severity: 'low'
562
+ code: "CACHE_ERROR",
563
+ message: "캐시 처리 중 오류가 발생했습니다",
564
+ suggestion: "캐시를 초기화하고 다시 시도해주세요",
565
+ action: "캐시 초기화",
566
+ severity: "low",
392
567
  },
393
568
  FALLBACK_LOAD_FAILED: {
394
- code: 'FALLBACK_LOAD_FAILED',
395
- message: '폴백 언어 로딩에 실패했습니다',
396
- suggestion: '폴백 언어 파일을 확인해주세요',
397
- action: '폴백 언어 파일 수정',
398
- severity: 'medium'
569
+ code: "FALLBACK_LOAD_FAILED",
570
+ message: "폴백 언어 로딩에 실패했습니다",
571
+ suggestion: "폴백 언어 파일을 확인해주세요",
572
+ action: "폴백 언어 파일 수정",
573
+ severity: "medium",
399
574
  },
400
575
  INITIALIZATION_FAILED: {
401
- code: 'INITIALIZATION_FAILED',
402
- message: '초기화에 실패했습니다',
403
- suggestion: '설정을 확인하고 다시 시도해주세요',
404
- action: '설정 확인',
405
- severity: 'critical'
576
+ code: "INITIALIZATION_FAILED",
577
+ message: "초기화에 실패했습니다",
578
+ suggestion: "설정을 확인하고 다시 시도해주세요",
579
+ action: "설정 확인",
580
+ severity: "critical",
406
581
  },
407
582
  RETRY_FAILED: {
408
- code: 'RETRY_FAILED',
409
- message: '재시도에 실패했습니다',
410
- suggestion: '네트워크 연결을 확인해주세요',
411
- action: '재시도',
412
- severity: 'high'
413
- }
583
+ code: "RETRY_FAILED",
584
+ message: "재시도에 실패했습니다",
585
+ suggestion: "네트워크 연결을 확인해주세요",
586
+ action: "재시도",
587
+ severity: "high",
588
+ },
414
589
  };
415
590
 
416
591
  return errorMessages[error.code];
@@ -418,14 +593,16 @@ export function createUserFriendlyError(error: TranslationError): UserFriendlyEr
418
593
 
419
594
  // 에러 복구 가능 여부 확인
420
595
  export function isRecoverableError(error: TranslationError): boolean {
421
- const recoverableCodes: TranslationError['code'][] = [
422
- 'LOAD_FAILED',
423
- 'NETWORK_ERROR',
424
- 'CACHE_ERROR'
596
+ const recoverableCodes: TranslationError["code"][] = [
597
+ "LOAD_FAILED",
598
+ "NETWORK_ERROR",
599
+ "CACHE_ERROR",
425
600
  ];
426
-
427
- return recoverableCodes.includes(error.code) &&
428
- (error.retryCount || 0) < (error.maxRetries || 3);
601
+
602
+ return (
603
+ recoverableCodes.includes(error.code) &&
604
+ (error.retryCount || 0) < (error.maxRetries || 3)
605
+ );
429
606
  }
430
607
 
431
608
  // 기본 에러 복구 전략
@@ -435,26 +612,33 @@ export const defaultErrorRecoveryStrategy: ErrorRecoveryStrategy = {
435
612
  backoffMultiplier: 2,
436
613
  shouldRetry: isRecoverableError,
437
614
  onRetry: (error: TranslationError, attempt: number) => {
438
- if (process.env.NODE_ENV !== 'production') console.warn(`Retrying translation operation (attempt ${attempt}/${error.maxRetries}):`, error.message);
615
+ if (process.env.NODE_ENV !== "production")
616
+ console.warn(
617
+ `Retrying translation operation (attempt ${attempt}/${error.maxRetries}):`,
618
+ error.message,
619
+ );
439
620
  },
440
621
  onMaxRetriesExceeded: (error: TranslationError) => {
441
- console.error('Max retries exceeded for translation operation:', error.message);
442
- }
622
+ console.error(
623
+ "Max retries exceeded for translation operation:",
624
+ error.message,
625
+ );
626
+ },
443
627
  };
444
628
 
445
629
  // 기본 에러 로깅 설정
446
630
  export const defaultErrorLoggingConfig: ErrorLoggingConfig = {
447
631
  enabled: true,
448
- level: 'error',
632
+ level: "error",
449
633
  includeStack: true,
450
634
  includeContext: true,
451
- customLogger: undefined
635
+ customLogger: undefined,
452
636
  };
453
637
 
454
638
  // 에러 로깅 함수
455
639
  export function logTranslationError(
456
- error: TranslationError,
457
- config: ErrorLoggingConfig = defaultErrorLoggingConfig
640
+ error: TranslationError,
641
+ config: ErrorLoggingConfig = defaultErrorLoggingConfig,
458
642
  ): void {
459
643
  if (!config.enabled) return;
460
644
 
@@ -463,7 +647,7 @@ export function logTranslationError(
463
647
  message: error.message,
464
648
  timestamp: error.timestamp,
465
649
  retryCount: error.retryCount,
466
- maxRetries: error.maxRetries
650
+ maxRetries: error.maxRetries,
467
651
  };
468
652
 
469
653
  if (config.includeContext) {
@@ -481,18 +665,19 @@ export function logTranslationError(
481
665
  config.customLogger(error);
482
666
  } else {
483
667
  switch (config.level) {
484
- case 'error':
485
- console.error('Translation Error:', logData);
668
+ case "error":
669
+ console.error("Translation Error:", logData);
486
670
  break;
487
- case 'warn':
488
- if (process.env.NODE_ENV !== 'production') console.warn('Translation Warning:', logData);
671
+ case "warn":
672
+ if (process.env.NODE_ENV !== "production")
673
+ console.warn("Translation Warning:", logData);
489
674
  break;
490
- case 'info':
491
- console.info('Translation Info:', logData);
675
+ case "info":
676
+ console.info("Translation Info:", logData);
492
677
  break;
493
- case 'debug':
494
- console.debug('Translation Debug:', logData);
678
+ case "debug":
679
+ console.debug("Translation Debug:", logData);
495
680
  break;
496
681
  }
497
682
  }
498
- }
683
+ }