@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
package/dist/server.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hua-labs/i18n-core",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "HUA Labs - Core i18n functionality with SSR/CSR support and state management integration",
|
|
5
|
-
"main": "./dist/index.
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"files": [
|
|
@@ -11,13 +11,16 @@
|
|
|
11
11
|
],
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
|
+
"react-native": "./dist/index.mjs",
|
|
14
15
|
"types": "./dist/index.d.ts",
|
|
15
16
|
"import": "./dist/index.mjs",
|
|
17
|
+
"require": "./dist/index.cjs",
|
|
16
18
|
"default": "./dist/index.mjs"
|
|
17
19
|
},
|
|
18
20
|
"./server": {
|
|
19
21
|
"types": "./dist/server.d.ts",
|
|
20
22
|
"import": "./dist/server.mjs",
|
|
23
|
+
"require": "./dist/server.cjs",
|
|
21
24
|
"default": "./dist/server.mjs"
|
|
22
25
|
}
|
|
23
26
|
},
|
|
@@ -28,16 +31,16 @@
|
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"@testing-library/jest-dom": "^6.6.3",
|
|
30
33
|
"@testing-library/react": "^16.3.0",
|
|
31
|
-
"@types/node": "^25.3
|
|
32
|
-
"@types/react": "^19.2.
|
|
33
|
-
"@vitejs/plugin-react": "^5.
|
|
34
|
-
"eslint": "^10.0
|
|
35
|
-
"jsdom": "^
|
|
36
|
-
"react": "^19.2.
|
|
37
|
-
"react-dom": "^19.2.
|
|
34
|
+
"@types/node": "^25.9.3",
|
|
35
|
+
"@types/react": "^19.2.17",
|
|
36
|
+
"@vitejs/plugin-react": "^5.2.0",
|
|
37
|
+
"eslint": "^10.5.0",
|
|
38
|
+
"jsdom": "^29.1.1",
|
|
39
|
+
"react": "^19.2.7",
|
|
40
|
+
"react-dom": "^19.2.7",
|
|
38
41
|
"tsup": "^8.5.1",
|
|
39
42
|
"typescript": "^5.9.3",
|
|
40
|
-
"vitest": "^4.
|
|
43
|
+
"vitest": "^4.1.8"
|
|
41
44
|
},
|
|
42
45
|
"peerDependencies": {
|
|
43
46
|
"react": ">=19.0.0"
|
|
@@ -59,12 +62,12 @@
|
|
|
59
62
|
"license": "MIT",
|
|
60
63
|
"repository": {
|
|
61
64
|
"type": "git",
|
|
62
|
-
"url": "https://github.com/HUA-Labs/
|
|
65
|
+
"url": "https://github.com/HUA-Labs/hua-packages.git"
|
|
63
66
|
},
|
|
64
67
|
"bugs": {
|
|
65
|
-
"url": "https://github.com/HUA-Labs/
|
|
68
|
+
"url": "https://github.com/HUA-Labs/hua-packages/issues"
|
|
66
69
|
},
|
|
67
|
-
"homepage": "https://github.com/HUA-Labs/
|
|
70
|
+
"homepage": "https://github.com/HUA-Labs/hua-packages#readme",
|
|
68
71
|
"publishConfig": {
|
|
69
72
|
"access": "public",
|
|
70
73
|
"provenance": true
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { Translator } from "../core/translator";
|
|
3
|
+
import { I18nConfig } from "../types";
|
|
4
|
+
|
|
5
|
+
function createConfig(overrides?: Partial<I18nConfig>): I18nConfig {
|
|
6
|
+
return {
|
|
7
|
+
defaultLanguage: "ko",
|
|
8
|
+
fallbackLanguage: "en",
|
|
9
|
+
supportedLanguages: [
|
|
10
|
+
{ code: "ko", name: "Korean", nativeName: "한국어" },
|
|
11
|
+
{ code: "en", name: "English", nativeName: "English" },
|
|
12
|
+
],
|
|
13
|
+
namespaces: ["common"],
|
|
14
|
+
loadTranslations: vi
|
|
15
|
+
.fn()
|
|
16
|
+
.mockImplementation(async (lang: string, ns: string) => {
|
|
17
|
+
if (lang === "ko" && ns === "common") {
|
|
18
|
+
return { greeting: "안녕하세요", welcome: "{name}님 환영합니다" };
|
|
19
|
+
}
|
|
20
|
+
if (lang === "en" && ns === "common") {
|
|
21
|
+
return { greeting: "Hello", welcome: "Welcome {name}" };
|
|
22
|
+
}
|
|
23
|
+
return {};
|
|
24
|
+
}),
|
|
25
|
+
...overrides,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("defaultValue support", () => {
|
|
30
|
+
let translator: Translator;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.useFakeTimers();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
vi.useRealTimers();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("Translator.translate()", () => {
|
|
41
|
+
beforeEach(async () => {
|
|
42
|
+
translator = new Translator(createConfig());
|
|
43
|
+
await translator.initialize();
|
|
44
|
+
vi.advanceTimersByTime(100);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("returns translation when key exists (ignores defaultValue)", () => {
|
|
48
|
+
const result = translator.translate("common:greeting", {
|
|
49
|
+
defaultValue: "Fallback",
|
|
50
|
+
});
|
|
51
|
+
expect(result).toBe("안녕하세요");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns defaultValue when key is missing", () => {
|
|
55
|
+
const result = translator.translate("common:nonexistent", {
|
|
56
|
+
defaultValue: "Fallback text",
|
|
57
|
+
});
|
|
58
|
+
expect(result).toBe("Fallback text");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("interpolates variables in defaultValue", () => {
|
|
62
|
+
const result = translator.translate("common:missing", {
|
|
63
|
+
defaultValue: "Hello {{name}}",
|
|
64
|
+
name: "World",
|
|
65
|
+
});
|
|
66
|
+
expect(result).toBe("Hello World");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("returns empty string when key is missing and no defaultValue (production)", () => {
|
|
70
|
+
const result = translator.translate("common:nonexistent");
|
|
71
|
+
expect(result).toBe("");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("returns defaultValue before initialization", () => {
|
|
75
|
+
const fresh = new Translator(createConfig());
|
|
76
|
+
// Not initialized — should still return defaultValue
|
|
77
|
+
const result = fresh.translate("common:missing", {
|
|
78
|
+
defaultValue: "Hello {{name}}",
|
|
79
|
+
name: "World",
|
|
80
|
+
});
|
|
81
|
+
expect(result).toBe("Hello World");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("returns key when missing and debug mode (no defaultValue)", () => {
|
|
85
|
+
const debugTranslator = new Translator(createConfig({ debug: true }));
|
|
86
|
+
// Use initialTranslations to bypass async init
|
|
87
|
+
const config = createConfig({
|
|
88
|
+
debug: true,
|
|
89
|
+
initialTranslations: { ko: { common: {} }, en: { common: {} } },
|
|
90
|
+
});
|
|
91
|
+
const t = new Translator(config);
|
|
92
|
+
const result = t.translate("common:nonexistent");
|
|
93
|
+
expect(result).toBe("common:nonexistent");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("Translator.translateAsync()", () => {
|
|
98
|
+
beforeEach(async () => {
|
|
99
|
+
translator = new Translator(createConfig());
|
|
100
|
+
await translator.initialize();
|
|
101
|
+
vi.advanceTimersByTime(100);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("returns defaultValue when key is missing", async () => {
|
|
105
|
+
const result = await translator.translateAsync("common:missing", {
|
|
106
|
+
defaultValue: "Async fallback",
|
|
107
|
+
});
|
|
108
|
+
expect(result).toBe("Async fallback");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("returns translation when key exists", async () => {
|
|
112
|
+
const result = await translator.translateAsync("common:greeting", {
|
|
113
|
+
defaultValue: "Fallback",
|
|
114
|
+
});
|
|
115
|
+
expect(result).toBe("안녕하세요");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("Translator.translateSync()", () => {
|
|
120
|
+
beforeEach(async () => {
|
|
121
|
+
translator = new Translator(createConfig());
|
|
122
|
+
await translator.initialize();
|
|
123
|
+
vi.advanceTimersByTime(100);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("returns defaultValue when key is missing", () => {
|
|
127
|
+
const result = translator.translateSync("common:missing", {
|
|
128
|
+
defaultValue: "Sync fallback",
|
|
129
|
+
});
|
|
130
|
+
expect(result).toBe("Sync fallback");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("returns translation when key exists", () => {
|
|
134
|
+
const result = translator.translateSync("common:greeting", {
|
|
135
|
+
defaultValue: "Fallback",
|
|
136
|
+
});
|
|
137
|
+
expect(result).toBe("안녕하세요");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("returns defaultValue even before initialization", () => {
|
|
141
|
+
const fresh = new Translator(createConfig());
|
|
142
|
+
// Not initialized — should still return defaultValue
|
|
143
|
+
const result = fresh.translateSync("common:missing", {
|
|
144
|
+
defaultValue: "Before init",
|
|
145
|
+
});
|
|
146
|
+
expect(result).toBe("Before init");
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -189,10 +189,12 @@ export const reportMissingKey = (key: string, options: {
|
|
|
189
189
|
component: options.component
|
|
190
190
|
};
|
|
191
191
|
|
|
192
|
-
// 커스텀 이벤트 발생
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
// 커스텀 이벤트 발생 (브라우저 환경에서만)
|
|
193
|
+
if (typeof window !== 'undefined' && typeof CustomEvent !== 'undefined') {
|
|
194
|
+
window.dispatchEvent(new CustomEvent('i18n:missing-key', {
|
|
195
|
+
detail: missingKey
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
196
198
|
|
|
197
199
|
// 콘솔에도 로그
|
|
198
200
|
if (process.env.NODE_ENV === 'development') console.warn(`Missing translation key: ${key}`, {
|