@mandujs/core 0.53.2 → 0.54.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/README.md +654 -654
- package/package.json +2 -2
- package/src/a11y/__tests__/run-audit.test.ts +333 -333
- package/src/a11y/fix-hints.ts +76 -76
- package/src/a11y/index.ts +18 -18
- package/src/a11y/run-audit.ts +394 -394
- package/src/a11y/types.ts +125 -125
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +122 -122
- package/src/auth/__tests__/tokens.test.ts +274 -274
- package/src/auth/__tests__/verification.test.ts +274 -274
- package/src/auth/index.ts +76 -76
- package/src/auth/login.ts +225 -225
- package/src/auth/password.ts +120 -120
- package/src/auth/reset.ts +243 -243
- package/src/auth/tokens.ts +612 -612
- package/src/auth/verification.ts +253 -253
- package/src/brain/__tests__/redactor.test.ts +94 -94
- package/src/brain/adapters/__tests__/_helpers.ts +83 -83
- package/src/brain/adapters/__tests__/anthropic-oauth.test.ts +196 -196
- package/src/brain/adapters/__tests__/chatgpt-auth.test.ts +193 -193
- package/src/brain/adapters/__tests__/openai-oauth.test.ts +209 -209
- package/src/brain/adapters/__tests__/resolver.test.ts +143 -143
- package/src/brain/adapters/anthropic-oauth.ts +1 -1
- package/src/brain/adapters/chatgpt-auth.ts +300 -300
- package/src/brain/adapters/index.ts +319 -319
- package/src/brain/adapters/oauth-flow.ts +439 -439
- package/src/brain/consent.ts +240 -240
- package/src/brain/credentials.ts +396 -396
- package/src/bundler/__tests__/build-runner.ts +113 -113
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -619
- package/src/bundler/__tests__/extended-watch.test.ts +711 -711
- package/src/bundler/__tests__/fast-refresh.test.ts +10 -10
- package/src/bundler/__tests__/hdr.test.ts +353 -353
- package/src/bundler/__tests__/hmr-client.test.ts +532 -532
- package/src/bundler/__tests__/manifest-schema.test.ts +266 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +519 -519
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -573
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -286
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -455
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +179 -179
- package/src/bundler/build.ts +55 -55
- package/src/bundler/dev.ts +42 -42
- package/src/bundler/manifest-schema.ts +301 -301
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +263 -263
- package/src/bundler/plugins/__tests__/react-compiler-config.test.ts +83 -83
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -110
- package/src/bundler/plugins/block-generated-imports.ts +155 -155
- package/src/bundler/plugins/index.ts +83 -83
- package/src/bundler/plugins/react-compiler-config.ts +108 -108
- package/src/bundler/plugins/react-compiler-lint.ts +253 -253
- package/src/bundler/plugins/react-compiler.ts +162 -162
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +201 -201
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +10 -10
- package/src/bundler/vendor-cache-types.ts +130 -130
- package/src/bundler/vendor-cache.ts +526 -526
- package/src/change/snapshot.ts +18 -18
- package/src/client/Form.tsx +105 -105
- package/src/client/Link.tsx +9 -9
- package/src/client/__tests__/use-sse.test.ts +153 -153
- package/src/client/globals.ts +1 -1
- package/src/client/hooks.ts +362 -362
- package/src/client/hydrate.ts +340 -340
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/router.ts +11 -11
- package/src/client/runtime.ts +47 -47
- package/src/client/serialize.ts +404 -404
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-head.ts +197 -197
- package/src/client/use-sse.ts +378 -378
- package/src/client/window-state.ts +101 -101
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +3 -3
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +8 -8
- package/src/content/content-layer.ts +7 -7
- package/src/content/data-store.ts +245 -245
- package/src/content/frontmatter.ts +189 -189
- package/src/content/loader-context.ts +171 -171
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +172 -172
- package/src/content/loaders/glob.ts +253 -253
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/prebuild.test.ts +571 -571
- package/src/content/prebuild.ts +636 -636
- package/src/content/schema.ts +20 -20
- package/src/content/sidebar.ts +630 -630
- package/src/content/slug.ts +110 -110
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/define.ts +11 -11
- package/src/contract/index.ts +1 -1
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +410 -410
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/route-helpers.ts +1 -1
- package/src/contract/rpc.ts +443 -443
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +482 -482
- package/src/db/index.ts +138 -138
- package/src/db/migrations/history-table.ts +345 -345
- package/src/db/migrations/index.ts +3 -3
- package/src/db/migrations/lock.ts +324 -324
- package/src/db/migrations/runner.ts +650 -650
- package/src/deploy/cache.ts +140 -140
- package/src/deploy/compile/vercel.ts +344 -344
- package/src/deploy/index.ts +87 -87
- package/src/deploy/inference/brain.ts +268 -268
- package/src/deploy/inference/context.ts +182 -182
- package/src/deploy/inference/filling-extract.ts +245 -245
- package/src/deploy/inference/heuristic.ts +182 -182
- package/src/deploy/intent.ts +173 -173
- package/src/deploy/plan.ts +178 -178
- package/src/design/__tests__/agents-link.test.ts +109 -109
- package/src/design/__tests__/extract-patch-diff.test.ts +265 -265
- package/src/design/__tests__/lint.test.ts +110 -110
- package/src/design/__tests__/parser.test.ts +195 -195
- package/src/design/__tests__/tailwind-theme.test.ts +229 -229
- package/src/design/agents-link.ts +165 -165
- package/src/design/diff.ts +138 -138
- package/src/design/extract.ts +285 -285
- package/src/design/index.ts +102 -102
- package/src/design/lint.ts +209 -209
- package/src/design/parser.ts +555 -555
- package/src/design/patch.ts +241 -241
- package/src/design/scaffold.ts +147 -147
- package/src/design/tailwind-theme.ts +441 -441
- package/src/design/types.ts +210 -210
- package/src/desktop/__tests__/webview-fallback.test.ts +254 -254
- package/src/desktop/__tests__/window.test.ts +248 -248
- package/src/desktop/__tests__/worker.test.ts +266 -266
- package/src/desktop/index.ts +43 -43
- package/src/desktop/types.ts +158 -158
- package/src/desktop/worker.ts +180 -180
- package/src/dev-error-overlay/__tests__/overlay-injector.test.ts +241 -241
- package/src/dev-error-overlay/index.ts +30 -30
- package/src/dev-error-overlay/overlay-injector.ts +243 -243
- package/src/dev-error-overlay/overlay-styles.ts +52 -52
- package/src/dev-error-overlay/types.ts +66 -66
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +25 -25
- package/src/devtools/client/catchers/error-catcher.ts +344 -344
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/mandu-character.tsx +331 -331
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +30 -30
- package/src/devtools/client/components/panel/islands-panel.tsx +320 -320
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +500 -500
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -46
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +265 -265
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +450 -450
- package/src/devtools/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/devtools/worker/worker-manager.ts +410 -410
- package/src/diagnose/__tests__/checks.test.ts +451 -380
- package/src/diagnose/checks.ts +832 -720
- package/src/diagnose/index.ts +17 -16
- package/src/diagnose/run.ts +93 -91
- package/src/diagnose/types.ts +53 -53
- package/src/email/__tests__/email.test.ts +355 -355
- package/src/email/index.ts +282 -282
- package/src/email/smtp.ts +64 -64
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +60 -60
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/__tests__/session-sqlite.test.ts +454 -454
- package/src/filling/auth.ts +308 -308
- package/src/filling/cookie-codec.ts +299 -299
- package/src/filling/deps.ts +265 -265
- package/src/filling/session-sqlite.ts +617 -617
- package/src/filling/sse.ts +5 -5
- package/src/filling/ws.ts +78 -78
- package/src/generator/index.ts +3 -3
- package/src/guard/__tests__/design-inline-class.test.ts +219 -219
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -347
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/define-rule.ts +243 -243
- package/src/guard/design-inline-class.ts +393 -393
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/reporter.ts +442 -442
- package/src/guard/rule-presets.ts +379 -379
- package/src/guard/semantic-slots.ts +1 -1
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/tsgolint-bridge.ts +512 -512
- package/src/guard/types.ts +348 -348
- package/src/guard/watcher.ts +405 -405
- package/src/i18n/define.ts +126 -126
- package/src/i18n/index.ts +52 -52
- package/src/i18n/message-registry.ts +173 -173
- package/src/i18n/types.ts +112 -112
- package/src/id/index.ts +105 -105
- package/src/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -544
- package/src/kitchen/api/errors-grouping.ts +126 -0
- package/src/kitchen/kitchen-handler.ts +192 -63
- package/src/kitchen/kitchen-ui.ts +842 -464
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/middleware/bridge.ts +147 -147
- package/src/middleware/compose.ts +134 -134
- package/src/middleware/compress.ts +62 -62
- package/src/middleware/cors.ts +47 -47
- package/src/middleware/csrf.ts +328 -328
- package/src/middleware/define.ts +132 -132
- package/src/middleware/jwt.ts +134 -134
- package/src/middleware/logger.ts +58 -58
- package/src/middleware/oauth/__tests__/oauth.test.ts +1 -1
- package/src/middleware/oauth/index.ts +505 -505
- package/src/middleware/oauth/providers.ts +115 -115
- package/src/middleware/rate-limit/index.ts +522 -522
- package/src/middleware/rate-limit/sqlite-store.ts +382 -382
- package/src/middleware/scheduler-cron.ts +96 -96
- package/src/middleware/secure/__tests__/secure.test.ts +360 -360
- package/src/middleware/secure/csp.ts +193 -193
- package/src/middleware/session.ts +174 -174
- package/src/middleware/timeout.ts +55 -55
- package/src/observability/logger-adapter.ts +36 -36
- package/src/observability/sqlite-store.ts +254 -254
- package/src/openapi/generator.ts +1 -1
- package/src/openapi/openapi.test.ts +43 -43
- package/src/perf/__tests__/user-marks.test.ts +354 -354
- package/src/perf/index.ts +133 -133
- package/src/plugins/__tests__/lifecycle-integration.test.ts +272 -272
- package/src/plugins/__tests__/runner.test.ts +409 -409
- package/src/plugins/define.ts +124 -124
- package/src/plugins/examples/dep-check-plugin.ts +80 -80
- package/src/plugins/examples/prerender-cache-plugin.ts +111 -111
- package/src/plugins/examples/sitemap-plugin.ts +65 -65
- package/src/plugins/runner.ts +361 -361
- package/src/plugins/types.ts +368 -368
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +32 -32
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +823 -823
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +559 -559
- package/src/resource/ddl/persistence-types.ts +218 -218
- package/src/resource/ddl/type-map.ts +223 -223
- package/src/resource/ddl/types.ts +232 -232
- package/src/resource/generator-repo.ts +630 -630
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.ts +6 -6
- package/src/routes/index.ts +74 -74
- package/src/routes/metadata-routes.ts +427 -427
- package/src/routes/types.ts +341 -341
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -141
- package/src/runtime/__tests__/hdr-client.test.ts +223 -223
- package/src/runtime/__tests__/http-errors.test.ts +117 -117
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/adapter.ts +47 -47
- package/src/runtime/boundary.tsx +252 -252
- package/src/runtime/cache.ts +494 -494
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +300 -300
- package/src/runtime/http-errors.ts +113 -113
- package/src/runtime/image-handler.ts +1 -1
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/middleware.ts +264 -264
- package/src/runtime/not-found.ts +93 -93
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/router.ts +105 -105
- package/src/runtime/server.ts +67 -67
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/ssr.ts +24 -31
- package/src/runtime/streaming-ssr.ts +32 -37
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/validate.ts +169 -169
- package/src/seo/index.ts +219 -219
- package/src/seo/integration/ssr.ts +306 -306
- package/src/seo/render/basic.ts +435 -435
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +197 -197
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +127 -127
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +104 -104
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +588 -588
- package/src/slot/validator.ts +39 -39
- package/src/storage/s3/__tests__/s3.test.ts +479 -479
- package/src/storage/s3/index.ts +412 -412
- package/src/testing/__tests__/assertions.test.ts +632 -632
- package/src/testing/__tests__/reporter.test.ts +454 -454
- package/src/testing/assertions.ts +986 -986
- package/src/testing/db.ts +157 -157
- package/src/testing/mocks.ts +203 -203
- package/src/testing/session.ts +190 -190
- package/src/types/branded.ts +56 -56
- package/src/types/index.ts +1 -1
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/watcher.ts +18 -18
package/src/i18n/define.ts
CHANGED
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 18.μ — `defineI18n()` contract.
|
|
3
|
-
*
|
|
4
|
-
* Thin factory with strict validation so misconfiguration surfaces
|
|
5
|
-
* at boot time (not at first request). Returns a frozen object
|
|
6
|
-
* branded as {@link I18nDefinition} to discourage callers from
|
|
7
|
-
* mutating the config after handing it to the runtime.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { I18nConfig, I18nDefinition, I18nStrategy, LocaleCode } from "./types";
|
|
11
|
-
|
|
12
|
-
export const VALID_STRATEGIES: readonly I18nStrategy[] = [
|
|
13
|
-
"path-prefix",
|
|
14
|
-
"domain",
|
|
15
|
-
"header",
|
|
16
|
-
"cookie",
|
|
17
|
-
] as const;
|
|
18
|
-
|
|
19
|
-
/** Default cookie name when none is specified. Matches Next.js `NEXT_LOCALE`. */
|
|
20
|
-
export const DEFAULT_I18N_COOKIE = "mandu_locale";
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Validate + brand an i18n configuration.
|
|
24
|
-
*
|
|
25
|
-
* @throws Error when:
|
|
26
|
-
* - `locales` is empty
|
|
27
|
-
* - `defaultLocale` is not in `locales`
|
|
28
|
-
* - `fallback` is supplied but not in `locales`
|
|
29
|
-
* - `strategy` is unknown
|
|
30
|
-
* - `strategy === "domain"` without a non-empty `domains` map
|
|
31
|
-
* - any `domains[...]` value is not in `locales`
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```ts
|
|
35
|
-
* export const i18n = defineI18n({
|
|
36
|
-
* locales: ['en', 'ko', 'ja'],
|
|
37
|
-
* defaultLocale: 'en',
|
|
38
|
-
* strategy: 'path-prefix',
|
|
39
|
-
* });
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
export function defineI18n(config: I18nConfig): I18nDefinition {
|
|
43
|
-
if (!config || typeof config !== "object") {
|
|
44
|
-
throw new Error("[mandu/i18n] defineI18n() requires a config object");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const { locales, defaultLocale, fallback, strategy, cookieName, domains } = config;
|
|
48
|
-
|
|
49
|
-
if (!Array.isArray(locales) || locales.length === 0) {
|
|
50
|
-
throw new Error("[mandu/i18n] `locales` must be a non-empty array");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
for (const l of locales) {
|
|
54
|
-
if (typeof l !== "string" || l.length === 0) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
"[mandu/i18n] every entry in `locales` must be a non-empty string"
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const dedup = new Set(locales);
|
|
62
|
-
if (dedup.size !== locales.length) {
|
|
63
|
-
throw new Error("[mandu/i18n] `locales` must not contain duplicates");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (typeof defaultLocale !== "string" || !dedup.has(defaultLocale)) {
|
|
67
|
-
throw new Error(
|
|
68
|
-
`[mandu/i18n] defaultLocale "${defaultLocale}" must be one of locales [${locales.join(", ")}]`
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (fallback !== undefined) {
|
|
73
|
-
if (typeof fallback !== "string" || !dedup.has(fallback)) {
|
|
74
|
-
throw new Error(
|
|
75
|
-
`[mandu/i18n] fallback "${fallback}" must be one of locales [${locales.join(", ")}]`
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (!VALID_STRATEGIES.includes(strategy)) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
`[mandu/i18n] strategy "${strategy}" is invalid. Expected one of: ${VALID_STRATEGIES.join(", ")}`
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (strategy === "domain") {
|
|
87
|
-
if (!domains || typeof domains !== "object" || Object.keys(domains).length === 0) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
"[mandu/i18n] strategy 'domain' requires a non-empty `domains` map"
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
for (const [host, locale] of Object.entries(domains)) {
|
|
93
|
-
if (typeof host !== "string" || host.length === 0) {
|
|
94
|
-
throw new Error("[mandu/i18n] domain keys must be non-empty strings");
|
|
95
|
-
}
|
|
96
|
-
if (!dedup.has(locale)) {
|
|
97
|
-
throw new Error(
|
|
98
|
-
`[mandu/i18n] domains["${host}"] = "${locale}" is not in locales`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const frozen: I18nDefinition = Object.freeze({
|
|
105
|
-
locales: Object.freeze([...locales]) as readonly LocaleCode[],
|
|
106
|
-
defaultLocale,
|
|
107
|
-
fallback,
|
|
108
|
-
strategy,
|
|
109
|
-
cookieName: cookieName ?? DEFAULT_I18N_COOKIE,
|
|
110
|
-
domains: domains ? Object.freeze({ ...domains }) : undefined,
|
|
111
|
-
__manduI18n: true,
|
|
112
|
-
});
|
|
113
|
-
return frozen;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Type guard for {@link I18nDefinition}. Use in adapter/runtime code
|
|
118
|
-
* that receives `unknown` from user config.
|
|
119
|
-
*/
|
|
120
|
-
export function isI18nDefinition(value: unknown): value is I18nDefinition {
|
|
121
|
-
return (
|
|
122
|
-
typeof value === "object" &&
|
|
123
|
-
value !== null &&
|
|
124
|
-
(value as { __manduI18n?: unknown }).__manduI18n === true
|
|
125
|
-
);
|
|
126
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Phase 18.μ — `defineI18n()` contract.
|
|
3
|
+
*
|
|
4
|
+
* Thin factory with strict validation so misconfiguration surfaces
|
|
5
|
+
* at boot time (not at first request). Returns a frozen object
|
|
6
|
+
* branded as {@link I18nDefinition} to discourage callers from
|
|
7
|
+
* mutating the config after handing it to the runtime.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { I18nConfig, I18nDefinition, I18nStrategy, LocaleCode } from "./types";
|
|
11
|
+
|
|
12
|
+
export const VALID_STRATEGIES: readonly I18nStrategy[] = [
|
|
13
|
+
"path-prefix",
|
|
14
|
+
"domain",
|
|
15
|
+
"header",
|
|
16
|
+
"cookie",
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
/** Default cookie name when none is specified. Matches Next.js `NEXT_LOCALE`. */
|
|
20
|
+
export const DEFAULT_I18N_COOKIE = "mandu_locale";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Validate + brand an i18n configuration.
|
|
24
|
+
*
|
|
25
|
+
* @throws Error when:
|
|
26
|
+
* - `locales` is empty
|
|
27
|
+
* - `defaultLocale` is not in `locales`
|
|
28
|
+
* - `fallback` is supplied but not in `locales`
|
|
29
|
+
* - `strategy` is unknown
|
|
30
|
+
* - `strategy === "domain"` without a non-empty `domains` map
|
|
31
|
+
* - any `domains[...]` value is not in `locales`
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* export const i18n = defineI18n({
|
|
36
|
+
* locales: ['en', 'ko', 'ja'],
|
|
37
|
+
* defaultLocale: 'en',
|
|
38
|
+
* strategy: 'path-prefix',
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function defineI18n(config: I18nConfig): I18nDefinition {
|
|
43
|
+
if (!config || typeof config !== "object") {
|
|
44
|
+
throw new Error("[mandu/i18n] defineI18n() requires a config object");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { locales, defaultLocale, fallback, strategy, cookieName, domains } = config;
|
|
48
|
+
|
|
49
|
+
if (!Array.isArray(locales) || locales.length === 0) {
|
|
50
|
+
throw new Error("[mandu/i18n] `locales` must be a non-empty array");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const l of locales) {
|
|
54
|
+
if (typeof l !== "string" || l.length === 0) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
"[mandu/i18n] every entry in `locales` must be a non-empty string"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const dedup = new Set(locales);
|
|
62
|
+
if (dedup.size !== locales.length) {
|
|
63
|
+
throw new Error("[mandu/i18n] `locales` must not contain duplicates");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof defaultLocale !== "string" || !dedup.has(defaultLocale)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`[mandu/i18n] defaultLocale "${defaultLocale}" must be one of locales [${locales.join(", ")}]`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (fallback !== undefined) {
|
|
73
|
+
if (typeof fallback !== "string" || !dedup.has(fallback)) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`[mandu/i18n] fallback "${fallback}" must be one of locales [${locales.join(", ")}]`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!VALID_STRATEGIES.includes(strategy)) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`[mandu/i18n] strategy "${strategy}" is invalid. Expected one of: ${VALID_STRATEGIES.join(", ")}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (strategy === "domain") {
|
|
87
|
+
if (!domains || typeof domains !== "object" || Object.keys(domains).length === 0) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
"[mandu/i18n] strategy 'domain' requires a non-empty `domains` map"
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
for (const [host, locale] of Object.entries(domains)) {
|
|
93
|
+
if (typeof host !== "string" || host.length === 0) {
|
|
94
|
+
throw new Error("[mandu/i18n] domain keys must be non-empty strings");
|
|
95
|
+
}
|
|
96
|
+
if (!dedup.has(locale)) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`[mandu/i18n] domains["${host}"] = "${locale}" is not in locales`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const frozen: I18nDefinition = Object.freeze({
|
|
105
|
+
locales: Object.freeze([...locales]) as readonly LocaleCode[],
|
|
106
|
+
defaultLocale,
|
|
107
|
+
fallback,
|
|
108
|
+
strategy,
|
|
109
|
+
cookieName: cookieName ?? DEFAULT_I18N_COOKIE,
|
|
110
|
+
domains: domains ? Object.freeze({ ...domains }) : undefined,
|
|
111
|
+
__manduI18n: true,
|
|
112
|
+
});
|
|
113
|
+
return frozen;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Type guard for {@link I18nDefinition}. Use in adapter/runtime code
|
|
118
|
+
* that receives `unknown` from user config.
|
|
119
|
+
*/
|
|
120
|
+
export function isI18nDefinition(value: unknown): value is I18nDefinition {
|
|
121
|
+
return (
|
|
122
|
+
typeof value === "object" &&
|
|
123
|
+
value !== null &&
|
|
124
|
+
(value as { __manduI18n?: unknown }).__manduI18n === true
|
|
125
|
+
);
|
|
126
|
+
}
|
package/src/i18n/index.ts
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 18.μ — i18n barrel.
|
|
3
|
-
*
|
|
4
|
-
* Public surface for first-class internationalization in Mandu.
|
|
5
|
-
*
|
|
6
|
-
* ```ts
|
|
7
|
-
* import { defineI18n, defineMessages, resolveLocale, createTranslator } from "@mandujs/core/i18n";
|
|
8
|
-
*
|
|
9
|
-
* export const i18n = defineI18n({
|
|
10
|
-
* locales: ["en", "ko"],
|
|
11
|
-
* defaultLocale: "en",
|
|
12
|
-
* strategy: "path-prefix",
|
|
13
|
-
* });
|
|
14
|
-
*
|
|
15
|
-
* export const messages = defineMessages({
|
|
16
|
-
* en: { welcome: "Welcome, {{name}}" },
|
|
17
|
-
* ko: { welcome: "환영합니다, {{name}}님" },
|
|
18
|
-
* } as const);
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
export type {
|
|
23
|
-
LocaleCode,
|
|
24
|
-
MessageBundle,
|
|
25
|
-
I18nStrategy,
|
|
26
|
-
I18nConfig,
|
|
27
|
-
I18nDefinition,
|
|
28
|
-
ResolvedLocale,
|
|
29
|
-
Translator,
|
|
30
|
-
} from "./types";
|
|
31
|
-
|
|
32
|
-
export {
|
|
33
|
-
defineI18n,
|
|
34
|
-
isI18nDefinition,
|
|
35
|
-
VALID_STRATEGIES,
|
|
36
|
-
DEFAULT_I18N_COOKIE,
|
|
37
|
-
} from "./define";
|
|
38
|
-
|
|
39
|
-
export {
|
|
40
|
-
resolveLocale,
|
|
41
|
-
stripLocalePrefix,
|
|
42
|
-
parseAcceptLanguage,
|
|
43
|
-
readLocaleCookie,
|
|
44
|
-
} from "./locale-resolver";
|
|
45
|
-
|
|
46
|
-
export {
|
|
47
|
-
defineMessages,
|
|
48
|
-
createTranslator,
|
|
49
|
-
interpolate,
|
|
50
|
-
isMessageRegistry,
|
|
51
|
-
type MessageRegistry,
|
|
52
|
-
} from "./message-registry";
|
|
1
|
+
/**
|
|
2
|
+
* Phase 18.μ — i18n barrel.
|
|
3
|
+
*
|
|
4
|
+
* Public surface for first-class internationalization in Mandu.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { defineI18n, defineMessages, resolveLocale, createTranslator } from "@mandujs/core/i18n";
|
|
8
|
+
*
|
|
9
|
+
* export const i18n = defineI18n({
|
|
10
|
+
* locales: ["en", "ko"],
|
|
11
|
+
* defaultLocale: "en",
|
|
12
|
+
* strategy: "path-prefix",
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* export const messages = defineMessages({
|
|
16
|
+
* en: { welcome: "Welcome, {{name}}" },
|
|
17
|
+
* ko: { welcome: "환영합니다, {{name}}님" },
|
|
18
|
+
* } as const);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export type {
|
|
23
|
+
LocaleCode,
|
|
24
|
+
MessageBundle,
|
|
25
|
+
I18nStrategy,
|
|
26
|
+
I18nConfig,
|
|
27
|
+
I18nDefinition,
|
|
28
|
+
ResolvedLocale,
|
|
29
|
+
Translator,
|
|
30
|
+
} from "./types";
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
defineI18n,
|
|
34
|
+
isI18nDefinition,
|
|
35
|
+
VALID_STRATEGIES,
|
|
36
|
+
DEFAULT_I18N_COOKIE,
|
|
37
|
+
} from "./define";
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
resolveLocale,
|
|
41
|
+
stripLocalePrefix,
|
|
42
|
+
parseAcceptLanguage,
|
|
43
|
+
readLocaleCookie,
|
|
44
|
+
} from "./locale-resolver";
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
defineMessages,
|
|
48
|
+
createTranslator,
|
|
49
|
+
interpolate,
|
|
50
|
+
isMessageRegistry,
|
|
51
|
+
type MessageRegistry,
|
|
52
|
+
} from "./message-registry";
|