@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.
@@ -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);
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@hua-labs/i18n-core",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "HUA Labs - Core i18n functionality with SSR/CSR support and state management integration",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -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
  };