@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 HUA Labs Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
# @hua-labs/i18n-core
|
|
2
|
+
|
|
3
|
+
**Type-safe i18n library with SSR/CSR support and state management integration**
|
|
4
|
+
|
|
5
|
+
HUA Labs - Core Internationalization Library
|
|
6
|
+
|
|
7
|
+
Lightweight i18n library for React applications with essential translation features only.
|
|
8
|
+
|
|
9
|
+
## Why @hua-labs/i18n-core?
|
|
10
|
+
|
|
11
|
+
Struggling with flickering on language changes or hydration mismatches? @hua-labs/i18n-core provides a pragmatic, production-ready solution for React i18n.
|
|
12
|
+
|
|
13
|
+
**Key advantages:**
|
|
14
|
+
- **Zero flickering**: Automatically shows previous language translation during switch
|
|
15
|
+
- **SSR-first**: Built-in hydration handling, no mismatch issues
|
|
16
|
+
- **State management integration**: First-class Zustand support
|
|
17
|
+
- **Small bundle**: ~2.8KB gzipped, zero dependencies (React only)
|
|
18
|
+
- **Framework agnostic**: Works with Next.js, Remix, Vite, and more
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Examples
|
|
22
|
+
|
|
23
|
+
- **[CodeSandbox Template](../../examples/codesandbox-template/)** - Quick start template
|
|
24
|
+
- **[Next.js Example](../../examples/next-app-router-example/)** - Complete Next.js App Router example
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @hua-labs/i18n-core
|
|
30
|
+
# or
|
|
31
|
+
yarn add @hua-labs/i18n-core
|
|
32
|
+
# or
|
|
33
|
+
pnpm add @hua-labs/i18n-core
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- Lightweight core translation functionality
|
|
39
|
+
- Multiple translation loader strategies (API, static files, custom)
|
|
40
|
+
- Lazy loading support for namespaces
|
|
41
|
+
- SSR/SSG support with initial translations
|
|
42
|
+
- TypeScript support
|
|
43
|
+
- Zero external dependencies (except React)
|
|
44
|
+
- Built-in caching
|
|
45
|
+
- Error handling and fallback support
|
|
46
|
+
- Debug mode for development
|
|
47
|
+
- **Language change flickering prevention**: Automatically shows previous language translation during language switch
|
|
48
|
+
- **State management integration**: Works seamlessly with Zustand via `@hua-labs/i18n-core-zustand`
|
|
49
|
+
- **Raw value access**: Get arrays, objects, or any non-string values from translations via `getRawValue`
|
|
50
|
+
- **Automatic retry**: Network errors are automatically retried with exponential backoff (when using API loader)
|
|
51
|
+
- **Memory leak prevention**: LRU cache for Translator instances to prevent memory accumulation
|
|
52
|
+
- **Production-optimized**: Console logs are automatically suppressed in production mode
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
### Basic Setup
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// app/layout.tsx (Next.js App Router)
|
|
60
|
+
import { createCoreI18n } from '@hua-labs/i18n-core';
|
|
61
|
+
|
|
62
|
+
export default function RootLayout({ children }) {
|
|
63
|
+
return (
|
|
64
|
+
<html>
|
|
65
|
+
<body>
|
|
66
|
+
{createCoreI18n({
|
|
67
|
+
defaultLanguage: 'ko',
|
|
68
|
+
fallbackLanguage: 'en',
|
|
69
|
+
namespaces: ['common', 'pages']
|
|
70
|
+
})({ children })}
|
|
71
|
+
</body>
|
|
72
|
+
</html>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Using Translations
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { useTranslation } from '@hua-labs/i18n-core';
|
|
81
|
+
|
|
82
|
+
function MyComponent() {
|
|
83
|
+
const { t } = useTranslation();
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div>
|
|
87
|
+
<h1>{t('common:welcome')}</h1>
|
|
88
|
+
<p>{t('pages:home.title')}</p>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Translation Loaders
|
|
95
|
+
|
|
96
|
+
The library supports three translation loading strategies:
|
|
97
|
+
|
|
98
|
+
### 1. API Loader (Default, Recommended)
|
|
99
|
+
|
|
100
|
+
Loads translations through API routes. Best for production environments.
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
createCoreI18n({
|
|
104
|
+
translationLoader: 'api',
|
|
105
|
+
translationApiPath: '/api/translations', // default
|
|
106
|
+
defaultLanguage: 'ko',
|
|
107
|
+
namespaces: ['common', 'pages']
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**API Route Example (Next.js):**
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
// app/api/translations/[language]/[namespace]/route.ts
|
|
115
|
+
import { NextResponse } from 'next/server';
|
|
116
|
+
import { readFile } from 'fs/promises';
|
|
117
|
+
import { join } from 'path';
|
|
118
|
+
|
|
119
|
+
export async function GET(
|
|
120
|
+
request: Request,
|
|
121
|
+
{ params }: { params: Promise<{ language: string; namespace: string }> }
|
|
122
|
+
) {
|
|
123
|
+
const { language, namespace } = await params;
|
|
124
|
+
const translationPath = join(
|
|
125
|
+
process.cwd(),
|
|
126
|
+
'translations',
|
|
127
|
+
language,
|
|
128
|
+
`${namespace}.json`
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const fileContent = await readFile(translationPath, 'utf-8');
|
|
133
|
+
return NextResponse.json(JSON.parse(fileContent), {
|
|
134
|
+
headers: {
|
|
135
|
+
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400'
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return NextResponse.json({ error: 'Translation not found' }, { status: 404 });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 2. Static File Loader
|
|
145
|
+
|
|
146
|
+
Loads translations from static JSON files in the public directory.
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
createCoreI18n({
|
|
150
|
+
translationLoader: 'static',
|
|
151
|
+
defaultLanguage: 'ko',
|
|
152
|
+
namespaces: ['common', 'pages']
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The loader will try these paths:
|
|
157
|
+
- `/translations/{language}/{namespace}.json`
|
|
158
|
+
- `../translations/{language}/{namespace}.json`
|
|
159
|
+
- `./translations/{language}/${namespace}.json`
|
|
160
|
+
- `translations/{language}/${namespace}.json`
|
|
161
|
+
- `../../translations/{language}/${namespace}.json`
|
|
162
|
+
|
|
163
|
+
### 3. Custom Loader
|
|
164
|
+
|
|
165
|
+
Use your own translation loading function.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
createCoreI18n({
|
|
169
|
+
translationLoader: 'custom',
|
|
170
|
+
loadTranslations: async (language, namespace) => {
|
|
171
|
+
// Load from database, CMS, or any other source
|
|
172
|
+
const response = await fetch(`https://api.example.com/translations/${language}/${namespace}`);
|
|
173
|
+
return response.json();
|
|
174
|
+
},
|
|
175
|
+
defaultLanguage: 'ko',
|
|
176
|
+
namespaces: ['common', 'pages']
|
|
177
|
+
})
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## File Structure
|
|
181
|
+
|
|
182
|
+
Recommended file structure for translations:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
your-app/
|
|
186
|
+
├── translations/
|
|
187
|
+
│ ├── ko/
|
|
188
|
+
│ │ ├── common.json
|
|
189
|
+
│ │ ├── pages.json
|
|
190
|
+
│ │ └── footer.json
|
|
191
|
+
│ └── en/
|
|
192
|
+
│ ├── common.json
|
|
193
|
+
│ ├── pages.json
|
|
194
|
+
│ └── footer.json
|
|
195
|
+
└── app/
|
|
196
|
+
└── layout.tsx
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Translation File Format
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
// translations/en/common.json
|
|
203
|
+
{
|
|
204
|
+
"welcome": "Welcome",
|
|
205
|
+
"hello": "Hello",
|
|
206
|
+
"goodbye": "Goodbye",
|
|
207
|
+
"loading": "Loading...",
|
|
208
|
+
"error": "An error occurred"
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
// translations/en/pages.json
|
|
214
|
+
{
|
|
215
|
+
"home": {
|
|
216
|
+
"title": "Home",
|
|
217
|
+
"description": "Home page"
|
|
218
|
+
},
|
|
219
|
+
"about": {
|
|
220
|
+
"title": "About",
|
|
221
|
+
"description": "About page"
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Usage
|
|
227
|
+
|
|
228
|
+
### Basic Translation
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import { useTranslation } from '@hua-labs/i18n-core';
|
|
232
|
+
|
|
233
|
+
function MyComponent() {
|
|
234
|
+
const { t } = useTranslation();
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<div>
|
|
238
|
+
<h1>{t('common:welcome')}</h1>
|
|
239
|
+
<p>{t('pages:home.title')}</p>
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Translation with Parameters
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
import { useTranslation } from '@hua-labs/i18n-core';
|
|
249
|
+
|
|
250
|
+
function MyComponent() {
|
|
251
|
+
const { tWithParams } = useTranslation();
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<div>
|
|
255
|
+
<p>{tWithParams('common:greeting', { name: 'John' })}</p>
|
|
256
|
+
</div>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Translation file:
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"greeting": "Hello, {{name}}!"
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Getting Raw Values (Arrays and Objects)
|
|
269
|
+
|
|
270
|
+
Use `getRawValue` to access arrays, objects, or any non-string values from translation files:
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
import { useTranslation } from '@hua-labs/i18n-core';
|
|
274
|
+
|
|
275
|
+
function MyComponent() {
|
|
276
|
+
const { getRawValue } = useTranslation();
|
|
277
|
+
|
|
278
|
+
// Get an array
|
|
279
|
+
const features = getRawValue('common:features') as string[];
|
|
280
|
+
|
|
281
|
+
// Get an object
|
|
282
|
+
const metadata = getRawValue('common:metadata') as Record<string, string>;
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<div>
|
|
286
|
+
<ul>
|
|
287
|
+
{features?.map((feature, index) => (
|
|
288
|
+
<li key={index}>{feature}</li>
|
|
289
|
+
))}
|
|
290
|
+
</ul>
|
|
291
|
+
<div>
|
|
292
|
+
<p>Version: {metadata?.version}</p>
|
|
293
|
+
<p>Author: {metadata?.author}</p>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Translation file:
|
|
301
|
+
```json
|
|
302
|
+
{
|
|
303
|
+
"features": ["Fast", "Lightweight", "Type-safe"],
|
|
304
|
+
"metadata": {
|
|
305
|
+
"version": "1.0.0",
|
|
306
|
+
"author": "HUA Labs"
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Language Switching
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
import { useLanguageChange } from '@hua-labs/i18n-core';
|
|
315
|
+
|
|
316
|
+
function LanguageSwitcher() {
|
|
317
|
+
const { changeLanguage, supportedLanguages, currentLanguage } = useLanguageChange();
|
|
318
|
+
|
|
319
|
+
return (
|
|
320
|
+
<div>
|
|
321
|
+
{supportedLanguages.map(lang => (
|
|
322
|
+
<button
|
|
323
|
+
key={lang.code}
|
|
324
|
+
onClick={() => changeLanguage(lang.code)}
|
|
325
|
+
disabled={lang.code === currentLanguage}
|
|
326
|
+
>
|
|
327
|
+
{lang.nativeName}
|
|
328
|
+
</button>
|
|
329
|
+
))}
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Advanced Hook Usage
|
|
336
|
+
|
|
337
|
+
```tsx
|
|
338
|
+
import { useTranslation } from '@hua-labs/i18n-core';
|
|
339
|
+
|
|
340
|
+
function MyComponent() {
|
|
341
|
+
const {
|
|
342
|
+
t,
|
|
343
|
+
tWithParams,
|
|
344
|
+
currentLanguage,
|
|
345
|
+
setLanguage,
|
|
346
|
+
isLoading,
|
|
347
|
+
error,
|
|
348
|
+
supportedLanguages,
|
|
349
|
+
isInitialized,
|
|
350
|
+
debug
|
|
351
|
+
} = useTranslation();
|
|
352
|
+
|
|
353
|
+
if (isLoading) {
|
|
354
|
+
return <div>Loading translations...</div>;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (error) {
|
|
358
|
+
return <div>Error: {error.message}</div>;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return (
|
|
362
|
+
<div>
|
|
363
|
+
<h1>{t('common:welcome')}</h1>
|
|
364
|
+
<p>Current language: {currentLanguage}</p>
|
|
365
|
+
<p>Loaded namespaces: {debug.getLoadedNamespaces().join(', ')}</p>
|
|
366
|
+
</div>
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## SSR Support
|
|
372
|
+
|
|
373
|
+
### Server-Side Translation
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
import { Translator, ssrTranslate, serverTranslate } from '@hua-labs/i18n-core';
|
|
377
|
+
|
|
378
|
+
// Using Translator class
|
|
379
|
+
export async function getServerTranslations(language: string) {
|
|
380
|
+
const translator = await Translator.create({
|
|
381
|
+
defaultLanguage: language,
|
|
382
|
+
namespaces: ['common', 'pages'],
|
|
383
|
+
loadTranslations: async (lang, namespace) => {
|
|
384
|
+
const path = `./translations/${lang}/${namespace}.json`;
|
|
385
|
+
return (await import(path)).default;
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
welcome: translator.translate('common:welcome'),
|
|
391
|
+
title: translator.translate('pages:home.title')
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Using helper functions
|
|
396
|
+
export function getStaticTranslations(language: string) {
|
|
397
|
+
const translations = require(`./translations/${language}/common.json`);
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
welcome: ssrTranslate({
|
|
401
|
+
translations,
|
|
402
|
+
key: 'common:welcome',
|
|
403
|
+
language
|
|
404
|
+
})
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### SSR with Initial Translations (Recommended)
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
// app/layout.tsx (Server Component)
|
|
413
|
+
import { loadSSRTranslations } from './lib/ssr-translations';
|
|
414
|
+
import { createCoreI18n } from '@hua-labs/i18n-core';
|
|
415
|
+
|
|
416
|
+
export default async function RootLayout({ children }) {
|
|
417
|
+
// Load translation data from SSR
|
|
418
|
+
const ssrTranslations = await loadSSRTranslations('ko');
|
|
419
|
+
|
|
420
|
+
const I18nProvider = createCoreI18n({
|
|
421
|
+
defaultLanguage: 'ko',
|
|
422
|
+
fallbackLanguage: 'en',
|
|
423
|
+
namespaces: ['common', 'navigation', 'footer'],
|
|
424
|
+
initialTranslations: ssrTranslations, // Pass SSR translation data
|
|
425
|
+
translationLoader: 'api'
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
return (
|
|
429
|
+
<html lang="ko">
|
|
430
|
+
<body>
|
|
431
|
+
<I18nProvider>
|
|
432
|
+
{children}
|
|
433
|
+
</I18nProvider>
|
|
434
|
+
</body>
|
|
435
|
+
</html>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## Key Rules
|
|
441
|
+
|
|
442
|
+
### Namespace Keys
|
|
443
|
+
|
|
444
|
+
Always include namespace in the key:
|
|
445
|
+
|
|
446
|
+
```tsx
|
|
447
|
+
t('common:welcome') // common.json -> welcome
|
|
448
|
+
t('pages:home.title') // pages.json -> home.title
|
|
449
|
+
t('footer:brand_name') // footer.json -> brand_name
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Common Namespace Shortcut
|
|
453
|
+
|
|
454
|
+
If the key doesn't include a namespace, it defaults to 'common':
|
|
455
|
+
|
|
456
|
+
```tsx
|
|
457
|
+
t('welcome') // same as t('common:welcome')
|
|
458
|
+
t('hello') // same as t('common:hello')
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Configuration Options
|
|
462
|
+
|
|
463
|
+
```tsx
|
|
464
|
+
createCoreI18n({
|
|
465
|
+
// Required
|
|
466
|
+
defaultLanguage: 'ko',
|
|
467
|
+
|
|
468
|
+
// Optional
|
|
469
|
+
fallbackLanguage: 'en',
|
|
470
|
+
namespaces: ['common', 'pages'],
|
|
471
|
+
debug: false,
|
|
472
|
+
|
|
473
|
+
// Loader options
|
|
474
|
+
translationLoader: 'api' | 'static' | 'custom',
|
|
475
|
+
translationApiPath: '/api/translations',
|
|
476
|
+
loadTranslations: async (language, namespace) => {
|
|
477
|
+
// Custom loader function
|
|
478
|
+
},
|
|
479
|
+
|
|
480
|
+
// SSR optimization: Pre-loaded translations (no network requests)
|
|
481
|
+
// Prevents missing key exposure during initial load
|
|
482
|
+
initialTranslations: {
|
|
483
|
+
ko: {
|
|
484
|
+
common: { /* ... */ },
|
|
485
|
+
navigation: { /* ... */ }
|
|
486
|
+
},
|
|
487
|
+
en: {
|
|
488
|
+
common: { /* ... */ },
|
|
489
|
+
navigation: { /* ... */ }
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
// Auto language sync (disabled by default when using Zustand adapter)
|
|
494
|
+
autoLanguageSync: false
|
|
495
|
+
})
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## State Management Integration
|
|
499
|
+
|
|
500
|
+
### Zustand Integration
|
|
501
|
+
|
|
502
|
+
For Zustand users, use the dedicated adapter package:
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
pnpm add @hua-labs/i18n-core-zustand zustand
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
```tsx
|
|
509
|
+
import { createZustandI18n } from '@hua-labs/i18n-core-zustand';
|
|
510
|
+
import { useAppStore } from './store/useAppStore';
|
|
511
|
+
|
|
512
|
+
export const I18nProvider = createZustandI18n(useAppStore, {
|
|
513
|
+
fallbackLanguage: 'en',
|
|
514
|
+
namespaces: ['common', 'navigation'],
|
|
515
|
+
defaultLanguage: 'ko', // SSR initial language
|
|
516
|
+
initialTranslations: ssrTranslations // Optional: SSR translations
|
|
517
|
+
});
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
See [@hua-labs/i18n-core-zustand README](../hua-i18n-core-zustand/README.md) for full documentation.
|
|
521
|
+
|
|
522
|
+
## Language Change Optimization
|
|
523
|
+
|
|
524
|
+
The library automatically prevents flickering during language changes by temporarily showing translations from the previous language while new translations are loading.
|
|
525
|
+
|
|
526
|
+
**How it works:**
|
|
527
|
+
1. When language changes, `translator.setLanguage()` is called
|
|
528
|
+
2. If a translation key is not found in the new language yet, the library checks other loaded languages
|
|
529
|
+
3. If found, it temporarily returns the previous language's translation
|
|
530
|
+
4. Once the new language's translation is loaded, it automatically updates
|
|
531
|
+
|
|
532
|
+
This ensures a smooth user experience without showing translation keys or empty strings.
|
|
533
|
+
|
|
534
|
+
## Debug Mode
|
|
535
|
+
|
|
536
|
+
Enable debug mode to see translation loading and missing keys:
|
|
537
|
+
|
|
538
|
+
```tsx
|
|
539
|
+
createCoreI18n({
|
|
540
|
+
debug: true,
|
|
541
|
+
// ... other options
|
|
542
|
+
})
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**Note**: In production (`debug: false`), console logs are automatically suppressed to improve performance and prevent information leakage.
|
|
546
|
+
|
|
547
|
+
### Missing Key Overlay (Development)
|
|
548
|
+
|
|
549
|
+
Display missing translation keys in development:
|
|
550
|
+
|
|
551
|
+
```tsx
|
|
552
|
+
import { MissingKeyOverlay } from '@hua-labs/i18n-core/components/MissingKeyOverlay';
|
|
553
|
+
|
|
554
|
+
function DebugBar() {
|
|
555
|
+
if (process.env.NODE_ENV !== 'development') return null;
|
|
556
|
+
|
|
557
|
+
return <MissingKeyOverlay />;
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
## Error Handling
|
|
562
|
+
|
|
563
|
+
The library includes built-in error handling:
|
|
564
|
+
|
|
565
|
+
- **Automatic fallback**: Falls back to default language when translations are missing
|
|
566
|
+
- **Missing key handling**: Returns key in debug mode, empty string in production
|
|
567
|
+
- **Network error recovery**: Automatic retry with exponential backoff (when using API loader)
|
|
568
|
+
- **Cache invalidation**: Automatically clears cache on errors
|
|
569
|
+
- **Error classification**: Distinguishes between recoverable and non-recoverable errors
|
|
570
|
+
- **Memory leak prevention**: LRU cache for Translator instances (max 10 instances)
|
|
571
|
+
|
|
572
|
+
## API Reference
|
|
573
|
+
|
|
574
|
+
### Main Exports
|
|
575
|
+
|
|
576
|
+
- `createCoreI18n(options?)` - Creates i18n Provider component
|
|
577
|
+
- `useTranslation()` - Hook for translations and language state
|
|
578
|
+
- `useLanguageChange()` - Hook for language switching
|
|
579
|
+
- `Translator` - Core translation class (for SSR)
|
|
580
|
+
- `ssrTranslate()` / `serverTranslate()` - Server-side translation helpers
|
|
581
|
+
|
|
582
|
+
## Requirements
|
|
583
|
+
|
|
584
|
+
- React >= 16.8.0
|
|
585
|
+
- TypeScript (recommended)
|
|
586
|
+
|
|
587
|
+
## Bundle Size
|
|
588
|
+
|
|
589
|
+
- **~2.8 KB** gzipped
|
|
590
|
+
- Zero dependencies (React only as peer dependency)
|
|
591
|
+
|
|
592
|
+
## Troubleshooting
|
|
593
|
+
|
|
594
|
+
### Translations Not Loading
|
|
595
|
+
|
|
596
|
+
1. Check file paths match the expected structure
|
|
597
|
+
2. Verify JSON format is valid
|
|
598
|
+
3. Check network requests in browser DevTools
|
|
599
|
+
4. Enable debug mode to see loading logs
|
|
600
|
+
|
|
601
|
+
### Missing Keys
|
|
602
|
+
|
|
603
|
+
1. Ensure namespace is included in key: `t('namespace:key')`
|
|
604
|
+
2. Check translation files contain the key
|
|
605
|
+
3. Verify namespace is included in config: `namespaces: ['namespace']`
|
|
606
|
+
|
|
607
|
+
### API Loader Not Working
|
|
608
|
+
|
|
609
|
+
1. Verify API route is accessible
|
|
610
|
+
2. Check API route returns valid JSON
|
|
611
|
+
3. Ensure API route handles 404 errors gracefully
|
|
612
|
+
4. Check CORS settings if loading from different domain
|
|
613
|
+
|
|
614
|
+
## Documentation
|
|
615
|
+
|
|
616
|
+
- [Architecture Guide](./docs/ARCHITECTURE.md) - Core architecture and design patterns
|
|
617
|
+
|
|
618
|
+
## Code Quality
|
|
619
|
+
|
|
620
|
+
This package has been refactored for better maintainability:
|
|
621
|
+
|
|
622
|
+
- **Modular functions**: Translation logic split into focused helper methods
|
|
623
|
+
- **Type safety**: Improved type guards and error handling
|
|
624
|
+
- **Performance**: Optimized translation lookup with proper memoization
|
|
625
|
+
- **Code clarity**: Removed commented code and improved function organization
|
|
626
|
+
|
|
627
|
+
## Related Packages
|
|
628
|
+
|
|
629
|
+
- `@hua-labs/i18n-core-zustand`: Zustand state management integration adapter
|
|
630
|
+
- `@hua-labs/i18n-loaders`: Production-ready loaders, caching, and preloading helpers
|
|
631
|
+
- `@hua-labs/i18n-advanced`: Advanced features like pluralization, date formatting, etc.
|
|
632
|
+
- `@hua-labs/i18n-debug`: Debug tools and development helpers
|
|
633
|
+
|
|
634
|
+
## License
|
|
635
|
+
|
|
636
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface MissingKeyOverlayProps {
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
|
|
5
|
+
style?: React.CSSProperties;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 개발 모드에서 누락된 번역 키를 화면에 표시하는 오버레이
|
|
9
|
+
* Lingui 스타일의 디버그 모드
|
|
10
|
+
*/
|
|
11
|
+
export declare const MissingKeyOverlay: React.FC<MissingKeyOverlayProps>;
|
|
12
|
+
/**
|
|
13
|
+
* 누락된 키를 오버레이에 표시하는 유틸리티 함수
|
|
14
|
+
*/
|
|
15
|
+
export declare const reportMissingKey: (key: string, options: {
|
|
16
|
+
namespace?: string;
|
|
17
|
+
language: string;
|
|
18
|
+
component?: string;
|
|
19
|
+
}) => void;
|
|
20
|
+
/**
|
|
21
|
+
* 누락된 키 오버레이를 자동으로 활성화하는 훅
|
|
22
|
+
*/
|
|
23
|
+
export declare const useMissingKeyOverlay: (enabled?: boolean) => {
|
|
24
|
+
showOverlay: boolean;
|
|
25
|
+
setShowOverlay: React.Dispatch<React.SetStateAction<boolean>>;
|
|
26
|
+
reportMissingKey: (key: string, options: {
|
|
27
|
+
namespace?: string;
|
|
28
|
+
language: string;
|
|
29
|
+
component?: string;
|
|
30
|
+
}) => void;
|
|
31
|
+
};
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=MissingKeyOverlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MissingKeyOverlay.d.ts","sourceRoot":"","sources":["../../src/components/MissingKeyOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,UAAU,sBAAsB;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,aAAa,CAAC;IACrE,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAUD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAwJ9D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,EAAE,SAAS;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,SAsBA,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAI,iBAAc;;;4BA/Bb,MAAM,WAAW;QACrD,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;CAyCA,CAAC"}
|