@fuzionx/framework 0.1.66 → 0.1.67
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/index.js
CHANGED
|
@@ -65,4 +65,4 @@ export { default as OpenAPI } from './lib/view/OpenAPI.js';
|
|
|
65
65
|
export { PaginationUtil, StrUtil, NumUtil, DateUtil, ArrUtil, FunctionUtil, ObjectUtil } from './lib/utilities/index.js';
|
|
66
66
|
|
|
67
67
|
// ── Built-in Middleware ──
|
|
68
|
-
export { bodyParser, cors, auth, apiAuth, csrf, session,
|
|
68
|
+
export { bodyParser, cors, auth, apiAuth, csrf, session, loadUser } from './lib/middleware/index.js';
|
package/lib/core/Application.js
CHANGED
|
@@ -103,6 +103,7 @@ export default class Application {
|
|
|
103
103
|
fallback: this.config.get('app.i18n.fallback', 'en'),
|
|
104
104
|
dir: this.config.get('app.i18n.dir', './locales'),
|
|
105
105
|
bridge: this._bridge,
|
|
106
|
+
autoComplete: this.config.get('app.i18n.auto_complete', false),
|
|
106
107
|
});
|
|
107
108
|
this.storage = null;
|
|
108
109
|
this._scheduler = null;
|
|
@@ -4,12 +4,18 @@
|
|
|
4
4
|
* Bridge의 i18NTranslate(locale, key) 직접 호출.
|
|
5
5
|
* Bridge 없으면 JS 파일 기반 폴백.
|
|
6
6
|
*
|
|
7
|
+
* auto_complete 활성 시 fs.watch로 locale 파일 변경을 감지하여
|
|
8
|
+
* 디바운스 후 자동 동기화한다. (매 요청마다 파일 I/O 없음)
|
|
9
|
+
*
|
|
7
10
|
* @see docs/framework/18-i18n.md
|
|
8
11
|
* @see packages/fuzionx/lib/i18n.js (Core 래퍼 참조)
|
|
9
12
|
*/
|
|
10
|
-
import { promises as fs } from 'node:fs';
|
|
13
|
+
import { promises as fs, readFileSync, watch as fsWatch, existsSync } from 'node:fs';
|
|
11
14
|
import path from 'node:path';
|
|
12
15
|
|
|
16
|
+
/** 디바운스 기본 대기 시간 (ms) */
|
|
17
|
+
const RELOAD_DEBOUNCE_MS = 500;
|
|
18
|
+
|
|
13
19
|
export default class I18nHelper {
|
|
14
20
|
/**
|
|
15
21
|
* @param {object} [opts]
|
|
@@ -17,18 +23,24 @@ export default class I18nHelper {
|
|
|
17
23
|
* @param {string} [opts.fallback='en']
|
|
18
24
|
* @param {string} [opts.dir='./locales']
|
|
19
25
|
* @param {object} [opts.bridge] - Bridge N-API 인스턴스
|
|
26
|
+
* @param {boolean} [opts.autoComplete=false] - auto_complete 활성 시 파일 감시
|
|
20
27
|
*/
|
|
21
28
|
constructor(opts = {}) {
|
|
22
29
|
this.defaultLocale = opts.defaultLocale || 'ko';
|
|
23
30
|
this.fallback = opts.fallback || 'en';
|
|
24
31
|
this.dir = opts.dir || './locales';
|
|
25
32
|
this._bridge = opts.bridge || null;
|
|
26
|
-
this.
|
|
33
|
+
this._autoComplete = !!opts.autoComplete;
|
|
34
|
+
this._messages = new Map(); // locale → { flat key: value }
|
|
27
35
|
this._loaded = false;
|
|
36
|
+
this._watcher = null; // fs.watch 인스턴스
|
|
37
|
+
this._reloadTimer = null; // 디바운스 타이머
|
|
38
|
+
this._dirtyLocales = new Set(); // 변경된 locale 큐
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
/**
|
|
31
|
-
* 번역 파일 로드 (JS 폴백용)
|
|
42
|
+
* 번역 파일 로드 (JS 폴백용).
|
|
43
|
+
* auto_complete가 활성이면 locale 디렉토리 감시 시작.
|
|
32
44
|
*/
|
|
33
45
|
async load() {
|
|
34
46
|
if (this._loaded) return;
|
|
@@ -45,6 +57,11 @@ export default class I18nHelper {
|
|
|
45
57
|
}
|
|
46
58
|
} catch {} // locales/ 없으면 무시
|
|
47
59
|
this._loaded = true;
|
|
60
|
+
|
|
61
|
+
// auto_complete 활성 시 파일 감시 시작
|
|
62
|
+
if (this._autoComplete) {
|
|
63
|
+
this._startWatcher();
|
|
64
|
+
}
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
/**
|
|
@@ -86,12 +103,30 @@ export default class I18nHelper {
|
|
|
86
103
|
}
|
|
87
104
|
|
|
88
105
|
/**
|
|
89
|
-
* 전체 locale 데이터 반환 (뷰 주입용)
|
|
106
|
+
* 전체 locale 데이터 반환 (뷰 주입용).
|
|
107
|
+
*
|
|
108
|
+
* Bridge가 있으면 i18NGetTranslations N-API로 최신 데이터를 직접 반환.
|
|
109
|
+
* Bridge 없으면 JS _messages 폴백.
|
|
110
|
+
*
|
|
90
111
|
* @param {string} locale
|
|
91
|
-
* @returns {object}
|
|
112
|
+
* @returns {object} flat dot-notation 번역 데이터
|
|
92
113
|
*/
|
|
93
114
|
all(locale) {
|
|
94
|
-
const
|
|
115
|
+
const target = locale || this.defaultLocale;
|
|
116
|
+
|
|
117
|
+
/** Bridge 우선 — 메모리에서 직접 최신 데이터 반환 */
|
|
118
|
+
if (this._bridge && typeof this._bridge.i18NGetTranslations === 'function') {
|
|
119
|
+
try {
|
|
120
|
+
const json = this._bridge.i18NGetTranslations(target);
|
|
121
|
+
if (json) {
|
|
122
|
+
const data = JSON.parse(json);
|
|
123
|
+
return this._flatten(data); // nested → flat dot-notation
|
|
124
|
+
}
|
|
125
|
+
} catch {} // Bridge 실패 시 JS 폴백
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** JS 폴백 — _messages 캐시 사용 */
|
|
129
|
+
const messages = this._messages.get(target) || this._messages.get(this.defaultLocale);
|
|
95
130
|
if (!messages) return {};
|
|
96
131
|
return { ...messages };
|
|
97
132
|
}
|
|
@@ -121,6 +156,75 @@ export default class I18nHelper {
|
|
|
121
156
|
}
|
|
122
157
|
}
|
|
123
158
|
|
|
159
|
+
// ──────────────────────────────────────────
|
|
160
|
+
// 파일 감시 & 디바운스 동기화
|
|
161
|
+
// ──────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* locale 디렉토리를 fs.watch로 감시 시작.
|
|
165
|
+
* JSON 파일 변경 감지 시 해당 locale을 dirty 큐에 넣고
|
|
166
|
+
* 디바운스 타이머 후 일괄 동기화.
|
|
167
|
+
* @private
|
|
168
|
+
*/
|
|
169
|
+
_startWatcher() {
|
|
170
|
+
if (!existsSync(this.dir)) return;
|
|
171
|
+
try {
|
|
172
|
+
this._watcher = fsWatch(this.dir, (eventType, filename) => {
|
|
173
|
+
// .json 파일 변경만 감시
|
|
174
|
+
if (!filename || !filename.endsWith('.json')) return;
|
|
175
|
+
const locale = path.basename(filename, '.json');
|
|
176
|
+
this._dirtyLocales.add(locale); // 큐에 추가
|
|
177
|
+
|
|
178
|
+
// 디바운스: 기존 타이머 취소 → 새로 시작
|
|
179
|
+
clearTimeout(this._reloadTimer);
|
|
180
|
+
this._reloadTimer = setTimeout(() => {
|
|
181
|
+
this._flushDirty();
|
|
182
|
+
}, RELOAD_DEBOUNCE_MS);
|
|
183
|
+
});
|
|
184
|
+
} catch {} // 감시 실패 시 무시 (기존 동작 유지)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* dirty 큐에 쌓인 locale 파일들을 일괄 동기 재로드.
|
|
189
|
+
* @private
|
|
190
|
+
*/
|
|
191
|
+
_flushDirty() {
|
|
192
|
+
if (this._dirtyLocales.size === 0) return;
|
|
193
|
+
for (const locale of this._dirtyLocales) {
|
|
194
|
+
this._syncReload(locale);
|
|
195
|
+
}
|
|
196
|
+
this._dirtyLocales.clear();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 단일 locale 파일을 동기적으로 재로드.
|
|
201
|
+
* @param {string} locale - 로드할 locale (예: 'ko', 'en')
|
|
202
|
+
* @private
|
|
203
|
+
*/
|
|
204
|
+
_syncReload(locale) {
|
|
205
|
+
try {
|
|
206
|
+
const jsonPath = path.join(this.dir, `${locale}.json`);
|
|
207
|
+
const content = readFileSync(jsonPath, 'utf-8');
|
|
208
|
+
const data = JSON.parse(content);
|
|
209
|
+
this._messages.set(locale, this._flatten(data));
|
|
210
|
+
} catch {} // 파일 없으면 무시
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 감시자 정리 (graceful shutdown 용)
|
|
215
|
+
*/
|
|
216
|
+
destroy() {
|
|
217
|
+
if (this._watcher) {
|
|
218
|
+
this._watcher.close();
|
|
219
|
+
this._watcher = null;
|
|
220
|
+
}
|
|
221
|
+
clearTimeout(this._reloadTimer);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ──────────────────────────────────────────
|
|
225
|
+
// 파서 유틸리티
|
|
226
|
+
// ──────────────────────────────────────────
|
|
227
|
+
|
|
124
228
|
/** {field} → value 치환 */
|
|
125
229
|
_substitute(template, vars) {
|
|
126
230
|
if (!vars || typeof template !== 'string') return template;
|
package/lib/middleware/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 내장 미들웨어 — 배럴 re-export
|
|
2
|
+
* 내장 미들웨어 — 배럴 re-export (공용)
|
|
3
|
+
*
|
|
4
|
+
* 앱 전용 미들웨어(theme, roleGuard 등)는
|
|
5
|
+
* 각 앱의 middleware/ 폴더에서 직접 구현합니다.
|
|
3
6
|
*
|
|
4
7
|
* @see docs/framework/12-middleware.md
|
|
5
8
|
* @see docs/framework/14-authentication.md
|
|
@@ -10,6 +13,4 @@ export { auth } from './auth.js';
|
|
|
10
13
|
export { apiAuth } from './apiAuth.js';
|
|
11
14
|
export { csrf } from './csrf.js';
|
|
12
15
|
export { session } from './session.js';
|
|
13
|
-
export { theme } from './theme.js';
|
|
14
16
|
export { loadUser } from './loadUser.js';
|
|
15
|
-
export { roleGuard } from './roleGuard.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fuzionx/framework",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.67",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Full-stack MVC framework built on @fuzionx/core — Controller, Service, Model, Middleware, DI, EventBus",
|
|
6
6
|
"main": "index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"url": "https://github.com/saytohenry/fuzionx"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@fuzionx/core": "^0.1.
|
|
37
|
+
"@fuzionx/core": "^0.1.67",
|
|
38
38
|
"better-sqlite3": "^12.8.0",
|
|
39
39
|
"knex": "^3.2.5",
|
|
40
40
|
"mongoose": "^9.3.2",
|