@hua-labs/i18n-loaders 1.0.0 → 1.1.0-alpha.0.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 +20 -20
- package/README.md +415 -415
- package/dist/api-loader.d.ts +24 -1
- package/dist/api-loader.d.ts.map +1 -1
- package/dist/api-loader.js +78 -1
- package/dist/api-loader.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/api-loader.ts +281 -199
- package/src/defaults.ts +63 -63
- package/src/index.ts +13 -12
- package/src/preload.ts +64 -64
- package/src/types.ts +56 -34
package/README.md
CHANGED
|
@@ -1,415 +1,415 @@
|
|
|
1
|
-
# @hua-labs/i18n-loaders
|
|
2
|
-
|
|
3
|
-
Production-ready translation loaders, caching, and preloading utilities. Use with `@hua-labs/i18n-core` to reuse proven loading strategies from SUM API.
|
|
4
|
-
|
|
5
|
-
## Key Features
|
|
6
|
-
|
|
7
|
-
- API-based translation loader (`createApiTranslationLoader`)
|
|
8
|
-
- Built-in TTL/global cache/duplicate request prevention
|
|
9
|
-
- Namespace preloading & fallback language warming
|
|
10
|
-
- Default translation (JSON) merging (SUM API style)
|
|
11
|
-
- Works on both server and client
|
|
12
|
-
- **Production tested**: Currently used in SUM API
|
|
13
|
-
|
|
14
|
-
## Installation
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
pnpm add @hua-labs/i18n-loaders
|
|
18
|
-
# or
|
|
19
|
-
npm install @hua-labs/i18n-loaders
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Quick Start
|
|
23
|
-
|
|
24
|
-
### Basic Usage
|
|
25
|
-
|
|
26
|
-
```ts
|
|
27
|
-
import { createCoreI18n } from '@hua-labs/i18n-core';
|
|
28
|
-
import { createApiTranslationLoader } from '@hua-labs/i18n-loaders';
|
|
29
|
-
|
|
30
|
-
const loadTranslations = createApiTranslationLoader({
|
|
31
|
-
translationApiPath: '/api/translations',
|
|
32
|
-
cacheTtlMs: 60_000, // 1 minute
|
|
33
|
-
enableGlobalCache: true
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
export const I18nProvider = createCoreI18n({
|
|
37
|
-
defaultLanguage: 'ko',
|
|
38
|
-
fallbackLanguage: 'en',
|
|
39
|
-
namespaces: ['common', 'dashboard'],
|
|
40
|
-
translationLoader: 'custom',
|
|
41
|
-
loadTranslations
|
|
42
|
-
});
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Preloading
|
|
46
|
-
|
|
47
|
-
```ts
|
|
48
|
-
import { createApiTranslationLoader, preloadNamespaces } from '@hua-labs/i18n-loaders';
|
|
49
|
-
|
|
50
|
-
const loadTranslations = createApiTranslationLoader({
|
|
51
|
-
translationApiPath: '/api/translations'
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// Preload required namespaces at app startup
|
|
55
|
-
preloadNamespaces('ko', ['common', 'dashboard'], loadTranslations);
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Using Default Translation Merging
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
import { createApiTranslationLoader, withDefaultTranslations } from '@hua-labs/i18n-loaders';
|
|
62
|
-
|
|
63
|
-
const apiLoader = createApiTranslationLoader({
|
|
64
|
-
translationApiPath: '/api/translations'
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const defaultTranslations = {
|
|
68
|
-
ko: {
|
|
69
|
-
common: {
|
|
70
|
-
welcome: 'Welcome',
|
|
71
|
-
hello: 'Hello'
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
en: {
|
|
75
|
-
common: {
|
|
76
|
-
welcome: 'Welcome',
|
|
77
|
-
hello: 'Hello'
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// Use default translations if API fails, merge if API succeeds
|
|
83
|
-
const loadTranslations = withDefaultTranslations(apiLoader, defaultTranslations);
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## API Reference
|
|
87
|
-
|
|
88
|
-
### createApiTranslationLoader
|
|
89
|
-
|
|
90
|
-
Creates an API-based translation loader. Includes TTL caching, duplicate request prevention, and global cache.
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
function createApiTranslationLoader(
|
|
94
|
-
options?: ApiLoaderOptions
|
|
95
|
-
): TranslationLoader
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
#### Options
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
interface ApiLoaderOptions {
|
|
102
|
-
// API path (default: '/api/translations')
|
|
103
|
-
translationApiPath?: string;
|
|
104
|
-
|
|
105
|
-
// Base URL (for server-side use)
|
|
106
|
-
baseUrl?: string;
|
|
107
|
-
|
|
108
|
-
// Local fallback URL (for development)
|
|
109
|
-
localFallbackBaseUrl?: string;
|
|
110
|
-
|
|
111
|
-
// Cache TTL (milliseconds, default: 5 minutes)
|
|
112
|
-
cacheTtlMs?: number;
|
|
113
|
-
|
|
114
|
-
// Disable cache
|
|
115
|
-
disableCache?: boolean;
|
|
116
|
-
|
|
117
|
-
// Fetch request options
|
|
118
|
-
requestInit?: RequestInit | ((language: string, namespace: string) => RequestInit | undefined);
|
|
119
|
-
|
|
120
|
-
// Custom fetcher (for testing)
|
|
121
|
-
fetcher?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
122
|
-
|
|
123
|
-
// Logger (default: console)
|
|
124
|
-
logger?: Pick<typeof console, 'log' | 'warn' | 'error'>;
|
|
125
|
-
|
|
126
|
-
// Retry configuration for network errors
|
|
127
|
-
retryCount?: number; // Number of retry attempts (default: 0, no retry)
|
|
128
|
-
retryDelay?: number; // Base delay in milliseconds (default: 1000), uses exponential backoff
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
#### Examples
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
// Basic usage
|
|
136
|
-
const loader = createApiTranslationLoader();
|
|
137
|
-
|
|
138
|
-
// Custom options
|
|
139
|
-
const loader = createApiTranslationLoader({
|
|
140
|
-
translationApiPath: '/api/v2/translations',
|
|
141
|
-
cacheTtlMs: 10 * 60 * 1000, // 10 minutes
|
|
142
|
-
disableCache: false,
|
|
143
|
-
retryCount: 3, // Retry 3 times on network errors
|
|
144
|
-
retryDelay: 1000, // 1 second base delay (exponential backoff)
|
|
145
|
-
requestInit: {
|
|
146
|
-
headers: {
|
|
147
|
-
'Authorization': 'Bearer token'
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Dynamic request options
|
|
153
|
-
const loader = createApiTranslationLoader({
|
|
154
|
-
requestInit: (language, namespace) => ({
|
|
155
|
-
headers: {
|
|
156
|
-
'X-Language': language,
|
|
157
|
-
'X-Namespace': namespace
|
|
158
|
-
}
|
|
159
|
-
})
|
|
160
|
-
});
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### preloadNamespaces
|
|
164
|
-
|
|
165
|
-
Preloads multiple namespaces in parallel.
|
|
166
|
-
|
|
167
|
-
```ts
|
|
168
|
-
function preloadNamespaces(
|
|
169
|
-
language: string,
|
|
170
|
-
namespaces: string[],
|
|
171
|
-
loader: TranslationLoader,
|
|
172
|
-
options?: PreloadOptions
|
|
173
|
-
): Promise<{
|
|
174
|
-
fulfilled: string[];
|
|
175
|
-
rejected: unknown[];
|
|
176
|
-
}>
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
#### Options
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
interface PreloadOptions {
|
|
183
|
-
// Logger (default: console)
|
|
184
|
-
logger?: Pick<typeof console, 'log' | 'warn'>;
|
|
185
|
-
|
|
186
|
-
// Suppress errors (default: false)
|
|
187
|
-
suppressErrors?: boolean;
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
#### Examples
|
|
192
|
-
|
|
193
|
-
```ts
|
|
194
|
-
import { preloadNamespaces } from '@hua-labs/i18n-loaders';
|
|
195
|
-
|
|
196
|
-
const loader = createApiTranslationLoader();
|
|
197
|
-
|
|
198
|
-
// Preload multiple namespaces
|
|
199
|
-
const result = await preloadNamespaces(
|
|
200
|
-
'ko',
|
|
201
|
-
['common', 'navigation', 'footer'],
|
|
202
|
-
loader
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
console.log(`Loaded: ${result.fulfilled.length}`);
|
|
206
|
-
console.log(`Failed: ${result.rejected.length}`);
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### warmFallbackLanguages
|
|
210
|
-
|
|
211
|
-
Pre-warms fallback languages.
|
|
212
|
-
|
|
213
|
-
```ts
|
|
214
|
-
function warmFallbackLanguages(
|
|
215
|
-
currentLanguage: string,
|
|
216
|
-
languages: string[],
|
|
217
|
-
namespaces: string[],
|
|
218
|
-
loader: TranslationLoader,
|
|
219
|
-
options?: PreloadOptions
|
|
220
|
-
): Promise<Array<{
|
|
221
|
-
fulfilled: string[];
|
|
222
|
-
rejected: unknown[];
|
|
223
|
-
}>>
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
#### Examples
|
|
227
|
-
|
|
228
|
-
```ts
|
|
229
|
-
import { warmFallbackLanguages } from '@hua-labs/i18n-loaders';
|
|
230
|
-
|
|
231
|
-
const loader = createApiTranslationLoader();
|
|
232
|
-
|
|
233
|
-
// When current language is 'ko', preload 'en', 'ja'
|
|
234
|
-
await warmFallbackLanguages(
|
|
235
|
-
'ko',
|
|
236
|
-
['ko', 'en', 'ja'],
|
|
237
|
-
['common', 'navigation'],
|
|
238
|
-
loader
|
|
239
|
-
);
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
### withDefaultTranslations
|
|
243
|
-
|
|
244
|
-
Merges default translations with API translations. Uses default translations if API fails.
|
|
245
|
-
|
|
246
|
-
```ts
|
|
247
|
-
function withDefaultTranslations(
|
|
248
|
-
loader: TranslationLoader,
|
|
249
|
-
defaults: DefaultTranslations
|
|
250
|
-
): TranslationLoader
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
#### Types
|
|
254
|
-
|
|
255
|
-
```ts
|
|
256
|
-
type DefaultTranslations = Record<
|
|
257
|
-
string, // language
|
|
258
|
-
Record<string, TranslationRecord> // namespace -> translations
|
|
259
|
-
>;
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
#### Examples
|
|
263
|
-
|
|
264
|
-
```ts
|
|
265
|
-
import { withDefaultTranslations } from '@hua-labs/i18n-loaders';
|
|
266
|
-
|
|
267
|
-
const apiLoader = createApiTranslationLoader();
|
|
268
|
-
|
|
269
|
-
const defaults = {
|
|
270
|
-
ko: {
|
|
271
|
-
common: {
|
|
272
|
-
welcome: 'Welcome',
|
|
273
|
-
hello: 'Hello'
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
en: {
|
|
277
|
-
common: {
|
|
278
|
-
welcome: 'Welcome',
|
|
279
|
-
hello: 'Hello'
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
const loader = withDefaultTranslations(apiLoader, defaults);
|
|
285
|
-
|
|
286
|
-
// Merges with default translations if API succeeds
|
|
287
|
-
// Uses only default translations if API fails
|
|
288
|
-
const translations = await loader('ko', 'common');
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
## Usage Scenarios
|
|
292
|
-
|
|
293
|
-
### Next.js App Router
|
|
294
|
-
|
|
295
|
-
```tsx
|
|
296
|
-
// lib/i18n-config.ts
|
|
297
|
-
import { createCoreI18n } from '@hua-labs/i18n-core';
|
|
298
|
-
import { createApiTranslationLoader, preloadNamespaces } from '@hua-labs/i18n-loaders';
|
|
299
|
-
|
|
300
|
-
const loadTranslations = createApiTranslationLoader({
|
|
301
|
-
translationApiPath: '/api/translations',
|
|
302
|
-
cacheTtlMs: 60_000
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
export const I18nProvider = createCoreI18n({
|
|
306
|
-
defaultLanguage: 'ko',
|
|
307
|
-
fallbackLanguage: 'en',
|
|
308
|
-
namespaces: ['common', 'navigation', 'footer'],
|
|
309
|
-
translationLoader: 'custom',
|
|
310
|
-
loadTranslations
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
// Use in app/layout.tsx
|
|
314
|
-
export default function RootLayout({ children }) {
|
|
315
|
-
// Preload on client
|
|
316
|
-
if (typeof window !== 'undefined') {
|
|
317
|
-
preloadNamespaces('ko', ['common', 'navigation'], loadTranslations);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return (
|
|
321
|
-
<html>
|
|
322
|
-
<body>
|
|
323
|
-
<I18nProvider>{children}</I18nProvider>
|
|
324
|
-
</body>
|
|
325
|
-
</html>
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
### Using with SSR
|
|
331
|
-
|
|
332
|
-
```tsx
|
|
333
|
-
// app/layout.tsx (Server Component)
|
|
334
|
-
import { loadSSRTranslations } from './lib/ssr-translations';
|
|
335
|
-
import { createCoreI18n } from '@hua-labs/i18n-core';
|
|
336
|
-
import { createApiTranslationLoader } from '@hua-labs/i18n-loaders';
|
|
337
|
-
|
|
338
|
-
export default async function RootLayout({ children }) {
|
|
339
|
-
// Load translations from SSR
|
|
340
|
-
const ssrTranslations = await loadSSRTranslations('ko');
|
|
341
|
-
|
|
342
|
-
// Client loader
|
|
343
|
-
const loadTranslations = createApiTranslationLoader({
|
|
344
|
-
translationApiPath: '/api/translations'
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
const I18nProvider = createCoreI18n({
|
|
348
|
-
defaultLanguage: 'ko',
|
|
349
|
-
fallbackLanguage: 'en',
|
|
350
|
-
namespaces: ['common', 'navigation', 'footer'],
|
|
351
|
-
translationLoader: 'custom',
|
|
352
|
-
loadTranslations,
|
|
353
|
-
initialTranslations: ssrTranslations // Pass SSR translations
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
return (
|
|
357
|
-
<html lang="ko">
|
|
358
|
-
<body>
|
|
359
|
-
<I18nProvider>{children}</I18nProvider>
|
|
360
|
-
</body>
|
|
361
|
-
</html>
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
## Caching Behavior
|
|
367
|
-
|
|
368
|
-
- **TTL Cache**: Each translation is cached for `cacheTtlMs` duration
|
|
369
|
-
- **Duplicate Request Prevention**: Reuses existing Promise if same translation is loading
|
|
370
|
-
- **Global Cache**: Same loader instance shares cache across all components
|
|
371
|
-
|
|
372
|
-
## Error Handling
|
|
373
|
-
|
|
374
|
-
- **Automatic retry**: Network errors are automatically retried with exponential backoff (configurable via `retryCount` and `retryDelay`)
|
|
375
|
-
- Throws error on API request failure after all retries are exhausted
|
|
376
|
-
- Falls back to default translations when using `withDefaultTranslations`
|
|
377
|
-
- `preloadNamespaces` uses `Promise.allSettled` to continue even if some fail
|
|
378
|
-
|
|
379
|
-
### Retry Configuration
|
|
380
|
-
|
|
381
|
-
```ts
|
|
382
|
-
const loader = createApiTranslationLoader({
|
|
383
|
-
translationApiPath: '/api/translations',
|
|
384
|
-
retryCount: 3, // Retry up to 3 times on network errors (default: 0, no retry)
|
|
385
|
-
retryDelay: 1000, // Start with 1 second delay, doubles on each retry (exponential backoff)
|
|
386
|
-
});
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
The retry mechanism uses exponential backoff:
|
|
390
|
-
- 1st retry: waits `retryDelay` ms (e.g., 1000ms)
|
|
391
|
-
- 2nd retry: waits `retryDelay * 2` ms (e.g., 2000ms)
|
|
392
|
-
- 3rd retry: waits `retryDelay * 4` ms (e.g., 4000ms)
|
|
393
|
-
|
|
394
|
-
## Examples
|
|
395
|
-
|
|
396
|
-
- **[Next.js Example](../../examples/next-app-router-example/)** - Complete example using API loader with caching
|
|
397
|
-
|
|
398
|
-
## Error Handling Improvements
|
|
399
|
-
|
|
400
|
-
The API loader now includes enhanced error detection:
|
|
401
|
-
|
|
402
|
-
- **Network error detection**: Improved detection of network failures
|
|
403
|
-
- **HTTP status code handling**: Automatic retry for 5xx errors and 408 timeouts
|
|
404
|
-
- **Exponential backoff**: Smart retry strategy with configurable delays
|
|
405
|
-
- **Error type classification**: Better distinction between retryable and non-retryable errors
|
|
406
|
-
|
|
407
|
-
See [API Loader Guide](./docs/API_LOADER.md) for detailed error handling documentation.
|
|
408
|
-
|
|
409
|
-
## Documentation
|
|
410
|
-
|
|
411
|
-
- [API Loader Guide](./docs/API_LOADER.md) - Detailed API loader documentation and error handling
|
|
412
|
-
|
|
413
|
-
## License
|
|
414
|
-
|
|
415
|
-
MIT License
|
|
1
|
+
# @hua-labs/i18n-loaders
|
|
2
|
+
|
|
3
|
+
Production-ready translation loaders, caching, and preloading utilities. Use with `@hua-labs/i18n-core` to reuse proven loading strategies from SUM API.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- API-based translation loader (`createApiTranslationLoader`)
|
|
8
|
+
- Built-in TTL/global cache/duplicate request prevention
|
|
9
|
+
- Namespace preloading & fallback language warming
|
|
10
|
+
- Default translation (JSON) merging (SUM API style)
|
|
11
|
+
- Works on both server and client
|
|
12
|
+
- **Production tested**: Currently used in SUM API
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @hua-labs/i18n-loaders
|
|
18
|
+
# or
|
|
19
|
+
npm install @hua-labs/i18n-loaders
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Basic Usage
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createCoreI18n } from '@hua-labs/i18n-core';
|
|
28
|
+
import { createApiTranslationLoader } from '@hua-labs/i18n-loaders';
|
|
29
|
+
|
|
30
|
+
const loadTranslations = createApiTranslationLoader({
|
|
31
|
+
translationApiPath: '/api/translations',
|
|
32
|
+
cacheTtlMs: 60_000, // 1 minute
|
|
33
|
+
enableGlobalCache: true
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const I18nProvider = createCoreI18n({
|
|
37
|
+
defaultLanguage: 'ko',
|
|
38
|
+
fallbackLanguage: 'en',
|
|
39
|
+
namespaces: ['common', 'dashboard'],
|
|
40
|
+
translationLoader: 'custom',
|
|
41
|
+
loadTranslations
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Preloading
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { createApiTranslationLoader, preloadNamespaces } from '@hua-labs/i18n-loaders';
|
|
49
|
+
|
|
50
|
+
const loadTranslations = createApiTranslationLoader({
|
|
51
|
+
translationApiPath: '/api/translations'
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Preload required namespaces at app startup
|
|
55
|
+
preloadNamespaces('ko', ['common', 'dashboard'], loadTranslations);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Using Default Translation Merging
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { createApiTranslationLoader, withDefaultTranslations } from '@hua-labs/i18n-loaders';
|
|
62
|
+
|
|
63
|
+
const apiLoader = createApiTranslationLoader({
|
|
64
|
+
translationApiPath: '/api/translations'
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const defaultTranslations = {
|
|
68
|
+
ko: {
|
|
69
|
+
common: {
|
|
70
|
+
welcome: 'Welcome',
|
|
71
|
+
hello: 'Hello'
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
en: {
|
|
75
|
+
common: {
|
|
76
|
+
welcome: 'Welcome',
|
|
77
|
+
hello: 'Hello'
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Use default translations if API fails, merge if API succeeds
|
|
83
|
+
const loadTranslations = withDefaultTranslations(apiLoader, defaultTranslations);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API Reference
|
|
87
|
+
|
|
88
|
+
### createApiTranslationLoader
|
|
89
|
+
|
|
90
|
+
Creates an API-based translation loader. Includes TTL caching, duplicate request prevention, and global cache.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
function createApiTranslationLoader(
|
|
94
|
+
options?: ApiLoaderOptions
|
|
95
|
+
): TranslationLoader
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Options
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
interface ApiLoaderOptions {
|
|
102
|
+
// API path (default: '/api/translations')
|
|
103
|
+
translationApiPath?: string;
|
|
104
|
+
|
|
105
|
+
// Base URL (for server-side use)
|
|
106
|
+
baseUrl?: string;
|
|
107
|
+
|
|
108
|
+
// Local fallback URL (for development)
|
|
109
|
+
localFallbackBaseUrl?: string;
|
|
110
|
+
|
|
111
|
+
// Cache TTL (milliseconds, default: 5 minutes)
|
|
112
|
+
cacheTtlMs?: number;
|
|
113
|
+
|
|
114
|
+
// Disable cache
|
|
115
|
+
disableCache?: boolean;
|
|
116
|
+
|
|
117
|
+
// Fetch request options
|
|
118
|
+
requestInit?: RequestInit | ((language: string, namespace: string) => RequestInit | undefined);
|
|
119
|
+
|
|
120
|
+
// Custom fetcher (for testing)
|
|
121
|
+
fetcher?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
122
|
+
|
|
123
|
+
// Logger (default: console)
|
|
124
|
+
logger?: Pick<typeof console, 'log' | 'warn' | 'error'>;
|
|
125
|
+
|
|
126
|
+
// Retry configuration for network errors
|
|
127
|
+
retryCount?: number; // Number of retry attempts (default: 0, no retry)
|
|
128
|
+
retryDelay?: number; // Base delay in milliseconds (default: 1000), uses exponential backoff
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### Examples
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// Basic usage
|
|
136
|
+
const loader = createApiTranslationLoader();
|
|
137
|
+
|
|
138
|
+
// Custom options
|
|
139
|
+
const loader = createApiTranslationLoader({
|
|
140
|
+
translationApiPath: '/api/v2/translations',
|
|
141
|
+
cacheTtlMs: 10 * 60 * 1000, // 10 minutes
|
|
142
|
+
disableCache: false,
|
|
143
|
+
retryCount: 3, // Retry 3 times on network errors
|
|
144
|
+
retryDelay: 1000, // 1 second base delay (exponential backoff)
|
|
145
|
+
requestInit: {
|
|
146
|
+
headers: {
|
|
147
|
+
'Authorization': 'Bearer token'
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Dynamic request options
|
|
153
|
+
const loader = createApiTranslationLoader({
|
|
154
|
+
requestInit: (language, namespace) => ({
|
|
155
|
+
headers: {
|
|
156
|
+
'X-Language': language,
|
|
157
|
+
'X-Namespace': namespace
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### preloadNamespaces
|
|
164
|
+
|
|
165
|
+
Preloads multiple namespaces in parallel.
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
function preloadNamespaces(
|
|
169
|
+
language: string,
|
|
170
|
+
namespaces: string[],
|
|
171
|
+
loader: TranslationLoader,
|
|
172
|
+
options?: PreloadOptions
|
|
173
|
+
): Promise<{
|
|
174
|
+
fulfilled: string[];
|
|
175
|
+
rejected: unknown[];
|
|
176
|
+
}>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### Options
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
interface PreloadOptions {
|
|
183
|
+
// Logger (default: console)
|
|
184
|
+
logger?: Pick<typeof console, 'log' | 'warn'>;
|
|
185
|
+
|
|
186
|
+
// Suppress errors (default: false)
|
|
187
|
+
suppressErrors?: boolean;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### Examples
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { preloadNamespaces } from '@hua-labs/i18n-loaders';
|
|
195
|
+
|
|
196
|
+
const loader = createApiTranslationLoader();
|
|
197
|
+
|
|
198
|
+
// Preload multiple namespaces
|
|
199
|
+
const result = await preloadNamespaces(
|
|
200
|
+
'ko',
|
|
201
|
+
['common', 'navigation', 'footer'],
|
|
202
|
+
loader
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
console.log(`Loaded: ${result.fulfilled.length}`);
|
|
206
|
+
console.log(`Failed: ${result.rejected.length}`);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### warmFallbackLanguages
|
|
210
|
+
|
|
211
|
+
Pre-warms fallback languages.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
function warmFallbackLanguages(
|
|
215
|
+
currentLanguage: string,
|
|
216
|
+
languages: string[],
|
|
217
|
+
namespaces: string[],
|
|
218
|
+
loader: TranslationLoader,
|
|
219
|
+
options?: PreloadOptions
|
|
220
|
+
): Promise<Array<{
|
|
221
|
+
fulfilled: string[];
|
|
222
|
+
rejected: unknown[];
|
|
223
|
+
}>>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### Examples
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { warmFallbackLanguages } from '@hua-labs/i18n-loaders';
|
|
230
|
+
|
|
231
|
+
const loader = createApiTranslationLoader();
|
|
232
|
+
|
|
233
|
+
// When current language is 'ko', preload 'en', 'ja'
|
|
234
|
+
await warmFallbackLanguages(
|
|
235
|
+
'ko',
|
|
236
|
+
['ko', 'en', 'ja'],
|
|
237
|
+
['common', 'navigation'],
|
|
238
|
+
loader
|
|
239
|
+
);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### withDefaultTranslations
|
|
243
|
+
|
|
244
|
+
Merges default translations with API translations. Uses default translations if API fails.
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
function withDefaultTranslations(
|
|
248
|
+
loader: TranslationLoader,
|
|
249
|
+
defaults: DefaultTranslations
|
|
250
|
+
): TranslationLoader
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### Types
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
type DefaultTranslations = Record<
|
|
257
|
+
string, // language
|
|
258
|
+
Record<string, TranslationRecord> // namespace -> translations
|
|
259
|
+
>;
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### Examples
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { withDefaultTranslations } from '@hua-labs/i18n-loaders';
|
|
266
|
+
|
|
267
|
+
const apiLoader = createApiTranslationLoader();
|
|
268
|
+
|
|
269
|
+
const defaults = {
|
|
270
|
+
ko: {
|
|
271
|
+
common: {
|
|
272
|
+
welcome: 'Welcome',
|
|
273
|
+
hello: 'Hello'
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
en: {
|
|
277
|
+
common: {
|
|
278
|
+
welcome: 'Welcome',
|
|
279
|
+
hello: 'Hello'
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const loader = withDefaultTranslations(apiLoader, defaults);
|
|
285
|
+
|
|
286
|
+
// Merges with default translations if API succeeds
|
|
287
|
+
// Uses only default translations if API fails
|
|
288
|
+
const translations = await loader('ko', 'common');
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Usage Scenarios
|
|
292
|
+
|
|
293
|
+
### Next.js App Router
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
// lib/i18n-config.ts
|
|
297
|
+
import { createCoreI18n } from '@hua-labs/i18n-core';
|
|
298
|
+
import { createApiTranslationLoader, preloadNamespaces } from '@hua-labs/i18n-loaders';
|
|
299
|
+
|
|
300
|
+
const loadTranslations = createApiTranslationLoader({
|
|
301
|
+
translationApiPath: '/api/translations',
|
|
302
|
+
cacheTtlMs: 60_000
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
export const I18nProvider = createCoreI18n({
|
|
306
|
+
defaultLanguage: 'ko',
|
|
307
|
+
fallbackLanguage: 'en',
|
|
308
|
+
namespaces: ['common', 'navigation', 'footer'],
|
|
309
|
+
translationLoader: 'custom',
|
|
310
|
+
loadTranslations
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Use in app/layout.tsx
|
|
314
|
+
export default function RootLayout({ children }) {
|
|
315
|
+
// Preload on client
|
|
316
|
+
if (typeof window !== 'undefined') {
|
|
317
|
+
preloadNamespaces('ko', ['common', 'navigation'], loadTranslations);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return (
|
|
321
|
+
<html>
|
|
322
|
+
<body>
|
|
323
|
+
<I18nProvider>{children}</I18nProvider>
|
|
324
|
+
</body>
|
|
325
|
+
</html>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Using with SSR
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
// app/layout.tsx (Server Component)
|
|
334
|
+
import { loadSSRTranslations } from './lib/ssr-translations';
|
|
335
|
+
import { createCoreI18n } from '@hua-labs/i18n-core';
|
|
336
|
+
import { createApiTranslationLoader } from '@hua-labs/i18n-loaders';
|
|
337
|
+
|
|
338
|
+
export default async function RootLayout({ children }) {
|
|
339
|
+
// Load translations from SSR
|
|
340
|
+
const ssrTranslations = await loadSSRTranslations('ko');
|
|
341
|
+
|
|
342
|
+
// Client loader
|
|
343
|
+
const loadTranslations = createApiTranslationLoader({
|
|
344
|
+
translationApiPath: '/api/translations'
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const I18nProvider = createCoreI18n({
|
|
348
|
+
defaultLanguage: 'ko',
|
|
349
|
+
fallbackLanguage: 'en',
|
|
350
|
+
namespaces: ['common', 'navigation', 'footer'],
|
|
351
|
+
translationLoader: 'custom',
|
|
352
|
+
loadTranslations,
|
|
353
|
+
initialTranslations: ssrTranslations // Pass SSR translations
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<html lang="ko">
|
|
358
|
+
<body>
|
|
359
|
+
<I18nProvider>{children}</I18nProvider>
|
|
360
|
+
</body>
|
|
361
|
+
</html>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Caching Behavior
|
|
367
|
+
|
|
368
|
+
- **TTL Cache**: Each translation is cached for `cacheTtlMs` duration
|
|
369
|
+
- **Duplicate Request Prevention**: Reuses existing Promise if same translation is loading
|
|
370
|
+
- **Global Cache**: Same loader instance shares cache across all components
|
|
371
|
+
|
|
372
|
+
## Error Handling
|
|
373
|
+
|
|
374
|
+
- **Automatic retry**: Network errors are automatically retried with exponential backoff (configurable via `retryCount` and `retryDelay`)
|
|
375
|
+
- Throws error on API request failure after all retries are exhausted
|
|
376
|
+
- Falls back to default translations when using `withDefaultTranslations`
|
|
377
|
+
- `preloadNamespaces` uses `Promise.allSettled` to continue even if some fail
|
|
378
|
+
|
|
379
|
+
### Retry Configuration
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
const loader = createApiTranslationLoader({
|
|
383
|
+
translationApiPath: '/api/translations',
|
|
384
|
+
retryCount: 3, // Retry up to 3 times on network errors (default: 0, no retry)
|
|
385
|
+
retryDelay: 1000, // Start with 1 second delay, doubles on each retry (exponential backoff)
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
The retry mechanism uses exponential backoff:
|
|
390
|
+
- 1st retry: waits `retryDelay` ms (e.g., 1000ms)
|
|
391
|
+
- 2nd retry: waits `retryDelay * 2` ms (e.g., 2000ms)
|
|
392
|
+
- 3rd retry: waits `retryDelay * 4` ms (e.g., 4000ms)
|
|
393
|
+
|
|
394
|
+
## Examples
|
|
395
|
+
|
|
396
|
+
- **[Next.js Example](../../examples/next-app-router-example/)** - Complete example using API loader with caching
|
|
397
|
+
|
|
398
|
+
## Error Handling Improvements
|
|
399
|
+
|
|
400
|
+
The API loader now includes enhanced error detection:
|
|
401
|
+
|
|
402
|
+
- **Network error detection**: Improved detection of network failures
|
|
403
|
+
- **HTTP status code handling**: Automatic retry for 5xx errors and 408 timeouts
|
|
404
|
+
- **Exponential backoff**: Smart retry strategy with configurable delays
|
|
405
|
+
- **Error type classification**: Better distinction between retryable and non-retryable errors
|
|
406
|
+
|
|
407
|
+
See [API Loader Guide](./docs/API_LOADER.md) for detailed error handling documentation.
|
|
408
|
+
|
|
409
|
+
## Documentation
|
|
410
|
+
|
|
411
|
+
- [API Loader Guide](./docs/API_LOADER.md) - Detailed API loader documentation and error handling
|
|
412
|
+
|
|
413
|
+
## License
|
|
414
|
+
|
|
415
|
+
MIT License
|