@gaonjs/config 0.25.12 → 0.26.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/dist/types.d.ts +7 -3
- package/dist/wire.js +29 -25
- package/package.json +6 -6
package/dist/types.d.ts
CHANGED
|
@@ -68,10 +68,14 @@ export interface StorageConfig {
|
|
|
68
68
|
readonly default: string;
|
|
69
69
|
readonly disks: Record<string, StorageDiskConfig>;
|
|
70
70
|
}
|
|
71
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* i18n — 카탈로그 로드 + 요청별 로케일 협상 옵션(결정 159 · 13차 W1).
|
|
73
|
+
*
|
|
74
|
+
* 결정 459: 카탈로그 **위치는 관례로 고정**됐다(`domain/locales/` · `apps/<앱>/locales/`).
|
|
75
|
+
* 옛 `dir` 은 제거됐다 — 위치가 관례면 `dir` 은 "정답이 둘" 을 만든다. 남아 있으면
|
|
76
|
+
* doctor `i18n-layout` 이 error 로 잡는다.
|
|
77
|
+
*/
|
|
72
78
|
export interface I18nConfig {
|
|
73
|
-
/** locales/ 디렉토리 경로 (cwd 기준 상대). 생략 시 'locales'. */
|
|
74
|
-
readonly dir?: string;
|
|
75
79
|
/** 기본 언어. */
|
|
76
80
|
readonly fallbackLng: string;
|
|
77
81
|
/** 허용 언어(생략 시 locales 폴더 하위 언어들). */
|
package/dist/wire.js
CHANGED
|
@@ -12,8 +12,8 @@ import { createDb, registerConnection, destroyAllConnections, getConnection, has
|
|
|
12
12
|
import { connectNats, configureJobs, configureEvents, outboxServiceTxWrapper, ensureOutboxTable, setLockBackend, redisLockBackend, memoryLockBackend, } from '@gaonjs/async';
|
|
13
13
|
import { configureMailer, smtpTransport } from '@gaonjs/mail';
|
|
14
14
|
import { configureStorage, localDisk, s3Disk, resetStorage } from '@gaonjs/storage';
|
|
15
|
-
import { configureI18n,
|
|
16
|
-
import { createApp, redisSessionStore, setValidationMessageResolver, setLocaleResolver, } from '@gaonjs/web';
|
|
15
|
+
import { buildI18nConfig, configureI18n, loadProjectLocales, projectLanguages, resetI18n, detectLanguage, enterApp, enterLanguage, currentLanguage, languages, t, } from '@gaonjs/i18n';
|
|
16
|
+
import { createApp, redisSessionStore, setAppScopeEnter, setValidationMessageResolver, setLocaleResolver, } from '@gaonjs/web';
|
|
17
17
|
import { resolveApps, toAppSpec } from './apps.js';
|
|
18
18
|
import { existsSync } from 'node:fs';
|
|
19
19
|
import { resolve as resolvePath, join } from 'node:path';
|
|
@@ -224,39 +224,40 @@ export async function configureNonDbBatteries(config, cwd = process.cwd(), opts
|
|
|
224
224
|
}
|
|
225
225
|
configureStorage({ default: config.storage.default, disks });
|
|
226
226
|
}
|
|
227
|
-
// i18n — locales
|
|
227
|
+
// i18n — 두 소유자(domain/locales · apps/*/locales)를 읽어 초기화(결정 459).
|
|
228
228
|
if (config.i18n) {
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
const resources = loadLocales(abs);
|
|
229
|
+
const project = loadProjectLocales(root);
|
|
230
|
+
const langs = projectLanguages(project);
|
|
232
231
|
// 결정 353: i18n 을 켰는데 카탈로그가 비면 fail-loud — 이전엔 무경고로 부팅해
|
|
233
|
-
// 모든 t() 가 원문 키를 화면에 노출했다(배포 이미지 locales
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
`
|
|
238
|
-
` →
|
|
239
|
-
` →
|
|
232
|
+
// 모든 t() 가 원문 키를 화면에 노출했다(배포 이미지 locales 누락 무신호).
|
|
233
|
+
// 결정 459 §6-E: 판정 대상이 2소유자로 늘었다 — **둘 다 비어야** 공허다.
|
|
234
|
+
if (langs.length === 0) {
|
|
235
|
+
throw new Error(`i18n 이 설정됐지만 로케일 카탈로그가 하나도 없습니다: ${root} (결정 459)\n` +
|
|
236
|
+
` → 화면 문구: apps/<앱>/locales/${config.i18n.fallbackLng}/frontend.json (앱마다 필수)\n` +
|
|
237
|
+
` → 도메인 공통 서버 문구(메일·잡·검증): domain/locales/${config.i18n.fallbackLng}/backend.json\n` +
|
|
238
|
+
` → 그 앱 컨트롤러 전용 서버 문구: apps/<앱>/locales/${config.i18n.fallbackLng}/backend.json (선택)\n` +
|
|
239
|
+
` → 루트 locales/ 는 더 이상 읽지 않습니다(결정 459) — gaon doctor --fix 로 이관하세요.\n` +
|
|
240
240
|
` (이 상태로 부팅하면 모든 t() 가 번역 대신 원문 키를 노출합니다 — 결정 353)`);
|
|
241
241
|
}
|
|
242
|
-
// 결정 353: fallbackLng·supportedLngs 가 지목한
|
|
243
|
-
// 이전엔 협상이 그 로케일을 골라 주고 전 키가 fallback 언어로 폴백돼,
|
|
244
|
-
// 조용히 다른 언어로
|
|
245
|
-
const missing = [config.i18n.fallbackLng, ...(config.i18n.supportedLngs ?? [])].filter((lng, i, arr) => arr.indexOf(lng) === i && !
|
|
242
|
+
// 결정 353: fallbackLng·supportedLngs 가 지목한 로케일이 어느 소유자에게도 없으면
|
|
243
|
+
// fail-loud — 이전엔 협상이 그 로케일을 골라 주고 전 키가 fallback 언어로 폴백돼,
|
|
244
|
+
// 로케일 하나가 조용히 다른 언어로 나갔다.
|
|
245
|
+
const missing = [config.i18n.fallbackLng, ...(config.i18n.supportedLngs ?? [])].filter((lng, i, arr) => arr.indexOf(lng) === i && !langs.includes(lng));
|
|
246
246
|
if (missing.length > 0) {
|
|
247
|
-
throw new Error(`i18n 설정이 지목한 로케일의 카탈로그가 없습니다: ${missing.join(', ')}\n` +
|
|
248
|
-
` →
|
|
249
|
-
|
|
250
|
-
.join(' · ')} 을 추가하거나(결정 454 분리 레이아웃),\n` +
|
|
251
|
-
` → 단일 파일 레이아웃이면 ${missing.map((l) => `${l}.json`).join(' · ')} 을 추가하거나,\n` +
|
|
247
|
+
throw new Error(`i18n 설정이 지목한 로케일의 카탈로그가 없습니다: ${missing.join(', ')} (결정 459)\n` +
|
|
248
|
+
` → 각 앱에 ${missing.map((l) => `apps/<앱>/locales/${l}/frontend.json`).join(' · ')} 을 추가하거나,\n` +
|
|
249
|
+
` → 도메인 공통 문구면 ${missing.map((l) => `domain/locales/${l}/backend.json`).join(' · ')} 을 추가하거나,\n` +
|
|
252
250
|
` → gaon.config.ts 의 fallbackLng/supportedLngs 에서 해당 로케일을 제거하세요.\n` +
|
|
253
251
|
` (카탈로그가 없는 로케일은 전 화면이 fallback 언어로 조용히 대체됩니다 — 결정 353)`);
|
|
254
252
|
}
|
|
255
|
-
await configureI18n({
|
|
256
|
-
resources,
|
|
253
|
+
await configureI18n(buildI18nConfig(project, {
|
|
257
254
|
fallbackLng: config.i18n.fallbackLng,
|
|
258
255
|
supportedLngs: config.i18n.supportedLngs,
|
|
259
|
-
});
|
|
256
|
+
}));
|
|
257
|
+
// 결정 459: 앱 스코프 ALS 배선 — 앱 스코프 onRequest 훅이 이 콜백으로 앱 이름을
|
|
258
|
+
// 심어 t() 가 `app:<이름>` → `domain` 으로 해석한다(web→i18n 하드 의존 회피 ·
|
|
259
|
+
// setLocaleResolver·setValidationMessageResolver 와 동형 역전).
|
|
260
|
+
setAppScopeEnter((app) => enterApp(app));
|
|
260
261
|
// 결정 183(옵션 C): 검증 사유를 요청 로케일로 로케일화한다. web 은 안정적 코드만
|
|
261
262
|
// 노출하고, 앱이 locales/ 의 예약 namespace `validation.<code>` 로 번역을 준다.
|
|
262
263
|
// 키가 없으면 defaultValue(web 내장 fallback)로 떨어져 거동이 보존된다. t() 는
|
|
@@ -466,6 +467,9 @@ async function assembleApp(config, root, domain, redis, nats) {
|
|
|
466
467
|
setServiceTxWrapper(undefined);
|
|
467
468
|
// 결정 214: 로케일 리졸버 해제 — 다음 인스턴스(비-i18n 일 수 있음)로 stale 로케일 누출 방지.
|
|
468
469
|
setLocaleResolver(undefined);
|
|
470
|
+
// 결정 459: 앱 스코프 진입 훅도 같은 이유로 해제한다(다음 인스턴스가 비-i18n 이면
|
|
471
|
+
// 죽은 enterApp 이 남아 ALS 에 stale 앱 이름을 심는다).
|
|
472
|
+
setAppScopeEnter(undefined);
|
|
469
473
|
// 결정 202: 락·캐시 백엔드 해제 + redis 종료는 closeDomain 이 소유한다(serve·work 공통 ·
|
|
470
474
|
// wireDomain 이 배선했으므로 해제도 거기서). serve 는 여기서 중복 해제하지 않는다.
|
|
471
475
|
await domain.closeDomain();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaonjs/config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Gaon 루트 설정 로더·자동 배선 (gaon.config.ts)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"ioredis": "^5.4.1",
|
|
33
|
-
"@gaonjs/adapter-mongo": "0.5.4",
|
|
34
|
-
"@gaonjs/i18n": "0.4.1",
|
|
35
33
|
"@gaonjs/core": "0.3.0",
|
|
36
|
-
"@gaonjs/
|
|
34
|
+
"@gaonjs/adapter-mongo": "0.5.4",
|
|
37
35
|
"@gaonjs/async": "0.22.0",
|
|
36
|
+
"@gaonjs/data": "0.26.2",
|
|
37
|
+
"@gaonjs/mail": "0.5.4",
|
|
38
|
+
"@gaonjs/i18n": "0.5.0",
|
|
38
39
|
"@gaonjs/storage": "0.1.6",
|
|
39
|
-
"@gaonjs/web": "0.
|
|
40
|
-
"@gaonjs/mail": "0.5.3"
|
|
40
|
+
"@gaonjs/web": "0.33.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"fastify": "^5.0.0"
|