@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.
- package/LICENSE +21 -0
- package/README.md +9 -3
- package/dist/chunk-4IYWT7MS.mjs +1104 -0
- package/dist/chunk-4IYWT7MS.mjs.map +1 -0
- package/dist/index.cjs +2086 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +22 -22
- package/dist/index.d.ts +249 -0
- package/dist/index.mjs +373 -264
- package/dist/index.mjs.map +1 -1
- package/dist/{server-4TeBq6hp.d.mts → server-CQztOmd-.d.mts} +48 -11
- package/dist/server-CQztOmd-.d.ts +404 -0
- package/dist/{chunk-EZL5TNH5.mjs → server.cjs} +148 -46
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.mts +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.mjs +1 -1
- package/package.json +16 -13
- package/src/__tests__/default-value.test.ts +149 -0
- package/src/components/MissingKeyOverlay.tsx +6 -4
- package/src/core/translator.tsx +392 -195
- package/src/hooks/useI18n.tsx +511 -367
- package/src/index.ts +5 -2
- package/src/types/index.ts +341 -156
- package/dist/chunk-EZL5TNH5.mjs.map +0 -1
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
type PluralCategory = "zero" | "one" | "two" | "few" | "many" | "other";
|
|
2
|
+
interface PluralValue {
|
|
3
|
+
zero?: string;
|
|
4
|
+
one?: string;
|
|
5
|
+
two?: string;
|
|
6
|
+
few?: string;
|
|
7
|
+
many?: string;
|
|
8
|
+
other: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 플랫폼별 i18n 어댑터 인터페이스.
|
|
12
|
+
*
|
|
13
|
+
* Web(기본), React Native, Flutter 브릿지 등 환경에 맞는 구현체를 주입.
|
|
14
|
+
* I18nConfig.platformAdapter로 전달하면 useI18n이 자동으로 사용한다.
|
|
15
|
+
*/
|
|
16
|
+
interface I18nPlatformAdapter {
|
|
17
|
+
/** 디바이스/브라우저 언어 코드 (e.g. 'ko', 'en'). 감지 불가 시 undefined */
|
|
18
|
+
getDeviceLanguage(): string | undefined;
|
|
19
|
+
/** 시스템 언어 변경 이벤트 구독. 해제 함수 반환 */
|
|
20
|
+
onLanguageChange(cb: (lang: string) => void): () => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Web 기본 어댑터.
|
|
24
|
+
* navigator.language + CustomEvent 기반. SSR에서는 안전하게 no-op.
|
|
25
|
+
*/
|
|
26
|
+
declare const webPlatformAdapter: I18nPlatformAdapter;
|
|
27
|
+
/**
|
|
28
|
+
* Headless 어댑터 (SSR / 테스트 / Flutter 브릿지용).
|
|
29
|
+
* 언어 감지·이벤트 없이 config.defaultLanguage만 사용.
|
|
30
|
+
*/
|
|
31
|
+
declare const headlessPlatformAdapter: I18nPlatformAdapter;
|
|
32
|
+
interface TranslationNamespace {
|
|
33
|
+
[key: string]: string | string[] | PluralValue | TranslationNamespace;
|
|
34
|
+
}
|
|
35
|
+
interface LanguageConfig {
|
|
36
|
+
code: string;
|
|
37
|
+
name: string;
|
|
38
|
+
nativeName: string;
|
|
39
|
+
tone?: "emotional" | "encouraging" | "calm" | "gentle" | "formal" | "technical" | "informal";
|
|
40
|
+
formality?: "informal" | "casual" | "formal" | "polite";
|
|
41
|
+
}
|
|
42
|
+
interface I18nConfig {
|
|
43
|
+
defaultLanguage: string;
|
|
44
|
+
fallbackLanguage?: string;
|
|
45
|
+
supportedLanguages: LanguageConfig[];
|
|
46
|
+
namespaces?: string[];
|
|
47
|
+
loadTranslations: (language: string, namespace: string) => Promise<TranslationNamespace>;
|
|
48
|
+
initialTranslations?: Record<string, Record<string, TranslationNamespace>>;
|
|
49
|
+
debug?: boolean;
|
|
50
|
+
missingKeyHandler?: (key: string, language: string, namespace: string) => string;
|
|
51
|
+
errorHandler?: (error: Error, language: string, namespace: string) => void;
|
|
52
|
+
cacheOptions?: {
|
|
53
|
+
maxSize?: number;
|
|
54
|
+
ttl?: number;
|
|
55
|
+
};
|
|
56
|
+
performanceOptions?: {
|
|
57
|
+
preloadAll?: boolean;
|
|
58
|
+
lazyLoad?: boolean;
|
|
59
|
+
};
|
|
60
|
+
errorHandling?: {
|
|
61
|
+
recoveryStrategy?: ErrorRecoveryStrategy;
|
|
62
|
+
logging?: ErrorLoggingConfig;
|
|
63
|
+
userFriendlyMessages?: boolean;
|
|
64
|
+
suppressErrors?: boolean;
|
|
65
|
+
};
|
|
66
|
+
autoLanguageSync?: boolean;
|
|
67
|
+
platformAdapter?: I18nPlatformAdapter;
|
|
68
|
+
}
|
|
69
|
+
interface TranslationError extends Error {
|
|
70
|
+
code: "MISSING_KEY" | "LOAD_FAILED" | "INVALID_KEY" | "NETWORK_ERROR" | "INITIALIZATION_ERROR" | "VALIDATION_ERROR" | "CACHE_ERROR" | "FALLBACK_LOAD_FAILED" | "INITIALIZATION_FAILED" | "RETRY_FAILED";
|
|
71
|
+
language?: string;
|
|
72
|
+
namespace?: string;
|
|
73
|
+
key?: string;
|
|
74
|
+
originalError?: Error;
|
|
75
|
+
retryCount?: number;
|
|
76
|
+
maxRetries?: number;
|
|
77
|
+
timestamp: number;
|
|
78
|
+
context?: Record<string, unknown>;
|
|
79
|
+
}
|
|
80
|
+
interface ErrorRecoveryStrategy {
|
|
81
|
+
maxRetries: number;
|
|
82
|
+
retryDelay: number;
|
|
83
|
+
backoffMultiplier: number;
|
|
84
|
+
shouldRetry: (error: TranslationError) => boolean;
|
|
85
|
+
onRetry: (error: TranslationError, attempt: number) => void;
|
|
86
|
+
onMaxRetriesExceeded: (error: TranslationError) => void;
|
|
87
|
+
}
|
|
88
|
+
interface ErrorLoggingConfig {
|
|
89
|
+
enabled: boolean;
|
|
90
|
+
level: "error" | "warn" | "info" | "debug";
|
|
91
|
+
includeStack: boolean;
|
|
92
|
+
includeContext: boolean;
|
|
93
|
+
customLogger?: (error: TranslationError) => void;
|
|
94
|
+
}
|
|
95
|
+
interface I18nContextType {
|
|
96
|
+
currentLanguage: string;
|
|
97
|
+
setLanguage: (language: string) => void | Promise<void>;
|
|
98
|
+
t: (key: ResolveStringKey, paramsOrLang?: TranslationParams | string, language?: string) => string;
|
|
99
|
+
tPlural: (key: ResolvePluralKey, count: number, params?: Record<string, unknown>, language?: string) => string;
|
|
100
|
+
/** @deprecated Use `t()` instead. Will be removed in a future major version. */
|
|
101
|
+
tAsync: (key: string, params?: TranslationParams) => Promise<string>;
|
|
102
|
+
/** @deprecated Use `t()` instead. Will be removed in a future major version. */
|
|
103
|
+
tSync: (key: string, namespace?: string, params?: TranslationParams) => string;
|
|
104
|
+
getRawValue: <T = unknown>(key: string, language?: string) => T | undefined;
|
|
105
|
+
tArray: (key: ResolveArrayKey, language?: string) => string[];
|
|
106
|
+
isLoading: boolean;
|
|
107
|
+
error: TranslationError | null;
|
|
108
|
+
supportedLanguages: LanguageConfig[];
|
|
109
|
+
isInitialized: boolean;
|
|
110
|
+
debug: {
|
|
111
|
+
getCurrentLanguage: () => string;
|
|
112
|
+
getSupportedLanguages: () => string[];
|
|
113
|
+
getLoadedNamespaces: () => string[];
|
|
114
|
+
getAllTranslations: () => Record<string, Record<string, unknown>>;
|
|
115
|
+
isReady: () => boolean;
|
|
116
|
+
getInitializationError: () => TranslationError | null;
|
|
117
|
+
clearCache: () => void;
|
|
118
|
+
reloadTranslations: () => Promise<void>;
|
|
119
|
+
getCacheStats: () => {
|
|
120
|
+
size: number;
|
|
121
|
+
hits: number;
|
|
122
|
+
misses: number;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Parameters for the `t()` function.
|
|
128
|
+
*
|
|
129
|
+
* `defaultValue` is a reserved key: when translation lookup fails entirely,
|
|
130
|
+
* the string provided here is returned instead of empty string (production)
|
|
131
|
+
* or the raw key (debug). Interpolation variables still work:
|
|
132
|
+
*
|
|
133
|
+
* ```ts
|
|
134
|
+
* t('missing:key', { defaultValue: 'Hello {{name}}', name: 'World' })
|
|
135
|
+
* // → 'Hello World'
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
interface TranslationParams {
|
|
139
|
+
[key: string]: string | number;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 타입 안전한 번역 키를 위한 augmentation point.
|
|
143
|
+
*
|
|
144
|
+
* 앱에서 declaration merging으로 좁힐 수 있음:
|
|
145
|
+
* ```ts
|
|
146
|
+
* declare module '@hua-labs/i18n-core' {
|
|
147
|
+
* interface TypedTranslationKeys {
|
|
148
|
+
* stringKey: TranslationStringKey;
|
|
149
|
+
* arrayKey: TranslationArrayKey;
|
|
150
|
+
* }
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
*
|
|
154
|
+
* augmentation이 없으면 string으로 fallback (breaking 없음).
|
|
155
|
+
*/
|
|
156
|
+
interface TypedTranslationKeys {
|
|
157
|
+
}
|
|
158
|
+
/** augmentation 시 좁혀진 타입, 미설정 시 string */
|
|
159
|
+
type ResolveStringKey = TypedTranslationKeys extends {
|
|
160
|
+
stringKey: infer K;
|
|
161
|
+
} ? K & string : string;
|
|
162
|
+
type ResolveArrayKey = TypedTranslationKeys extends {
|
|
163
|
+
arrayKey: infer K;
|
|
164
|
+
} ? K & string : string;
|
|
165
|
+
type ResolvePluralKey = TypedTranslationKeys extends {
|
|
166
|
+
pluralKey: infer K;
|
|
167
|
+
} ? K & string : string;
|
|
168
|
+
|
|
169
|
+
interface TranslatorInterface {
|
|
170
|
+
translate(key: string, paramsOrLang?: Record<string, unknown> | string, language?: string): string;
|
|
171
|
+
tPlural(key: string, count: number, params?: Record<string, unknown>, language?: string): string;
|
|
172
|
+
setLanguage(lang: string): void;
|
|
173
|
+
getCurrentLanguage(): string;
|
|
174
|
+
initialize(): Promise<void>;
|
|
175
|
+
isReady(): boolean;
|
|
176
|
+
debug(): unknown;
|
|
177
|
+
getRawValue<T = unknown>(key: string, language?: string): T | undefined;
|
|
178
|
+
tArray(key: string, language?: string): string[];
|
|
179
|
+
}
|
|
180
|
+
declare class Translator implements TranslatorInterface {
|
|
181
|
+
private cache;
|
|
182
|
+
private pluralRulesCache;
|
|
183
|
+
private loadedNamespaces;
|
|
184
|
+
private loadingPromises;
|
|
185
|
+
private allTranslations;
|
|
186
|
+
private isInitialized;
|
|
187
|
+
private initializationError;
|
|
188
|
+
private config;
|
|
189
|
+
private currentLang;
|
|
190
|
+
private cacheStats;
|
|
191
|
+
private onTranslationLoadedCallbacks;
|
|
192
|
+
private onLanguageChangedCallbacks;
|
|
193
|
+
private notifyTimer;
|
|
194
|
+
private recentlyNotified;
|
|
195
|
+
/**
|
|
196
|
+
* 번역 로드 완료 콜백 등록
|
|
197
|
+
*/
|
|
198
|
+
onTranslationLoaded(callback: () => void): () => void;
|
|
199
|
+
/**
|
|
200
|
+
* 언어 변경 콜백 등록
|
|
201
|
+
*/
|
|
202
|
+
onLanguageChanged(callback: (language: string) => void): () => void;
|
|
203
|
+
/**
|
|
204
|
+
* 언어 변경 이벤트 발생
|
|
205
|
+
*/
|
|
206
|
+
private notifyLanguageChanged;
|
|
207
|
+
/**
|
|
208
|
+
* 번역 로드 완료 이벤트 발생 (디바운싱 적용)
|
|
209
|
+
*/
|
|
210
|
+
private notifyTranslationLoaded;
|
|
211
|
+
constructor(config: I18nConfig);
|
|
212
|
+
/**
|
|
213
|
+
* 모든 번역 데이터를 미리 로드 (hua-api 스타일)
|
|
214
|
+
*/
|
|
215
|
+
initialize(): Promise<void>;
|
|
216
|
+
/**
|
|
217
|
+
* 초기화되지 않은 상태에서 번역 시도
|
|
218
|
+
*/
|
|
219
|
+
private translateBeforeInitialized;
|
|
220
|
+
/**
|
|
221
|
+
* 다른 로드된 언어에서 번역 찾기 (언어 변경 중 깜빡임 방지)
|
|
222
|
+
*/
|
|
223
|
+
private findInOtherLanguages;
|
|
224
|
+
/**
|
|
225
|
+
* 폴백 언어에서 번역 찾기
|
|
226
|
+
*/
|
|
227
|
+
private findInFallbackLanguage;
|
|
228
|
+
/**
|
|
229
|
+
* 번역 키를 번역된 텍스트로 변환
|
|
230
|
+
*/
|
|
231
|
+
translate(key: string, paramsOrLang?: Record<string, unknown> | string, language?: string): string;
|
|
232
|
+
/**
|
|
233
|
+
* 네임스페이스에서 키 찾기
|
|
234
|
+
*/
|
|
235
|
+
private findInNamespace;
|
|
236
|
+
/**
|
|
237
|
+
* 중첩된 객체에서 값을 가져오기
|
|
238
|
+
* 배열도 지원: 최종 값이 string[]이면 그대로 반환
|
|
239
|
+
*/
|
|
240
|
+
private getNestedValue;
|
|
241
|
+
/**
|
|
242
|
+
* 문자열 값인지 확인하는 타입 가드
|
|
243
|
+
*/
|
|
244
|
+
private isStringValue;
|
|
245
|
+
/**
|
|
246
|
+
* string[] 배열인지 확인하는 타입 가드
|
|
247
|
+
* 배열 값이 t()에 전달되면 랜덤으로 하나를 선택하여 반환
|
|
248
|
+
*/
|
|
249
|
+
private isStringArray;
|
|
250
|
+
/**
|
|
251
|
+
* 원시 값 가져오기 (배열, 객체 포함)
|
|
252
|
+
*/
|
|
253
|
+
getRawValue<T = unknown>(key: string, language?: string): T | undefined;
|
|
254
|
+
/**
|
|
255
|
+
* 배열 번역 값 가져오기 (타입 안전)
|
|
256
|
+
*/
|
|
257
|
+
tArray(key: string, language?: string): string[];
|
|
258
|
+
/**
|
|
259
|
+
* Intl.PluralRules 인스턴스 (언어별 캐시)
|
|
260
|
+
*/
|
|
261
|
+
private getPluralRules;
|
|
262
|
+
/**
|
|
263
|
+
* 복수형 번역 (ICU / Intl.PluralRules 기반)
|
|
264
|
+
*
|
|
265
|
+
* JSON: { "other": "총 {count}개" } (ko)
|
|
266
|
+
* { "one": "{count} item", "other": "{count} items" } (en)
|
|
267
|
+
*
|
|
268
|
+
* tPlural('common:total_count', 1) → en: "1 item" / ko: "총 1개"
|
|
269
|
+
* tPlural('common:total_count', 5) → en: "5 items" / ko: "총 5개"
|
|
270
|
+
*/
|
|
271
|
+
tPlural(key: string, count: number, params?: Record<string, unknown>, language?: string): string;
|
|
272
|
+
/**
|
|
273
|
+
* 매개변수 보간
|
|
274
|
+
*
|
|
275
|
+
* 지원 형식:
|
|
276
|
+
* - {key} - 단일 중괄호 (일반적인 i18n 형식)
|
|
277
|
+
* - {{key}} - 이중 중괄호 (하위 호환성)
|
|
278
|
+
*/
|
|
279
|
+
private interpolate;
|
|
280
|
+
/**
|
|
281
|
+
* 언어 설정
|
|
282
|
+
*/
|
|
283
|
+
setLanguage(language: string): void;
|
|
284
|
+
/**
|
|
285
|
+
* 언어 데이터 로드
|
|
286
|
+
*/
|
|
287
|
+
private loadLanguageData;
|
|
288
|
+
/**
|
|
289
|
+
* 현재 언어 가져오기
|
|
290
|
+
*/
|
|
291
|
+
getCurrentLanguage(): string;
|
|
292
|
+
/**
|
|
293
|
+
* 지원되는 언어 목록 가져오기
|
|
294
|
+
*/
|
|
295
|
+
getSupportedLanguages(): string[];
|
|
296
|
+
/**
|
|
297
|
+
* 초기화 완료 여부 확인
|
|
298
|
+
*/
|
|
299
|
+
isReady(): boolean;
|
|
300
|
+
/**
|
|
301
|
+
* 초기화 오류 가져오기
|
|
302
|
+
*/
|
|
303
|
+
getInitializationError(): TranslationError | null;
|
|
304
|
+
/**
|
|
305
|
+
* 캐시 클리어
|
|
306
|
+
*/
|
|
307
|
+
clearCache(): void;
|
|
308
|
+
/**
|
|
309
|
+
* 캐시 엔트리 설정
|
|
310
|
+
*/
|
|
311
|
+
private setCacheEntry;
|
|
312
|
+
/**
|
|
313
|
+
* 캐시 엔트리 가져오기
|
|
314
|
+
*/
|
|
315
|
+
private getCacheEntry;
|
|
316
|
+
/**
|
|
317
|
+
* 번역 오류 생성
|
|
318
|
+
*/
|
|
319
|
+
private createTranslationError;
|
|
320
|
+
/**
|
|
321
|
+
* 오류 로깅
|
|
322
|
+
*/
|
|
323
|
+
private logError;
|
|
324
|
+
/**
|
|
325
|
+
* 재시도 작업
|
|
326
|
+
*/
|
|
327
|
+
private retryOperation;
|
|
328
|
+
/**
|
|
329
|
+
* 안전한 번역 로드
|
|
330
|
+
*/
|
|
331
|
+
private safeLoadTranslations;
|
|
332
|
+
/**
|
|
333
|
+
* 디버그 정보
|
|
334
|
+
*/
|
|
335
|
+
debug(): {
|
|
336
|
+
isInitialized: boolean;
|
|
337
|
+
currentLanguage: string;
|
|
338
|
+
loadedNamespaces: string[];
|
|
339
|
+
cacheStats: {
|
|
340
|
+
hits: number;
|
|
341
|
+
misses: number;
|
|
342
|
+
};
|
|
343
|
+
cacheSize: number;
|
|
344
|
+
allTranslations: Record<string, Record<string, TranslationNamespace>>;
|
|
345
|
+
initializationError: TranslationError | null;
|
|
346
|
+
config: I18nConfig;
|
|
347
|
+
};
|
|
348
|
+
/**
|
|
349
|
+
* SSR에서 하이드레이션
|
|
350
|
+
*/
|
|
351
|
+
hydrateFromSSR(translations: Record<string, Record<string, TranslationNamespace>>): void;
|
|
352
|
+
/**
|
|
353
|
+
* 비동기 번역 (고급 기능)
|
|
354
|
+
*/
|
|
355
|
+
translateAsync(key: string, params?: Record<string, unknown>): Promise<string>;
|
|
356
|
+
/**
|
|
357
|
+
* 동기 번역 (고급 기능)
|
|
358
|
+
*/
|
|
359
|
+
translateSync(key: string, params?: Record<string, unknown>): string;
|
|
360
|
+
/**
|
|
361
|
+
* 키 파싱 (네임스페이스:키 형식)
|
|
362
|
+
*
|
|
363
|
+
* - 콜론(:)만 네임스페이스 구분자로 사용
|
|
364
|
+
* - 점(.)은 키 이름의 일부로 취급 (중첩 객체 접근용)
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* parseKey("home:hero.badge") → { namespace: "home", key: "hero.badge" }
|
|
368
|
+
* parseKey("hero.badge") → { namespace: "common", key: "hero.badge" }
|
|
369
|
+
* parseKey("save") → { namespace: "common", key: "save" }
|
|
370
|
+
*/
|
|
371
|
+
private parseKey;
|
|
372
|
+
/**
|
|
373
|
+
* 번역 데이터 로드 (고급 기능)
|
|
374
|
+
*/
|
|
375
|
+
private loadTranslationData;
|
|
376
|
+
/**
|
|
377
|
+
* 실제 번역 데이터 로드
|
|
378
|
+
*/
|
|
379
|
+
private _loadTranslationData;
|
|
380
|
+
}
|
|
381
|
+
declare function ssrTranslate({ translations, key, language, fallbackLanguage, missingKeyHandler, }: {
|
|
382
|
+
translations: Record<string, Record<string, TranslationNamespace>>;
|
|
383
|
+
key: string;
|
|
384
|
+
language?: string;
|
|
385
|
+
fallbackLanguage?: string;
|
|
386
|
+
missingKeyHandler?: (key: string) => string;
|
|
387
|
+
}): string;
|
|
388
|
+
declare function serverTranslate({ translations, key, language, fallbackLanguage, missingKeyHandler, options, }: {
|
|
389
|
+
translations: Record<string, unknown>;
|
|
390
|
+
key: string;
|
|
391
|
+
language?: string;
|
|
392
|
+
fallbackLanguage?: string;
|
|
393
|
+
missingKeyHandler?: (key: string) => string;
|
|
394
|
+
options?: {
|
|
395
|
+
cache?: Map<string, string>;
|
|
396
|
+
metrics?: {
|
|
397
|
+
hits: number;
|
|
398
|
+
misses: number;
|
|
399
|
+
};
|
|
400
|
+
debug?: boolean;
|
|
401
|
+
};
|
|
402
|
+
}): string;
|
|
403
|
+
|
|
404
|
+
export { type I18nConfig as I, type LanguageConfig as L, type PluralCategory as P, type ResolveStringKey as R, type TranslationParams as T, type I18nContextType as a, type ResolvePluralKey as b, type ResolveArrayKey as c, type TranslationError as d, type I18nPlatformAdapter as e, type PluralValue as f, Translator as g, type TypedTranslationKeys as h, headlessPlatformAdapter as i, ssrTranslate as j, serverTranslate as s, webPlatformAdapter as w };
|