@hua-labs/i18n-core 1.0.0 → 2.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 +20 -20
- package/README.md +636 -636
- package/dist/core/debug-tools.js +53 -53
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/MissingKeyOverlay.tsx +222 -222
- package/src/core/debug-tools.ts +297 -297
- package/src/core/i18n-resource.ts +179 -179
- package/src/core/lazy-loader.ts +254 -254
- package/src/core/translator-factory.ts +136 -136
- package/src/core/translator.tsx +1193 -1193
- package/src/hooks/useI18n.tsx +594 -594
- package/src/hooks/useTranslation.tsx +61 -61
- package/src/index.ts +297 -297
- package/src/types/index.ts +442 -442
- package/src/utils/default-translations.ts +129 -129
package/src/core/debug-tools.ts
CHANGED
|
@@ -1,298 +1,298 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 조건부 디버깅 도구 - 개발 환경에서만 로드
|
|
3
|
-
* 번들 크기 최적화를 위해 조건부 로딩
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface DebugTools {
|
|
7
|
-
// 번역 키 시각화
|
|
8
|
-
highlightMissingKeys: (container: HTMLElement) => void;
|
|
9
|
-
showTranslationKeys: (container: HTMLElement) => void;
|
|
10
|
-
|
|
11
|
-
// 성능 모니터링
|
|
12
|
-
performanceMetrics: {
|
|
13
|
-
translationCount: number;
|
|
14
|
-
cacheHits: number;
|
|
15
|
-
cacheMisses: number;
|
|
16
|
-
loadTimes: number[];
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// 개발자 도구
|
|
20
|
-
devTools: {
|
|
21
|
-
open: () => void;
|
|
22
|
-
close: () => void;
|
|
23
|
-
isOpen: boolean;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// 번역 데이터 검증
|
|
27
|
-
validateTranslations: (translations: Record<string, unknown>) => {
|
|
28
|
-
missingKeys: string[];
|
|
29
|
-
duplicateKeys: string[];
|
|
30
|
-
invalidKeys: string[];
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 디버깅 도구 팩토리 - 조건부 로딩
|
|
36
|
-
*/
|
|
37
|
-
export function createDebugTools(): DebugTools | null {
|
|
38
|
-
// 프로덕션에서는 디버깅 도구 비활성화
|
|
39
|
-
if (process.env.NODE_ENV === 'production') {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// 개발 환경에서만 로드
|
|
44
|
-
return {
|
|
45
|
-
highlightMissingKeys: (container: HTMLElement) => {
|
|
46
|
-
const elements = container.querySelectorAll('[data-i18n-key]');
|
|
47
|
-
elements.forEach((element) => {
|
|
48
|
-
const key = element.getAttribute('data-i18n-key');
|
|
49
|
-
const text = element.textContent;
|
|
50
|
-
|
|
51
|
-
// 번역 키가 텍스트와 다른 경우 하이라이트
|
|
52
|
-
if (key && text === key) {
|
|
53
|
-
(element as HTMLElement).style.backgroundColor = '#ffeb3b';
|
|
54
|
-
(element as HTMLElement).style.border = '2px solid #f57c00';
|
|
55
|
-
(element as HTMLElement).title = `Missing translation: ${key}`;
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
showTranslationKeys: (container: HTMLElement) => {
|
|
61
|
-
const elements = container.querySelectorAll('[data-i18n-key]');
|
|
62
|
-
elements.forEach((element) => {
|
|
63
|
-
const key = element.getAttribute('data-i18n-key');
|
|
64
|
-
if (key) {
|
|
65
|
-
const tooltip = document.createElement('div');
|
|
66
|
-
tooltip.style.cssText = `
|
|
67
|
-
position: absolute;
|
|
68
|
-
background: #333;
|
|
69
|
-
color: white;
|
|
70
|
-
padding: 4px 8px;
|
|
71
|
-
border-radius: 4px;
|
|
72
|
-
font-size: 12px;
|
|
73
|
-
z-index: 10000;
|
|
74
|
-
pointer-events: none;
|
|
75
|
-
opacity: 0;
|
|
76
|
-
transition: opacity 0.2s;
|
|
77
|
-
`;
|
|
78
|
-
tooltip.textContent = `Key: ${key}`;
|
|
79
|
-
|
|
80
|
-
element.addEventListener('mouseenter', () => {
|
|
81
|
-
const rect = element.getBoundingClientRect();
|
|
82
|
-
tooltip.style.left = `${rect.left}px`;
|
|
83
|
-
tooltip.style.top = `${rect.bottom + 5}px`;
|
|
84
|
-
tooltip.style.opacity = '1';
|
|
85
|
-
document.body.appendChild(tooltip);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
element.addEventListener('mouseleave', () => {
|
|
89
|
-
tooltip.style.opacity = '0';
|
|
90
|
-
setTimeout(() => {
|
|
91
|
-
if (tooltip.parentNode) {
|
|
92
|
-
tooltip.parentNode.removeChild(tooltip);
|
|
93
|
-
}
|
|
94
|
-
}, 200);
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
performanceMetrics: {
|
|
101
|
-
translationCount: 0,
|
|
102
|
-
cacheHits: 0,
|
|
103
|
-
cacheMisses: 0,
|
|
104
|
-
loadTimes: [],
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
devTools: {
|
|
108
|
-
isOpen: false,
|
|
109
|
-
open: () => {
|
|
110
|
-
const devTools = createDevToolsPanel();
|
|
111
|
-
document.body.appendChild(devTools);
|
|
112
|
-
(devTools as HTMLElement & { isOpen?: boolean }).isOpen = true;
|
|
113
|
-
},
|
|
114
|
-
close: () => {
|
|
115
|
-
const existingPanel = document.getElementById('hua-i18n-devtools');
|
|
116
|
-
if (existingPanel) {
|
|
117
|
-
existingPanel.remove();
|
|
118
|
-
}
|
|
119
|
-
// devTools 변수는 이 스코프에서 접근할 수 없으므로 전역에서 관리
|
|
120
|
-
const globalDebugTools = (window as Window & { __HUA_I18N_DEBUG__?: DebugTools }).__HUA_I18N_DEBUG__;
|
|
121
|
-
if (globalDebugTools) {
|
|
122
|
-
globalDebugTools.devTools.isOpen = false;
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
validateTranslations: (translations: Record<string, unknown>) => {
|
|
128
|
-
const missingKeys: string[] = [];
|
|
129
|
-
const duplicateKeys: string[] = [];
|
|
130
|
-
const invalidKeys: string[] = [];
|
|
131
|
-
const seenKeys = new Set<string>();
|
|
132
|
-
|
|
133
|
-
const traverse = (obj: unknown, path: string = '') => {
|
|
134
|
-
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
138
|
-
const currentPath = path ? `${path}.${key}` : key;
|
|
139
|
-
|
|
140
|
-
// 키 유효성 검사
|
|
141
|
-
if (typeof key !== 'string' || key.trim() === '') {
|
|
142
|
-
invalidKeys.push(currentPath);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// 중복 키 검사
|
|
146
|
-
if (seenKeys.has(currentPath)) {
|
|
147
|
-
duplicateKeys.push(currentPath);
|
|
148
|
-
} else {
|
|
149
|
-
seenKeys.add(currentPath);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// 값 검사
|
|
153
|
-
if (value === null || value === undefined) {
|
|
154
|
-
missingKeys.push(currentPath);
|
|
155
|
-
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
156
|
-
traverse(value, currentPath);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
traverse(translations);
|
|
162
|
-
|
|
163
|
-
return { missingKeys, duplicateKeys, invalidKeys };
|
|
164
|
-
},
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* 개발자 도구 패널 생성
|
|
170
|
-
*/
|
|
171
|
-
function createDevToolsPanel(): HTMLElement {
|
|
172
|
-
const panel = document.createElement('div');
|
|
173
|
-
panel.id = 'hua-i18n-devtools';
|
|
174
|
-
panel.style.cssText = `
|
|
175
|
-
position: fixed;
|
|
176
|
-
top: 20px;
|
|
177
|
-
right: 20px;
|
|
178
|
-
width: 300px;
|
|
179
|
-
max-height: 500px;
|
|
180
|
-
background: #1e1e1e;
|
|
181
|
-
color: #fff;
|
|
182
|
-
border-radius: 8px;
|
|
183
|
-
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
184
|
-
z-index: 10000;
|
|
185
|
-
font-family: 'Monaco', 'Menlo', monospace;
|
|
186
|
-
font-size: 12px;
|
|
187
|
-
overflow: hidden;
|
|
188
|
-
`;
|
|
189
|
-
|
|
190
|
-
const header = document.createElement('div');
|
|
191
|
-
header.style.cssText = `
|
|
192
|
-
background: #2d2d2d;
|
|
193
|
-
padding: 12px;
|
|
194
|
-
border-bottom: 1px solid #444;
|
|
195
|
-
display: flex;
|
|
196
|
-
justify-content: space-between;
|
|
197
|
-
align-items: center;
|
|
198
|
-
`;
|
|
199
|
-
header.innerHTML = `
|
|
200
|
-
<span style="font-weight: bold;">HUA i18n Debug</span>
|
|
201
|
-
<button id="close-devtools" style="background: none; border: none; color: #fff; cursor: pointer; font-size: 16px;">×</button>
|
|
202
|
-
`;
|
|
203
|
-
|
|
204
|
-
const content = document.createElement('div');
|
|
205
|
-
content.style.cssText = `
|
|
206
|
-
padding: 12px;
|
|
207
|
-
max-height: 400px;
|
|
208
|
-
overflow-y: auto;
|
|
209
|
-
`;
|
|
210
|
-
|
|
211
|
-
// 성능 메트릭
|
|
212
|
-
const metricsSection = document.createElement('div');
|
|
213
|
-
metricsSection.innerHTML = `
|
|
214
|
-
<h4 style="margin: 0 0 8px 0; color: #4fc3f7;">Performance</h4>
|
|
215
|
-
<div>Translations: <span id="translation-count">0</span></div>
|
|
216
|
-
<div>Cache Hits: <span id="cache-hits">0</span></div>
|
|
217
|
-
<div>Cache Misses: <span id="cache-misses">0</span></div>
|
|
218
|
-
<div>Hit Rate: <span id="hit-rate">0%</span></div>
|
|
219
|
-
`;
|
|
220
|
-
|
|
221
|
-
// 현재 언어 정보
|
|
222
|
-
const languageSection = document.createElement('div');
|
|
223
|
-
languageSection.style.marginTop = '16px';
|
|
224
|
-
languageSection.innerHTML = `
|
|
225
|
-
<h4 style="margin: 0 0 8px 0; color: #4fc3f7;">Current Language</h4>
|
|
226
|
-
<div>Language: <span id="current-language">ko</span></div>
|
|
227
|
-
<div>Fallback: <span id="fallback-language">en</span></div>
|
|
228
|
-
`;
|
|
229
|
-
|
|
230
|
-
// 액션 버튼들
|
|
231
|
-
const actionsSection = document.createElement('div');
|
|
232
|
-
actionsSection.style.marginTop = '16px';
|
|
233
|
-
actionsSection.innerHTML = `
|
|
234
|
-
<h4 style="margin: 0 0 8px 0; color: #4fc3f7;">Actions</h4>
|
|
235
|
-
<button id="highlight-missing" style="background: #4fc3f7; border: none; color: #000; padding: 4px 8px; border-radius: 4px; cursor: pointer; margin-right: 8px;">Highlight Missing</button>
|
|
236
|
-
<button id="show-keys" style="background: #4fc3f7; border: none; color: #000; padding: 4px 8px; border-radius: 4px; cursor: pointer;">Show Keys</button>
|
|
237
|
-
`;
|
|
238
|
-
|
|
239
|
-
content.appendChild(metricsSection);
|
|
240
|
-
content.appendChild(languageSection);
|
|
241
|
-
content.appendChild(actionsSection);
|
|
242
|
-
|
|
243
|
-
panel.appendChild(header);
|
|
244
|
-
panel.appendChild(content);
|
|
245
|
-
|
|
246
|
-
// 이벤트 리스너
|
|
247
|
-
panel.querySelector('#close-devtools')?.addEventListener('click', () => {
|
|
248
|
-
panel.remove();
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
panel.querySelector('#highlight-missing')?.addEventListener('click', () => {
|
|
252
|
-
const debugTools = createDebugTools();
|
|
253
|
-
if (debugTools) {
|
|
254
|
-
debugTools.highlightMissingKeys(document.body);
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
panel.querySelector('#show-keys')?.addEventListener('click', () => {
|
|
259
|
-
const debugTools = createDebugTools();
|
|
260
|
-
if (debugTools) {
|
|
261
|
-
debugTools.showTranslationKeys(document.body);
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
return panel;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* 디버깅 도구 활성화 (전역 함수)
|
|
270
|
-
*/
|
|
271
|
-
export function enableDebugTools(): void {
|
|
272
|
-
if (process.env.NODE_ENV === 'production') {
|
|
273
|
-
console.warn('Debug tools are not available in production');
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const debugTools = createDebugTools();
|
|
278
|
-
if (!debugTools) return;
|
|
279
|
-
|
|
280
|
-
// 전역 객체에 추가
|
|
281
|
-
(window as Window & { __HUA_I18N_DEBUG__?: DebugTools }).__HUA_I18N_DEBUG__ = debugTools;
|
|
282
|
-
|
|
283
|
-
// 개발자 도구 열기
|
|
284
|
-
debugTools.devTools.open();
|
|
285
|
-
|
|
286
|
-
console.log('HUA i18n debug tools enabled. Use window.__HUA_I18N_DEBUG__ to access.');
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* 디버깅 도구 비활성화
|
|
291
|
-
*/
|
|
292
|
-
export function disableDebugTools(): void {
|
|
293
|
-
const debugTools = (window as Window & { __HUA_I18N_DEBUG__?: DebugTools }).__HUA_I18N_DEBUG__;
|
|
294
|
-
if (debugTools) {
|
|
295
|
-
debugTools.devTools.close();
|
|
296
|
-
delete (window as Window & { __HUA_I18N_DEBUG__?: DebugTools }).__HUA_I18N_DEBUG__;
|
|
297
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 조건부 디버깅 도구 - 개발 환경에서만 로드
|
|
3
|
+
* 번들 크기 최적화를 위해 조건부 로딩
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface DebugTools {
|
|
7
|
+
// 번역 키 시각화
|
|
8
|
+
highlightMissingKeys: (container: HTMLElement) => void;
|
|
9
|
+
showTranslationKeys: (container: HTMLElement) => void;
|
|
10
|
+
|
|
11
|
+
// 성능 모니터링
|
|
12
|
+
performanceMetrics: {
|
|
13
|
+
translationCount: number;
|
|
14
|
+
cacheHits: number;
|
|
15
|
+
cacheMisses: number;
|
|
16
|
+
loadTimes: number[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// 개발자 도구
|
|
20
|
+
devTools: {
|
|
21
|
+
open: () => void;
|
|
22
|
+
close: () => void;
|
|
23
|
+
isOpen: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// 번역 데이터 검증
|
|
27
|
+
validateTranslations: (translations: Record<string, unknown>) => {
|
|
28
|
+
missingKeys: string[];
|
|
29
|
+
duplicateKeys: string[];
|
|
30
|
+
invalidKeys: string[];
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 디버깅 도구 팩토리 - 조건부 로딩
|
|
36
|
+
*/
|
|
37
|
+
export function createDebugTools(): DebugTools | null {
|
|
38
|
+
// 프로덕션에서는 디버깅 도구 비활성화
|
|
39
|
+
if (process.env.NODE_ENV === 'production') {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 개발 환경에서만 로드
|
|
44
|
+
return {
|
|
45
|
+
highlightMissingKeys: (container: HTMLElement) => {
|
|
46
|
+
const elements = container.querySelectorAll('[data-i18n-key]');
|
|
47
|
+
elements.forEach((element) => {
|
|
48
|
+
const key = element.getAttribute('data-i18n-key');
|
|
49
|
+
const text = element.textContent;
|
|
50
|
+
|
|
51
|
+
// 번역 키가 텍스트와 다른 경우 하이라이트
|
|
52
|
+
if (key && text === key) {
|
|
53
|
+
(element as HTMLElement).style.backgroundColor = '#ffeb3b';
|
|
54
|
+
(element as HTMLElement).style.border = '2px solid #f57c00';
|
|
55
|
+
(element as HTMLElement).title = `Missing translation: ${key}`;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
showTranslationKeys: (container: HTMLElement) => {
|
|
61
|
+
const elements = container.querySelectorAll('[data-i18n-key]');
|
|
62
|
+
elements.forEach((element) => {
|
|
63
|
+
const key = element.getAttribute('data-i18n-key');
|
|
64
|
+
if (key) {
|
|
65
|
+
const tooltip = document.createElement('div');
|
|
66
|
+
tooltip.style.cssText = `
|
|
67
|
+
position: absolute;
|
|
68
|
+
background: #333;
|
|
69
|
+
color: white;
|
|
70
|
+
padding: 4px 8px;
|
|
71
|
+
border-radius: 4px;
|
|
72
|
+
font-size: 12px;
|
|
73
|
+
z-index: 10000;
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
opacity: 0;
|
|
76
|
+
transition: opacity 0.2s;
|
|
77
|
+
`;
|
|
78
|
+
tooltip.textContent = `Key: ${key}`;
|
|
79
|
+
|
|
80
|
+
element.addEventListener('mouseenter', () => {
|
|
81
|
+
const rect = element.getBoundingClientRect();
|
|
82
|
+
tooltip.style.left = `${rect.left}px`;
|
|
83
|
+
tooltip.style.top = `${rect.bottom + 5}px`;
|
|
84
|
+
tooltip.style.opacity = '1';
|
|
85
|
+
document.body.appendChild(tooltip);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
element.addEventListener('mouseleave', () => {
|
|
89
|
+
tooltip.style.opacity = '0';
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
if (tooltip.parentNode) {
|
|
92
|
+
tooltip.parentNode.removeChild(tooltip);
|
|
93
|
+
}
|
|
94
|
+
}, 200);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
performanceMetrics: {
|
|
101
|
+
translationCount: 0,
|
|
102
|
+
cacheHits: 0,
|
|
103
|
+
cacheMisses: 0,
|
|
104
|
+
loadTimes: [],
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
devTools: {
|
|
108
|
+
isOpen: false,
|
|
109
|
+
open: () => {
|
|
110
|
+
const devTools = createDevToolsPanel();
|
|
111
|
+
document.body.appendChild(devTools);
|
|
112
|
+
(devTools as HTMLElement & { isOpen?: boolean }).isOpen = true;
|
|
113
|
+
},
|
|
114
|
+
close: () => {
|
|
115
|
+
const existingPanel = document.getElementById('hua-i18n-devtools');
|
|
116
|
+
if (existingPanel) {
|
|
117
|
+
existingPanel.remove();
|
|
118
|
+
}
|
|
119
|
+
// devTools 변수는 이 스코프에서 접근할 수 없으므로 전역에서 관리
|
|
120
|
+
const globalDebugTools = (window as Window & { __HUA_I18N_DEBUG__?: DebugTools }).__HUA_I18N_DEBUG__;
|
|
121
|
+
if (globalDebugTools) {
|
|
122
|
+
globalDebugTools.devTools.isOpen = false;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
validateTranslations: (translations: Record<string, unknown>) => {
|
|
128
|
+
const missingKeys: string[] = [];
|
|
129
|
+
const duplicateKeys: string[] = [];
|
|
130
|
+
const invalidKeys: string[] = [];
|
|
131
|
+
const seenKeys = new Set<string>();
|
|
132
|
+
|
|
133
|
+
const traverse = (obj: unknown, path: string = '') => {
|
|
134
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
138
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
139
|
+
|
|
140
|
+
// 키 유효성 검사
|
|
141
|
+
if (typeof key !== 'string' || key.trim() === '') {
|
|
142
|
+
invalidKeys.push(currentPath);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 중복 키 검사
|
|
146
|
+
if (seenKeys.has(currentPath)) {
|
|
147
|
+
duplicateKeys.push(currentPath);
|
|
148
|
+
} else {
|
|
149
|
+
seenKeys.add(currentPath);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 값 검사
|
|
153
|
+
if (value === null || value === undefined) {
|
|
154
|
+
missingKeys.push(currentPath);
|
|
155
|
+
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
|
156
|
+
traverse(value, currentPath);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
traverse(translations);
|
|
162
|
+
|
|
163
|
+
return { missingKeys, duplicateKeys, invalidKeys };
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 개발자 도구 패널 생성
|
|
170
|
+
*/
|
|
171
|
+
function createDevToolsPanel(): HTMLElement {
|
|
172
|
+
const panel = document.createElement('div');
|
|
173
|
+
panel.id = 'hua-i18n-devtools';
|
|
174
|
+
panel.style.cssText = `
|
|
175
|
+
position: fixed;
|
|
176
|
+
top: 20px;
|
|
177
|
+
right: 20px;
|
|
178
|
+
width: 300px;
|
|
179
|
+
max-height: 500px;
|
|
180
|
+
background: #1e1e1e;
|
|
181
|
+
color: #fff;
|
|
182
|
+
border-radius: 8px;
|
|
183
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
184
|
+
z-index: 10000;
|
|
185
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
186
|
+
font-size: 12px;
|
|
187
|
+
overflow: hidden;
|
|
188
|
+
`;
|
|
189
|
+
|
|
190
|
+
const header = document.createElement('div');
|
|
191
|
+
header.style.cssText = `
|
|
192
|
+
background: #2d2d2d;
|
|
193
|
+
padding: 12px;
|
|
194
|
+
border-bottom: 1px solid #444;
|
|
195
|
+
display: flex;
|
|
196
|
+
justify-content: space-between;
|
|
197
|
+
align-items: center;
|
|
198
|
+
`;
|
|
199
|
+
header.innerHTML = `
|
|
200
|
+
<span style="font-weight: bold;">HUA i18n Debug</span>
|
|
201
|
+
<button id="close-devtools" style="background: none; border: none; color: #fff; cursor: pointer; font-size: 16px;">×</button>
|
|
202
|
+
`;
|
|
203
|
+
|
|
204
|
+
const content = document.createElement('div');
|
|
205
|
+
content.style.cssText = `
|
|
206
|
+
padding: 12px;
|
|
207
|
+
max-height: 400px;
|
|
208
|
+
overflow-y: auto;
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
// 성능 메트릭
|
|
212
|
+
const metricsSection = document.createElement('div');
|
|
213
|
+
metricsSection.innerHTML = `
|
|
214
|
+
<h4 style="margin: 0 0 8px 0; color: #4fc3f7;">Performance</h4>
|
|
215
|
+
<div>Translations: <span id="translation-count">0</span></div>
|
|
216
|
+
<div>Cache Hits: <span id="cache-hits">0</span></div>
|
|
217
|
+
<div>Cache Misses: <span id="cache-misses">0</span></div>
|
|
218
|
+
<div>Hit Rate: <span id="hit-rate">0%</span></div>
|
|
219
|
+
`;
|
|
220
|
+
|
|
221
|
+
// 현재 언어 정보
|
|
222
|
+
const languageSection = document.createElement('div');
|
|
223
|
+
languageSection.style.marginTop = '16px';
|
|
224
|
+
languageSection.innerHTML = `
|
|
225
|
+
<h4 style="margin: 0 0 8px 0; color: #4fc3f7;">Current Language</h4>
|
|
226
|
+
<div>Language: <span id="current-language">ko</span></div>
|
|
227
|
+
<div>Fallback: <span id="fallback-language">en</span></div>
|
|
228
|
+
`;
|
|
229
|
+
|
|
230
|
+
// 액션 버튼들
|
|
231
|
+
const actionsSection = document.createElement('div');
|
|
232
|
+
actionsSection.style.marginTop = '16px';
|
|
233
|
+
actionsSection.innerHTML = `
|
|
234
|
+
<h4 style="margin: 0 0 8px 0; color: #4fc3f7;">Actions</h4>
|
|
235
|
+
<button id="highlight-missing" style="background: #4fc3f7; border: none; color: #000; padding: 4px 8px; border-radius: 4px; cursor: pointer; margin-right: 8px;">Highlight Missing</button>
|
|
236
|
+
<button id="show-keys" style="background: #4fc3f7; border: none; color: #000; padding: 4px 8px; border-radius: 4px; cursor: pointer;">Show Keys</button>
|
|
237
|
+
`;
|
|
238
|
+
|
|
239
|
+
content.appendChild(metricsSection);
|
|
240
|
+
content.appendChild(languageSection);
|
|
241
|
+
content.appendChild(actionsSection);
|
|
242
|
+
|
|
243
|
+
panel.appendChild(header);
|
|
244
|
+
panel.appendChild(content);
|
|
245
|
+
|
|
246
|
+
// 이벤트 리스너
|
|
247
|
+
panel.querySelector('#close-devtools')?.addEventListener('click', () => {
|
|
248
|
+
panel.remove();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
panel.querySelector('#highlight-missing')?.addEventListener('click', () => {
|
|
252
|
+
const debugTools = createDebugTools();
|
|
253
|
+
if (debugTools) {
|
|
254
|
+
debugTools.highlightMissingKeys(document.body);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
panel.querySelector('#show-keys')?.addEventListener('click', () => {
|
|
259
|
+
const debugTools = createDebugTools();
|
|
260
|
+
if (debugTools) {
|
|
261
|
+
debugTools.showTranslationKeys(document.body);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return panel;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* 디버깅 도구 활성화 (전역 함수)
|
|
270
|
+
*/
|
|
271
|
+
export function enableDebugTools(): void {
|
|
272
|
+
if (process.env.NODE_ENV === 'production') {
|
|
273
|
+
console.warn('Debug tools are not available in production');
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const debugTools = createDebugTools();
|
|
278
|
+
if (!debugTools) return;
|
|
279
|
+
|
|
280
|
+
// 전역 객체에 추가
|
|
281
|
+
(window as Window & { __HUA_I18N_DEBUG__?: DebugTools }).__HUA_I18N_DEBUG__ = debugTools;
|
|
282
|
+
|
|
283
|
+
// 개발자 도구 열기
|
|
284
|
+
debugTools.devTools.open();
|
|
285
|
+
|
|
286
|
+
console.log('HUA i18n debug tools enabled. Use window.__HUA_I18N_DEBUG__ to access.');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 디버깅 도구 비활성화
|
|
291
|
+
*/
|
|
292
|
+
export function disableDebugTools(): void {
|
|
293
|
+
const debugTools = (window as Window & { __HUA_I18N_DEBUG__?: DebugTools }).__HUA_I18N_DEBUG__;
|
|
294
|
+
if (debugTools) {
|
|
295
|
+
debugTools.devTools.close();
|
|
296
|
+
delete (window as Window & { __HUA_I18N_DEBUG__?: DebugTools }).__HUA_I18N_DEBUG__;
|
|
297
|
+
}
|
|
298
298
|
}
|