@hua-labs/i18n-core 1.0.0
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/LICENSE +21 -0
- package/README.md +636 -0
- package/dist/components/MissingKeyOverlay.d.ts +33 -0
- package/dist/components/MissingKeyOverlay.d.ts.map +1 -0
- package/dist/components/MissingKeyOverlay.js +138 -0
- package/dist/components/MissingKeyOverlay.js.map +1 -0
- package/dist/core/debug-tools.d.ts +37 -0
- package/dist/core/debug-tools.d.ts.map +1 -0
- package/dist/core/debug-tools.js +241 -0
- package/dist/core/debug-tools.js.map +1 -0
- package/dist/core/i18n-resource.d.ts +59 -0
- package/dist/core/i18n-resource.d.ts.map +1 -0
- package/dist/core/i18n-resource.js +153 -0
- package/dist/core/i18n-resource.js.map +1 -0
- package/dist/core/lazy-loader.d.ts +82 -0
- package/dist/core/lazy-loader.d.ts.map +1 -0
- package/dist/core/lazy-loader.js +193 -0
- package/dist/core/lazy-loader.js.map +1 -0
- package/dist/core/translator-factory.d.ts +50 -0
- package/dist/core/translator-factory.d.ts.map +1 -0
- package/dist/core/translator-factory.js +117 -0
- package/dist/core/translator-factory.js.map +1 -0
- package/dist/core/translator.d.ts +202 -0
- package/dist/core/translator.d.ts.map +1 -0
- package/dist/core/translator.js +912 -0
- package/dist/core/translator.js.map +1 -0
- package/dist/hooks/useI18n.d.ts +39 -0
- package/dist/hooks/useI18n.d.ts.map +1 -0
- package/dist/hooks/useI18n.js +531 -0
- package/dist/hooks/useI18n.js.map +1 -0
- package/dist/hooks/useTranslation.d.ts +55 -0
- package/dist/hooks/useTranslation.d.ts.map +1 -0
- package/dist/hooks/useTranslation.js +58 -0
- package/dist/hooks/useTranslation.js.map +1 -0
- package/dist/index.d.ts +162 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +191 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +162 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +191 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/default-translations.d.ts +20 -0
- package/dist/utils/default-translations.d.ts.map +1 -0
- package/dist/utils/default-translations.js +123 -0
- package/dist/utils/default-translations.js.map +1 -0
- package/package.json +60 -0
- package/src/components/MissingKeyOverlay.tsx +223 -0
- package/src/core/debug-tools.ts +298 -0
- package/src/core/i18n-resource.ts +180 -0
- package/src/core/lazy-loader.ts +255 -0
- package/src/core/translator-factory.ts +137 -0
- package/src/core/translator.tsx +1194 -0
- package/src/hooks/useI18n.tsx +595 -0
- package/src/hooks/useTranslation.tsx +62 -0
- package/src/index.ts +298 -0
- package/src/types/index.ts +443 -0
- package/src/utils/default-translations.ts +129 -0
|
@@ -0,0 +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
|
+
}
|
|
443
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 기본 번역 데이터 유틸리티
|
|
3
|
+
*
|
|
4
|
+
* 번역 파일을 로드할 수 없을 때 사용되는 기본 번역 데이터를 제공합니다.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 기본 번역 데이터
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_TRANSLATIONS: Record<string, Record<string, Record<string, string>>> = {
|
|
11
|
+
ko: {
|
|
12
|
+
common: {
|
|
13
|
+
welcome: "환영합니다",
|
|
14
|
+
greeting: "안녕하세요",
|
|
15
|
+
goodbye: "안녕히 가세요",
|
|
16
|
+
loading: "로딩 중...",
|
|
17
|
+
error: "오류가 발생했습니다",
|
|
18
|
+
success: "성공했습니다",
|
|
19
|
+
cancel: "취소",
|
|
20
|
+
confirm: "확인",
|
|
21
|
+
save: "저장",
|
|
22
|
+
delete: "삭제",
|
|
23
|
+
edit: "편집",
|
|
24
|
+
add: "추가",
|
|
25
|
+
search: "검색",
|
|
26
|
+
filter: "필터",
|
|
27
|
+
sort: "정렬",
|
|
28
|
+
refresh: "새로고침",
|
|
29
|
+
back: "뒤로",
|
|
30
|
+
next: "다음",
|
|
31
|
+
previous: "이전",
|
|
32
|
+
home: "홈",
|
|
33
|
+
about: "소개",
|
|
34
|
+
contact: "연락처",
|
|
35
|
+
settings: "설정",
|
|
36
|
+
profile: "프로필",
|
|
37
|
+
logout: "로그아웃",
|
|
38
|
+
login: "로그인",
|
|
39
|
+
register: "회원가입"
|
|
40
|
+
},
|
|
41
|
+
auth: {
|
|
42
|
+
login: "로그인",
|
|
43
|
+
logout: "로그아웃",
|
|
44
|
+
register: "회원가입",
|
|
45
|
+
email: "이메일",
|
|
46
|
+
password: "비밀번호",
|
|
47
|
+
forgot_password: "비밀번호 찾기",
|
|
48
|
+
remember_me: "로그인 상태 유지"
|
|
49
|
+
},
|
|
50
|
+
errors: {
|
|
51
|
+
not_found: "페이지를 찾을 수 없습니다",
|
|
52
|
+
server_error: "서버 오류가 발생했습니다",
|
|
53
|
+
network_error: "네트워크 오류가 발생했습니다",
|
|
54
|
+
unauthorized: "인증이 필요합니다",
|
|
55
|
+
forbidden: "접근이 거부되었습니다"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
en: {
|
|
59
|
+
common: {
|
|
60
|
+
welcome: "Welcome",
|
|
61
|
+
greeting: "Hello",
|
|
62
|
+
goodbye: "Goodbye",
|
|
63
|
+
loading: "Loading...",
|
|
64
|
+
error: "An error occurred",
|
|
65
|
+
success: "Success",
|
|
66
|
+
cancel: "Cancel",
|
|
67
|
+
confirm: "Confirm",
|
|
68
|
+
save: "Save",
|
|
69
|
+
delete: "Delete",
|
|
70
|
+
edit: "Edit",
|
|
71
|
+
add: "Add",
|
|
72
|
+
search: "Search",
|
|
73
|
+
filter: "Filter",
|
|
74
|
+
sort: "Sort",
|
|
75
|
+
refresh: "Refresh",
|
|
76
|
+
back: "Back",
|
|
77
|
+
next: "Next",
|
|
78
|
+
previous: "Previous",
|
|
79
|
+
home: "Home",
|
|
80
|
+
about: "About",
|
|
81
|
+
contact: "Contact",
|
|
82
|
+
settings: "Settings",
|
|
83
|
+
profile: "Profile",
|
|
84
|
+
logout: "Logout",
|
|
85
|
+
login: "Login",
|
|
86
|
+
register: "Register"
|
|
87
|
+
},
|
|
88
|
+
auth: {
|
|
89
|
+
login: "Login",
|
|
90
|
+
logout: "Logout",
|
|
91
|
+
register: "Register",
|
|
92
|
+
email: "Email",
|
|
93
|
+
password: "Password",
|
|
94
|
+
forgot_password: "Forgot Password",
|
|
95
|
+
remember_me: "Remember Me"
|
|
96
|
+
},
|
|
97
|
+
errors: {
|
|
98
|
+
not_found: "Page not found",
|
|
99
|
+
server_error: "Server error occurred",
|
|
100
|
+
network_error: "Network error occurred",
|
|
101
|
+
unauthorized: "Authentication required",
|
|
102
|
+
forbidden: "Access denied"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 기본 번역 데이터 가져오기
|
|
109
|
+
*
|
|
110
|
+
* @param language - 언어 코드 (예: 'ko', 'en')
|
|
111
|
+
* @param namespace - 네임스페이스 (예: 'common', 'auth')
|
|
112
|
+
* @returns 해당 언어와 네임스페이스의 기본 번역 데이터
|
|
113
|
+
*/
|
|
114
|
+
export function getDefaultTranslations(
|
|
115
|
+
language: string,
|
|
116
|
+
namespace: string
|
|
117
|
+
): Record<string, string> {
|
|
118
|
+
return DEFAULT_TRANSLATIONS[language]?.[namespace] || {};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 모든 기본 번역 데이터 가져오기
|
|
123
|
+
*
|
|
124
|
+
* @returns 모든 언어와 네임스페이스의 기본 번역 데이터
|
|
125
|
+
*/
|
|
126
|
+
export function getAllDefaultTranslations(): Record<string, Record<string, Record<string, string>>> {
|
|
127
|
+
return DEFAULT_TRANSLATIONS;
|
|
128
|
+
}
|
|
129
|
+
|