@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/dist/core/debug-tools.js
CHANGED
|
@@ -31,17 +31,17 @@ export function createDebugTools() {
|
|
|
31
31
|
const key = element.getAttribute('data-i18n-key');
|
|
32
32
|
if (key) {
|
|
33
33
|
const tooltip = document.createElement('div');
|
|
34
|
-
tooltip.style.cssText = `
|
|
35
|
-
position: absolute;
|
|
36
|
-
background: #333;
|
|
37
|
-
color: white;
|
|
38
|
-
padding: 4px 8px;
|
|
39
|
-
border-radius: 4px;
|
|
40
|
-
font-size: 12px;
|
|
41
|
-
z-index: 10000;
|
|
42
|
-
pointer-events: none;
|
|
43
|
-
opacity: 0;
|
|
44
|
-
transition: opacity 0.2s;
|
|
34
|
+
tooltip.style.cssText = `
|
|
35
|
+
position: absolute;
|
|
36
|
+
background: #333;
|
|
37
|
+
color: white;
|
|
38
|
+
padding: 4px 8px;
|
|
39
|
+
border-radius: 4px;
|
|
40
|
+
font-size: 12px;
|
|
41
|
+
z-index: 10000;
|
|
42
|
+
pointer-events: none;
|
|
43
|
+
opacity: 0;
|
|
44
|
+
transition: opacity 0.2s;
|
|
45
45
|
`;
|
|
46
46
|
tooltip.textContent = `Key: ${key}`;
|
|
47
47
|
element.addEventListener('mouseenter', () => {
|
|
@@ -129,64 +129,64 @@ export function createDebugTools() {
|
|
|
129
129
|
function createDevToolsPanel() {
|
|
130
130
|
const panel = document.createElement('div');
|
|
131
131
|
panel.id = 'hua-i18n-devtools';
|
|
132
|
-
panel.style.cssText = `
|
|
133
|
-
position: fixed;
|
|
134
|
-
top: 20px;
|
|
135
|
-
right: 20px;
|
|
136
|
-
width: 300px;
|
|
137
|
-
max-height: 500px;
|
|
138
|
-
background: #1e1e1e;
|
|
139
|
-
color: #fff;
|
|
140
|
-
border-radius: 8px;
|
|
141
|
-
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
142
|
-
z-index: 10000;
|
|
143
|
-
font-family: 'Monaco', 'Menlo', monospace;
|
|
144
|
-
font-size: 12px;
|
|
145
|
-
overflow: hidden;
|
|
132
|
+
panel.style.cssText = `
|
|
133
|
+
position: fixed;
|
|
134
|
+
top: 20px;
|
|
135
|
+
right: 20px;
|
|
136
|
+
width: 300px;
|
|
137
|
+
max-height: 500px;
|
|
138
|
+
background: #1e1e1e;
|
|
139
|
+
color: #fff;
|
|
140
|
+
border-radius: 8px;
|
|
141
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
142
|
+
z-index: 10000;
|
|
143
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
144
|
+
font-size: 12px;
|
|
145
|
+
overflow: hidden;
|
|
146
146
|
`;
|
|
147
147
|
const header = document.createElement('div');
|
|
148
|
-
header.style.cssText = `
|
|
149
|
-
background: #2d2d2d;
|
|
150
|
-
padding: 12px;
|
|
151
|
-
border-bottom: 1px solid #444;
|
|
152
|
-
display: flex;
|
|
153
|
-
justify-content: space-between;
|
|
154
|
-
align-items: center;
|
|
148
|
+
header.style.cssText = `
|
|
149
|
+
background: #2d2d2d;
|
|
150
|
+
padding: 12px;
|
|
151
|
+
border-bottom: 1px solid #444;
|
|
152
|
+
display: flex;
|
|
153
|
+
justify-content: space-between;
|
|
154
|
+
align-items: center;
|
|
155
155
|
`;
|
|
156
|
-
header.innerHTML = `
|
|
157
|
-
<span style="font-weight: bold;">HUA i18n Debug</span>
|
|
158
|
-
<button id="close-devtools" style="background: none; border: none; color: #fff; cursor: pointer; font-size: 16px;">×</button>
|
|
156
|
+
header.innerHTML = `
|
|
157
|
+
<span style="font-weight: bold;">HUA i18n Debug</span>
|
|
158
|
+
<button id="close-devtools" style="background: none; border: none; color: #fff; cursor: pointer; font-size: 16px;">×</button>
|
|
159
159
|
`;
|
|
160
160
|
const content = document.createElement('div');
|
|
161
|
-
content.style.cssText = `
|
|
162
|
-
padding: 12px;
|
|
163
|
-
max-height: 400px;
|
|
164
|
-
overflow-y: auto;
|
|
161
|
+
content.style.cssText = `
|
|
162
|
+
padding: 12px;
|
|
163
|
+
max-height: 400px;
|
|
164
|
+
overflow-y: auto;
|
|
165
165
|
`;
|
|
166
166
|
// 성능 메트릭
|
|
167
167
|
const metricsSection = document.createElement('div');
|
|
168
|
-
metricsSection.innerHTML = `
|
|
169
|
-
<h4 style="margin: 0 0 8px 0; color: #4fc3f7;">Performance</h4>
|
|
170
|
-
<div>Translations: <span id="translation-count">0</span></div>
|
|
171
|
-
<div>Cache Hits: <span id="cache-hits">0</span></div>
|
|
172
|
-
<div>Cache Misses: <span id="cache-misses">0</span></div>
|
|
173
|
-
<div>Hit Rate: <span id="hit-rate">0%</span></div>
|
|
168
|
+
metricsSection.innerHTML = `
|
|
169
|
+
<h4 style="margin: 0 0 8px 0; color: #4fc3f7;">Performance</h4>
|
|
170
|
+
<div>Translations: <span id="translation-count">0</span></div>
|
|
171
|
+
<div>Cache Hits: <span id="cache-hits">0</span></div>
|
|
172
|
+
<div>Cache Misses: <span id="cache-misses">0</span></div>
|
|
173
|
+
<div>Hit Rate: <span id="hit-rate">0%</span></div>
|
|
174
174
|
`;
|
|
175
175
|
// 현재 언어 정보
|
|
176
176
|
const languageSection = document.createElement('div');
|
|
177
177
|
languageSection.style.marginTop = '16px';
|
|
178
|
-
languageSection.innerHTML = `
|
|
179
|
-
<h4 style="margin: 0 0 8px 0; color: #4fc3f7;">Current Language</h4>
|
|
180
|
-
<div>Language: <span id="current-language">ko</span></div>
|
|
181
|
-
<div>Fallback: <span id="fallback-language">en</span></div>
|
|
178
|
+
languageSection.innerHTML = `
|
|
179
|
+
<h4 style="margin: 0 0 8px 0; color: #4fc3f7;">Current Language</h4>
|
|
180
|
+
<div>Language: <span id="current-language">ko</span></div>
|
|
181
|
+
<div>Fallback: <span id="fallback-language">en</span></div>
|
|
182
182
|
`;
|
|
183
183
|
// 액션 버튼들
|
|
184
184
|
const actionsSection = document.createElement('div');
|
|
185
185
|
actionsSection.style.marginTop = '16px';
|
|
186
|
-
actionsSection.innerHTML = `
|
|
187
|
-
<h4 style="margin: 0 0 8px 0; color: #4fc3f7;">Actions</h4>
|
|
188
|
-
<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>
|
|
189
|
-
<button id="show-keys" style="background: #4fc3f7; border: none; color: #000; padding: 4px 8px; border-radius: 4px; cursor: pointer;">Show Keys</button>
|
|
186
|
+
actionsSection.innerHTML = `
|
|
187
|
+
<h4 style="margin: 0 0 8px 0; color: #4fc3f7;">Actions</h4>
|
|
188
|
+
<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>
|
|
189
|
+
<button id="show-keys" style="background: #4fc3f7; border: none; color: #000; padding: 4px 8px; border-radius: 4px; cursor: pointer;">Show Keys</button>
|
|
190
190
|
`;
|
|
191
191
|
content.appendChild(metricsSection);
|
|
192
192
|
content.appendChild(languageSection);
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,223 +1,223 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
interface MissingKeyOverlayProps {
|
|
4
|
-
enabled?: boolean;
|
|
5
|
-
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
|
|
6
|
-
style?: React.CSSProperties;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface MissingKey {
|
|
10
|
-
key: string;
|
|
11
|
-
namespace?: string;
|
|
12
|
-
language: string;
|
|
13
|
-
timestamp: number;
|
|
14
|
-
component?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* 개발 모드에서 누락된 번역 키를 화면에 표시하는 오버레이
|
|
19
|
-
* Lingui 스타일의 디버그 모드
|
|
20
|
-
*/
|
|
21
|
-
export const MissingKeyOverlay: React.FC<MissingKeyOverlayProps> = ({
|
|
22
|
-
enabled = process.env.NODE_ENV === 'development',
|
|
23
|
-
position = 'top-right',
|
|
24
|
-
style
|
|
25
|
-
}) => {
|
|
26
|
-
const [missingKeys, setMissingKeys] = useState<MissingKey[]>([]);
|
|
27
|
-
const [isVisible, setIsVisible] = useState(false);
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (!enabled) return;
|
|
31
|
-
|
|
32
|
-
// 누락된 키 이벤트 리스너
|
|
33
|
-
const handleMissingKey = (event: CustomEvent<MissingKey>) => {
|
|
34
|
-
setMissingKeys(prev => [...prev, event.detail]);
|
|
35
|
-
setIsVisible(true);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// 전역 이벤트 리스너 등록
|
|
39
|
-
window.addEventListener('i18n:missing-key', handleMissingKey as EventListener);
|
|
40
|
-
|
|
41
|
-
return () => {
|
|
42
|
-
window.removeEventListener('i18n:missing-key', handleMissingKey as EventListener);
|
|
43
|
-
};
|
|
44
|
-
}, [enabled]);
|
|
45
|
-
|
|
46
|
-
if (!enabled || !isVisible) return null;
|
|
47
|
-
|
|
48
|
-
const positionStyles = {
|
|
49
|
-
'top-right': { top: 20, right: 20 },
|
|
50
|
-
'top-left': { top: 20, left: 20 },
|
|
51
|
-
'bottom-right': { bottom: 20, right: 20 },
|
|
52
|
-
'bottom-left': { bottom: 20, left: 20 }
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const overlayStyle: React.CSSProperties = {
|
|
56
|
-
position: 'fixed',
|
|
57
|
-
zIndex: 9999,
|
|
58
|
-
backgroundColor: 'rgba(255, 0, 0, 0.9)',
|
|
59
|
-
color: 'white',
|
|
60
|
-
padding: '12px 16px',
|
|
61
|
-
borderRadius: '8px',
|
|
62
|
-
fontFamily: 'monospace',
|
|
63
|
-
fontSize: '12px',
|
|
64
|
-
maxWidth: '400px',
|
|
65
|
-
maxHeight: '300px',
|
|
66
|
-
overflow: 'auto',
|
|
67
|
-
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
|
68
|
-
border: '2px solid #ff4444',
|
|
69
|
-
...positionStyles[position],
|
|
70
|
-
...style
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const keyStyle: React.CSSProperties = {
|
|
74
|
-
marginBottom: '8px',
|
|
75
|
-
padding: '4px 8px',
|
|
76
|
-
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
77
|
-
borderRadius: '4px',
|
|
78
|
-
borderLeft: '3px solid #ff8888'
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const headerStyle: React.CSSProperties = {
|
|
82
|
-
display: 'flex',
|
|
83
|
-
justifyContent: 'space-between',
|
|
84
|
-
alignItems: 'center',
|
|
85
|
-
marginBottom: '12px',
|
|
86
|
-
paddingBottom: '8px',
|
|
87
|
-
borderBottom: '1px solid rgba(255, 255, 255, 0.3)'
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const closeButtonStyle: React.CSSProperties = {
|
|
91
|
-
background: 'none',
|
|
92
|
-
border: 'none',
|
|
93
|
-
color: 'white',
|
|
94
|
-
cursor: 'pointer',
|
|
95
|
-
fontSize: '16px',
|
|
96
|
-
padding: '0',
|
|
97
|
-
width: '20px',
|
|
98
|
-
height: '20px',
|
|
99
|
-
display: 'flex',
|
|
100
|
-
alignItems: 'center',
|
|
101
|
-
justifyContent: 'center'
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const clearButtonStyle: React.CSSProperties = {
|
|
105
|
-
background: 'rgba(255, 255, 255, 0.2)',
|
|
106
|
-
border: 'none',
|
|
107
|
-
color: 'white',
|
|
108
|
-
cursor: 'pointer',
|
|
109
|
-
padding: '4px 8px',
|
|
110
|
-
borderRadius: '4px',
|
|
111
|
-
fontSize: '10px',
|
|
112
|
-
marginLeft: '8px'
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const handleClose = () => {
|
|
116
|
-
setIsVisible(false);
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const handleClear = () => {
|
|
120
|
-
setMissingKeys([]);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const formatTime = (timestamp: number) => {
|
|
124
|
-
return new Date(timestamp).toLocaleTimeString();
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<div style={overlayStyle}>
|
|
129
|
-
<div style={headerStyle}>
|
|
130
|
-
<div>
|
|
131
|
-
<strong>🚨 Missing Translation Keys</strong>
|
|
132
|
-
<span style={{ fontSize: '10px', marginLeft: '8px' }}>
|
|
133
|
-
({missingKeys.length})
|
|
134
|
-
</span>
|
|
135
|
-
</div>
|
|
136
|
-
<div>
|
|
137
|
-
<button style={clearButtonStyle} onClick={handleClear}>
|
|
138
|
-
Clear
|
|
139
|
-
</button>
|
|
140
|
-
<button style={closeButtonStyle} onClick={handleClose}>
|
|
141
|
-
×
|
|
142
|
-
</button>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
|
|
146
|
-
<div>
|
|
147
|
-
{missingKeys.slice(-10).reverse().map((key, index) => (
|
|
148
|
-
<div key={`${key.key}-${key.timestamp}`} style={keyStyle}>
|
|
149
|
-
<div style={{ fontWeight: 'bold', color: '#ffcccc' }}>
|
|
150
|
-
{key.key}
|
|
151
|
-
</div>
|
|
152
|
-
<div style={{ fontSize: '10px', marginTop: '2px' }}>
|
|
153
|
-
<span>Lang: {key.language}</span>
|
|
154
|
-
{key.namespace && <span style={{ marginLeft: '8px' }}>NS: {key.namespace}</span>}
|
|
155
|
-
<span style={{ marginLeft: '8px' }}>Time: {formatTime(key.timestamp)}</span>
|
|
156
|
-
</div>
|
|
157
|
-
{key.component && (
|
|
158
|
-
<div style={{ fontSize: '10px', color: '#ffaaaa', marginTop: '2px' }}>
|
|
159
|
-
Component: {key.component}
|
|
160
|
-
</div>
|
|
161
|
-
)}
|
|
162
|
-
</div>
|
|
163
|
-
))}
|
|
164
|
-
|
|
165
|
-
{missingKeys.length > 10 && (
|
|
166
|
-
<div style={{ fontSize: '10px', textAlign: 'center', marginTop: '8px' }}>
|
|
167
|
-
... and {missingKeys.length - 10} more
|
|
168
|
-
</div>
|
|
169
|
-
)}
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
);
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* 누락된 키를 오버레이에 표시하는 유틸리티 함수
|
|
177
|
-
*/
|
|
178
|
-
export const reportMissingKey = (key: string, options: {
|
|
179
|
-
namespace?: string;
|
|
180
|
-
language: string;
|
|
181
|
-
component?: string;
|
|
182
|
-
}) => {
|
|
183
|
-
if (process.env.NODE_ENV === 'development') {
|
|
184
|
-
const missingKey: MissingKey = {
|
|
185
|
-
key,
|
|
186
|
-
namespace: options.namespace,
|
|
187
|
-
language: options.language,
|
|
188
|
-
timestamp: Date.now(),
|
|
189
|
-
component: options.component
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
// 커스텀 이벤트 발생
|
|
193
|
-
window.dispatchEvent(new CustomEvent('i18n:missing-key', {
|
|
194
|
-
detail: missingKey
|
|
195
|
-
}));
|
|
196
|
-
|
|
197
|
-
// 콘솔에도 로그
|
|
198
|
-
console.warn(`Missing translation key: ${key}`, {
|
|
199
|
-
namespace: options.namespace,
|
|
200
|
-
language: options.language,
|
|
201
|
-
component: options.component
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* 누락된 키 오버레이를 자동으로 활성화하는 훅
|
|
208
|
-
*/
|
|
209
|
-
export const useMissingKeyOverlay = (enabled = true) => {
|
|
210
|
-
const [showOverlay, setShowOverlay] = useState(enabled);
|
|
211
|
-
|
|
212
|
-
useEffect(() => {
|
|
213
|
-
if (enabled && process.env.NODE_ENV === 'development') {
|
|
214
|
-
setShowOverlay(true);
|
|
215
|
-
}
|
|
216
|
-
}, [enabled]);
|
|
217
|
-
|
|
218
|
-
return {
|
|
219
|
-
showOverlay,
|
|
220
|
-
setShowOverlay,
|
|
221
|
-
reportMissingKey
|
|
222
|
-
};
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface MissingKeyOverlayProps {
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
|
|
6
|
+
style?: React.CSSProperties;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface MissingKey {
|
|
10
|
+
key: string;
|
|
11
|
+
namespace?: string;
|
|
12
|
+
language: string;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
component?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 개발 모드에서 누락된 번역 키를 화면에 표시하는 오버레이
|
|
19
|
+
* Lingui 스타일의 디버그 모드
|
|
20
|
+
*/
|
|
21
|
+
export const MissingKeyOverlay: React.FC<MissingKeyOverlayProps> = ({
|
|
22
|
+
enabled = process.env.NODE_ENV === 'development',
|
|
23
|
+
position = 'top-right',
|
|
24
|
+
style
|
|
25
|
+
}) => {
|
|
26
|
+
const [missingKeys, setMissingKeys] = useState<MissingKey[]>([]);
|
|
27
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!enabled) return;
|
|
31
|
+
|
|
32
|
+
// 누락된 키 이벤트 리스너
|
|
33
|
+
const handleMissingKey = (event: CustomEvent<MissingKey>) => {
|
|
34
|
+
setMissingKeys(prev => [...prev, event.detail]);
|
|
35
|
+
setIsVisible(true);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// 전역 이벤트 리스너 등록
|
|
39
|
+
window.addEventListener('i18n:missing-key', handleMissingKey as EventListener);
|
|
40
|
+
|
|
41
|
+
return () => {
|
|
42
|
+
window.removeEventListener('i18n:missing-key', handleMissingKey as EventListener);
|
|
43
|
+
};
|
|
44
|
+
}, [enabled]);
|
|
45
|
+
|
|
46
|
+
if (!enabled || !isVisible) return null;
|
|
47
|
+
|
|
48
|
+
const positionStyles = {
|
|
49
|
+
'top-right': { top: 20, right: 20 },
|
|
50
|
+
'top-left': { top: 20, left: 20 },
|
|
51
|
+
'bottom-right': { bottom: 20, right: 20 },
|
|
52
|
+
'bottom-left': { bottom: 20, left: 20 }
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const overlayStyle: React.CSSProperties = {
|
|
56
|
+
position: 'fixed',
|
|
57
|
+
zIndex: 9999,
|
|
58
|
+
backgroundColor: 'rgba(255, 0, 0, 0.9)',
|
|
59
|
+
color: 'white',
|
|
60
|
+
padding: '12px 16px',
|
|
61
|
+
borderRadius: '8px',
|
|
62
|
+
fontFamily: 'monospace',
|
|
63
|
+
fontSize: '12px',
|
|
64
|
+
maxWidth: '400px',
|
|
65
|
+
maxHeight: '300px',
|
|
66
|
+
overflow: 'auto',
|
|
67
|
+
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
|
68
|
+
border: '2px solid #ff4444',
|
|
69
|
+
...positionStyles[position],
|
|
70
|
+
...style
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const keyStyle: React.CSSProperties = {
|
|
74
|
+
marginBottom: '8px',
|
|
75
|
+
padding: '4px 8px',
|
|
76
|
+
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
|
77
|
+
borderRadius: '4px',
|
|
78
|
+
borderLeft: '3px solid #ff8888'
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const headerStyle: React.CSSProperties = {
|
|
82
|
+
display: 'flex',
|
|
83
|
+
justifyContent: 'space-between',
|
|
84
|
+
alignItems: 'center',
|
|
85
|
+
marginBottom: '12px',
|
|
86
|
+
paddingBottom: '8px',
|
|
87
|
+
borderBottom: '1px solid rgba(255, 255, 255, 0.3)'
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const closeButtonStyle: React.CSSProperties = {
|
|
91
|
+
background: 'none',
|
|
92
|
+
border: 'none',
|
|
93
|
+
color: 'white',
|
|
94
|
+
cursor: 'pointer',
|
|
95
|
+
fontSize: '16px',
|
|
96
|
+
padding: '0',
|
|
97
|
+
width: '20px',
|
|
98
|
+
height: '20px',
|
|
99
|
+
display: 'flex',
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
justifyContent: 'center'
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const clearButtonStyle: React.CSSProperties = {
|
|
105
|
+
background: 'rgba(255, 255, 255, 0.2)',
|
|
106
|
+
border: 'none',
|
|
107
|
+
color: 'white',
|
|
108
|
+
cursor: 'pointer',
|
|
109
|
+
padding: '4px 8px',
|
|
110
|
+
borderRadius: '4px',
|
|
111
|
+
fontSize: '10px',
|
|
112
|
+
marginLeft: '8px'
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handleClose = () => {
|
|
116
|
+
setIsVisible(false);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const handleClear = () => {
|
|
120
|
+
setMissingKeys([]);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const formatTime = (timestamp: number) => {
|
|
124
|
+
return new Date(timestamp).toLocaleTimeString();
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div style={overlayStyle}>
|
|
129
|
+
<div style={headerStyle}>
|
|
130
|
+
<div>
|
|
131
|
+
<strong>🚨 Missing Translation Keys</strong>
|
|
132
|
+
<span style={{ fontSize: '10px', marginLeft: '8px' }}>
|
|
133
|
+
({missingKeys.length})
|
|
134
|
+
</span>
|
|
135
|
+
</div>
|
|
136
|
+
<div>
|
|
137
|
+
<button style={clearButtonStyle} onClick={handleClear}>
|
|
138
|
+
Clear
|
|
139
|
+
</button>
|
|
140
|
+
<button style={closeButtonStyle} onClick={handleClose}>
|
|
141
|
+
×
|
|
142
|
+
</button>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div>
|
|
147
|
+
{missingKeys.slice(-10).reverse().map((key, index) => (
|
|
148
|
+
<div key={`${key.key}-${key.timestamp}`} style={keyStyle}>
|
|
149
|
+
<div style={{ fontWeight: 'bold', color: '#ffcccc' }}>
|
|
150
|
+
{key.key}
|
|
151
|
+
</div>
|
|
152
|
+
<div style={{ fontSize: '10px', marginTop: '2px' }}>
|
|
153
|
+
<span>Lang: {key.language}</span>
|
|
154
|
+
{key.namespace && <span style={{ marginLeft: '8px' }}>NS: {key.namespace}</span>}
|
|
155
|
+
<span style={{ marginLeft: '8px' }}>Time: {formatTime(key.timestamp)}</span>
|
|
156
|
+
</div>
|
|
157
|
+
{key.component && (
|
|
158
|
+
<div style={{ fontSize: '10px', color: '#ffaaaa', marginTop: '2px' }}>
|
|
159
|
+
Component: {key.component}
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
))}
|
|
164
|
+
|
|
165
|
+
{missingKeys.length > 10 && (
|
|
166
|
+
<div style={{ fontSize: '10px', textAlign: 'center', marginTop: '8px' }}>
|
|
167
|
+
... and {missingKeys.length - 10} more
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 누락된 키를 오버레이에 표시하는 유틸리티 함수
|
|
177
|
+
*/
|
|
178
|
+
export const reportMissingKey = (key: string, options: {
|
|
179
|
+
namespace?: string;
|
|
180
|
+
language: string;
|
|
181
|
+
component?: string;
|
|
182
|
+
}) => {
|
|
183
|
+
if (process.env.NODE_ENV === 'development') {
|
|
184
|
+
const missingKey: MissingKey = {
|
|
185
|
+
key,
|
|
186
|
+
namespace: options.namespace,
|
|
187
|
+
language: options.language,
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
component: options.component
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// 커스텀 이벤트 발생
|
|
193
|
+
window.dispatchEvent(new CustomEvent('i18n:missing-key', {
|
|
194
|
+
detail: missingKey
|
|
195
|
+
}));
|
|
196
|
+
|
|
197
|
+
// 콘솔에도 로그
|
|
198
|
+
console.warn(`Missing translation key: ${key}`, {
|
|
199
|
+
namespace: options.namespace,
|
|
200
|
+
language: options.language,
|
|
201
|
+
component: options.component
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 누락된 키 오버레이를 자동으로 활성화하는 훅
|
|
208
|
+
*/
|
|
209
|
+
export const useMissingKeyOverlay = (enabled = true) => {
|
|
210
|
+
const [showOverlay, setShowOverlay] = useState(enabled);
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (enabled && process.env.NODE_ENV === 'development') {
|
|
214
|
+
setShowOverlay(true);
|
|
215
|
+
}
|
|
216
|
+
}, [enabled]);
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
showOverlay,
|
|
220
|
+
setShowOverlay,
|
|
221
|
+
reportMissingKey
|
|
222
|
+
};
|
|
223
223
|
};
|