intor 2.3.10 → 2.3.12
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/core/src/config/constants/routing.js +11 -15
- package/dist/core/src/config/define-intor-config.js +16 -16
- package/dist/core/src/config/resolvers/resolve-fallback-locales.js +45 -35
- package/dist/core/src/config/resolvers/resolve-routing-options.js +56 -36
- package/dist/core/src/config/validators/validate-default-locale.js +5 -19
- package/dist/core/src/config/validators/validate-id.js +21 -0
- package/dist/core/src/config/validators/validate-supported-locales.js +6 -16
- package/dist/core/src/core/error/intor-error.js +1 -1
- package/dist/core/src/routing/pathname/locale-prefix-pathname.js +11 -11
- package/dist/core/src/server/helpers/local-messages-from-url.js +0 -1
- package/dist/core/src/server/messages/load-local-messages/load-local-messages.js +14 -12
- package/dist/core/src/server/messages/load-local-messages/read-locale-messages/read-locale-messages.js +1 -1
- package/dist/core/src/server/messages/load-messages.js +0 -1
- package/dist/express/src/adapters/express/middleware/create-intor.js +8 -4
- package/dist/express/src/core/constants/headers.js +1 -2
- package/dist/express/src/core/error/intor-error.js +1 -1
- package/dist/express/src/routing/inbound/resolve-inbound.js +9 -8
- package/dist/express/src/routing/inbound/resolve-locale/resolve-locale.js +5 -12
- package/dist/express/src/routing/inbound/resolve-pathname/resolve-pathname.js +9 -19
- package/dist/express/src/routing/inbound/resolve-pathname/strategies/all.js +18 -13
- package/dist/express/src/routing/inbound/resolve-pathname/strategies/except-default.js +20 -24
- package/dist/express/src/routing/inbound/resolve-pathname/strategies/none.js +1 -7
- package/dist/express/src/routing/locale/get-locale-from-query.js +2 -2
- package/dist/express/src/routing/pathname/locale-prefix-pathname.js +11 -11
- package/dist/express/src/server/messages/load-local-messages/load-local-messages.js +14 -12
- package/dist/express/src/server/messages/load-local-messages/read-locale-messages/read-locale-messages.js +1 -1
- package/dist/express/src/server/messages/load-messages.js +0 -1
- package/dist/next/src/adapters/next/proxy/intor-proxy.js +17 -6
- package/dist/next/src/core/constants/headers.js +1 -0
- package/dist/next/src/core/error/intor-error.js +1 -1
- package/dist/next/src/policies/shoud-full-reload.js +1 -1
- package/dist/next/src/routing/inbound/resolve-inbound.js +10 -7
- package/dist/next/src/routing/inbound/resolve-locale/resolve-locale.js +5 -12
- package/dist/next/src/routing/inbound/resolve-pathname/resolve-pathname.js +9 -19
- package/dist/next/src/routing/inbound/resolve-pathname/strategies/all.js +24 -13
- package/dist/next/src/routing/inbound/resolve-pathname/strategies/except-default.js +25 -23
- package/dist/next/src/routing/inbound/resolve-pathname/strategies/none.js +1 -7
- package/dist/next/src/routing/locale/get-locale-from-query.js +2 -2
- package/dist/next/src/routing/navigation/derive-target.js +11 -8
- package/dist/next/src/routing/navigation/utils/derive-host-destination.js +3 -3
- package/dist/next/src/routing/navigation/utils/derive-query-destination.js +2 -2
- package/dist/next/src/routing/pathname/locale-prefix-pathname.js +11 -11
- package/dist/next/src/server/messages/load-local-messages/load-local-messages.js +14 -12
- package/dist/next/src/server/messages/load-local-messages/read-locale-messages/read-locale-messages.js +1 -1
- package/dist/next/src/server/messages/load-messages.js +0 -1
- package/dist/react/src/client/react/navigation/use-execute-navigation.js +15 -3
- package/dist/react/src/client/react/provider/effects/use-locale-effects.js +5 -2
- package/dist/react/src/core/error/intor-error.js +1 -1
- package/dist/react/src/policies/shoud-full-reload.js +1 -1
- package/dist/react/src/policies/should-sync-locale.js +8 -0
- package/dist/react/src/routing/navigation/derive-target.js +11 -8
- package/dist/react/src/routing/navigation/utils/derive-host-destination.js +3 -3
- package/dist/react/src/routing/navigation/utils/derive-query-destination.js +2 -2
- package/dist/react/src/routing/pathname/locale-prefix-pathname.js +11 -11
- package/dist/svelte/src/client/svelte/runtime/effects/locale-effects.js +4 -1
- package/dist/svelte/src/core/error/intor-error.js +1 -1
- package/dist/types/src/adapters/express/middleware/create-intor.d.ts +1 -1
- package/dist/types/src/adapters/next/proxy/intor-proxy.d.ts +2 -0
- package/dist/types/src/client/react/navigation/use-execute-navigation.d.ts +1 -1
- package/dist/types/src/config/define-intor-config.d.ts +6 -12
- package/dist/types/src/config/resolvers/resolve-fallback-locales.d.ts +4 -16
- package/dist/types/src/config/resolvers/resolve-routing-options.d.ts +9 -2
- package/dist/types/src/config/types/index.d.ts +1 -1
- package/dist/types/src/config/types/intor-config.d.ts +16 -3
- package/dist/types/src/config/types/routing.d.ts +60 -73
- package/dist/types/src/config/types/translator.d.ts +2 -2
- package/dist/types/src/config/validators/validate-default-locale.d.ts +3 -9
- package/dist/types/src/config/validators/validate-id.d.ts +6 -0
- package/dist/types/src/config/validators/validate-supported-locales.d.ts +3 -8
- package/dist/types/src/core/constants/headers.d.ts +1 -0
- package/dist/types/src/core/error/intor-error.d.ts +1 -1
- package/dist/types/src/core/index.d.ts +1 -1
- package/dist/types/src/core/types/index.d.ts +1 -1
- package/dist/types/src/core/types/routing.d.ts +5 -6
- package/dist/types/src/core/types/translator-instance.d.ts +8 -3
- package/dist/types/src/policies/index.d.ts +2 -0
- package/dist/types/src/policies/should-sync-locale.d.ts +4 -0
- package/dist/types/src/routing/inbound/resolve-inbound.d.ts +2 -2
- package/dist/types/src/routing/inbound/resolve-locale/resolve-locale.d.ts +3 -10
- package/dist/types/src/routing/inbound/resolve-pathname/resolve-pathname.d.ts +3 -13
- package/dist/types/src/routing/inbound/resolve-pathname/strategies/all.d.ts +1 -1
- package/dist/types/src/routing/inbound/resolve-pathname/strategies/except-default.d.ts +1 -1
- package/dist/types/src/routing/inbound/resolve-pathname/strategies/none.d.ts +2 -2
- package/dist/types/src/routing/inbound/resolve-pathname/types.d.ts +7 -4
- package/dist/types/src/routing/pathname/locale-prefix-pathname.d.ts +3 -3
- package/dist/types/src/server/messages/load-local-messages/load-local-messages.d.ts +5 -2
- package/dist/types/src/server/messages/load-local-messages/types.d.ts +1 -2
- package/dist/vue/src/client/vue/provider/effects/use-locale-effects.js +4 -1
- package/dist/vue/src/core/error/intor-error.js +1 -1
- package/package.json +1 -1
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
// Default routing options
|
|
2
2
|
const DEFAULT_ROUTING_OPTIONS = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
path: { prefix: "none" },
|
|
10
|
-
query: { key: "locale" },
|
|
11
|
-
host: { map: {} },
|
|
3
|
+
basePath: "/",
|
|
4
|
+
localePrefix: "none",
|
|
5
|
+
inbound: {
|
|
6
|
+
localeSources: ["path", "query", "cookie", "detected"],
|
|
7
|
+
queryKey: "locale",
|
|
8
|
+
firstVisit: { localeSource: "browser", persist: true, redirect: true },
|
|
12
9
|
},
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
outbound: {
|
|
11
|
+
localeCarrier: "path",
|
|
12
|
+
queryKey: "locale",
|
|
13
|
+
host: { map: {}, default: undefined },
|
|
14
|
+
forceFullReload: false,
|
|
17
15
|
},
|
|
18
|
-
basePath: "/",
|
|
19
|
-
forceFullReload: false,
|
|
20
16
|
};
|
|
21
17
|
|
|
22
18
|
export { DEFAULT_ROUTING_OPTIONS };
|
|
@@ -3,34 +3,34 @@ import { resolveCookieOptions } from './resolvers/resolve-cookie-options.js';
|
|
|
3
3
|
import { resolveFallbackLocales } from './resolvers/resolve-fallback-locales.js';
|
|
4
4
|
import { resolveRoutingOptions } from './resolvers/resolve-routing-options.js';
|
|
5
5
|
import { validateDefaultLocale } from './validators/validate-default-locale.js';
|
|
6
|
+
import { validateId } from './validators/validate-id.js';
|
|
6
7
|
import { validateSupportedLocales } from './validators/validate-supported-locales.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* Defines
|
|
10
|
+
* Defines the canonical Intor configuration.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* Transforms a raw config into a validated and runtime-ready form by:
|
|
13
|
+
* - enforcing required invariants
|
|
14
|
+
* - resolving derived options
|
|
15
|
+
* - applying stable defaults
|
|
13
16
|
*
|
|
14
|
-
*
|
|
15
|
-
* - Validate required invariants
|
|
16
|
-
* - Resolve and normalize derived options
|
|
17
|
-
* - Apply stable defaults
|
|
18
|
-
*
|
|
19
|
-
* This function is purely declarative and side-effect free.
|
|
20
|
-
* It does not create runtime instances or attach dynamic behavior.
|
|
21
|
-
*
|
|
22
|
-
* Intended to run once during application initialization.
|
|
17
|
+
* This function is declarative and side-effect free.
|
|
23
18
|
*/
|
|
24
19
|
const defineIntorConfig = (config) => {
|
|
20
|
+
// -----------------------------------------------------------------------------
|
|
25
21
|
// Validators
|
|
26
|
-
|
|
27
|
-
const
|
|
22
|
+
// -----------------------------------------------------------------------------
|
|
23
|
+
const id = validateId(config.id ?? "default");
|
|
24
|
+
const supportedLocales = validateSupportedLocales(id, config.supportedLocales);
|
|
25
|
+
const supportedSet = new Set(supportedLocales);
|
|
26
|
+
const defaultLocale = validateDefaultLocale(id, config.defaultLocale, supportedSet);
|
|
27
|
+
// -----------------------------------------------------------------------------
|
|
28
28
|
// Resolvers
|
|
29
|
-
|
|
29
|
+
// -----------------------------------------------------------------------------
|
|
30
|
+
const fallbackLocales = resolveFallbackLocales(config, id, supportedSet);
|
|
30
31
|
const cookie = resolveCookieOptions(config.cookie);
|
|
31
32
|
const routing = resolveRoutingOptions(config.routing);
|
|
32
33
|
const cache = resolveCacheOptions(config.cache);
|
|
33
|
-
const id = config.id ?? "default";
|
|
34
34
|
return {
|
|
35
35
|
id,
|
|
36
36
|
messages: config.messages,
|
|
@@ -1,50 +1,60 @@
|
|
|
1
|
+
import '../../core/error/intor-error.js';
|
|
2
|
+
import { getLogger } from '../../core/logger/get-logger.js';
|
|
3
|
+
import 'keyv';
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
|
-
* Resolves
|
|
3
|
-
*
|
|
4
|
-
* This utility validates the user-defined `fallbackLocales` configuration
|
|
5
|
-
* against the provided `supportedLocales` list and the configured `defaultLocale`.
|
|
6
|
-
*
|
|
7
|
-
* Resolution rules:
|
|
8
|
-
* - Only locales listed in `supportedLocales` are considered valid fallbacks.
|
|
9
|
-
* - The literal value `"default"` is always allowed and represents the configured default locale.
|
|
10
|
-
* - Fallback entries matching `defaultLocale` are treated as valid.
|
|
11
|
-
* - Invalid fallback locales are ignored and reported via warnings.
|
|
6
|
+
* Resolves fallbackLocales into a runtime-safe mapping.
|
|
12
7
|
*
|
|
13
|
-
*
|
|
14
|
-
* - This function is purely defensive and does not throw on invalid input.
|
|
15
|
-
* - Only validated fallback mappings are included in the returned result.
|
|
16
|
-
* - Logging is used for diagnostics only and does not affect the output.
|
|
8
|
+
* Invalid entries are ignored and reported via warnings.
|
|
17
9
|
*/
|
|
18
|
-
const resolveFallbackLocales = (config,
|
|
10
|
+
const resolveFallbackLocales = (config, id, supportedSet) => {
|
|
19
11
|
const { defaultLocale, fallbackLocales } = config;
|
|
12
|
+
// Fast exit: no fallback configuration provided
|
|
20
13
|
if (!fallbackLocales || typeof fallbackLocales !== "object") {
|
|
21
14
|
return {};
|
|
22
15
|
}
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
const logger = getLogger({ id }).child({
|
|
17
|
+
scope: "resolve-fallback-locales",
|
|
18
|
+
});
|
|
19
|
+
const resolvedMap = {};
|
|
20
|
+
const invalidMap = new Map();
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Normalize each fallback rule
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
26
24
|
for (const [locale, fallbacks] of Object.entries(fallbackLocales)) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
fallback === defaultLocale);
|
|
32
|
-
// Invalid: not "default", not supported, and not defaultLocale
|
|
33
|
-
const invalidFallbacks = fallbackArray.filter((fallback) => fallback !== "default" &&
|
|
34
|
-
!supportedSet.has(fallback) &&
|
|
35
|
-
fallback !== defaultLocale);
|
|
36
|
-
if (invalidFallbacks.length > 0) {
|
|
37
|
-
invalidFallbackMap.set(locale, invalidFallbacks);
|
|
25
|
+
// Guard: fallback rules for unreachable locales are ignored.
|
|
26
|
+
if (!supportedSet.has(locale) && locale !== defaultLocale) {
|
|
27
|
+
logger.warn(`Fallback locale "${locale}" is not listed in supportedLocales.`);
|
|
28
|
+
continue;
|
|
38
29
|
}
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
const fallbackArray = Array.isArray(fallbacks) ? fallbacks : [];
|
|
31
|
+
const validFallbacks = [];
|
|
32
|
+
const invalidFallbacks = [];
|
|
33
|
+
for (const fallback of fallbackArray) {
|
|
34
|
+
// A fallback target is considered valid if:
|
|
35
|
+
// - It exists in `supportedLocales`
|
|
36
|
+
// - It is the literal value "default"
|
|
37
|
+
if (fallback === "default" || supportedSet.has(fallback)) {
|
|
38
|
+
validFallbacks.push(fallback);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
invalidFallbacks.push(fallback);
|
|
42
|
+
}
|
|
41
43
|
}
|
|
44
|
+
if (invalidFallbacks.length > 0)
|
|
45
|
+
invalidMap.set(locale, invalidFallbacks);
|
|
46
|
+
if (validFallbacks.length > 0)
|
|
47
|
+
resolvedMap[locale] = validFallbacks;
|
|
42
48
|
}
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Diagnostics: report ignored fallback entries
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
for (const [locale, invalids] of invalidMap.entries()) {
|
|
53
|
+
logger.warn(`Invalid fallback locales for "${locale}".`, {
|
|
54
|
+
invalids: invalids.join(", "),
|
|
55
|
+
});
|
|
46
56
|
}
|
|
47
|
-
return
|
|
57
|
+
return resolvedMap;
|
|
48
58
|
};
|
|
49
59
|
|
|
50
60
|
export { resolveFallbackLocales };
|
|
@@ -1,45 +1,65 @@
|
|
|
1
1
|
import '../../core/error/intor-error.js';
|
|
2
|
-
import {
|
|
2
|
+
import { deepMerge } from '../../core/utils/deep-merge.js';
|
|
3
3
|
import 'logry';
|
|
4
4
|
import 'keyv';
|
|
5
5
|
import { DEFAULT_ROUTING_OPTIONS } from '../constants/routing.js';
|
|
6
6
|
import '../constants/cookie.js';
|
|
7
7
|
import '../constants/cache.js';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
};
|
|
43
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Resolves routing configuration into a fully normalized form.
|
|
11
|
+
*
|
|
12
|
+
* - Flat shortcuts are projected into structured options.
|
|
13
|
+
* - Structured options override projected values.
|
|
14
|
+
* - Defaults are applied last.
|
|
15
|
+
*/
|
|
16
|
+
function resolveRoutingOptions(raw) {
|
|
17
|
+
if (!raw)
|
|
18
|
+
return DEFAULT_ROUTING_OPTIONS;
|
|
19
|
+
const projectedFromFlat = projectFlatToStructured(raw);
|
|
20
|
+
const structuredOverrides = stripFlatShortcuts(raw);
|
|
21
|
+
const defined = deepMerge(projectedFromFlat, structuredOverrides);
|
|
22
|
+
return deepMerge(DEFAULT_ROUTING_OPTIONS, defined);
|
|
23
|
+
}
|
|
24
|
+
/** Assigns a value to the target only if it is explicitly defined. */
|
|
25
|
+
function assignIfDefined(target, key, value) {
|
|
26
|
+
if (value !== undefined)
|
|
27
|
+
target[key] = value;
|
|
28
|
+
}
|
|
29
|
+
/** Projects flat routing shortcuts into structured options. */
|
|
30
|
+
function projectFlatToStructured(flat) {
|
|
31
|
+
const structured = {};
|
|
32
|
+
assignIfDefined(structured, "basePath", flat.basePath);
|
|
33
|
+
assignIfDefined(structured, "localePrefix", flat.localePrefix);
|
|
34
|
+
// inbound
|
|
35
|
+
const inbound = {};
|
|
36
|
+
assignIfDefined(inbound, "localeSources", flat.localeSources);
|
|
37
|
+
assignIfDefined(inbound, "queryKey", flat.queryKey);
|
|
38
|
+
assignIfDefined(inbound, "firstVisit", flat.firstVisit);
|
|
39
|
+
if (Object.keys(inbound).length > 0)
|
|
40
|
+
structured.inbound = inbound;
|
|
41
|
+
// outbound
|
|
42
|
+
const outbound = {};
|
|
43
|
+
assignIfDefined(outbound, "localeCarrier", flat.localeCarrier);
|
|
44
|
+
assignIfDefined(outbound, "queryKey", flat.queryKey);
|
|
45
|
+
assignIfDefined(outbound, "host", flat.host);
|
|
46
|
+
assignIfDefined(outbound, "forceFullReload", flat.forceFullReload);
|
|
47
|
+
if (Object.keys(outbound).length > 0)
|
|
48
|
+
structured.outbound = outbound;
|
|
49
|
+
return structured;
|
|
50
|
+
}
|
|
51
|
+
/** Removes flat shortcuts, preserving structured overrides only. */
|
|
52
|
+
function stripFlatShortcuts(raw) {
|
|
53
|
+
const structured = {};
|
|
54
|
+
if ("basePath" in raw)
|
|
55
|
+
structured.basePath = raw.basePath;
|
|
56
|
+
if ("localePrefix" in raw)
|
|
57
|
+
structured.localePrefix = raw.localePrefix;
|
|
58
|
+
if ("inbound" in raw)
|
|
59
|
+
structured.inbound = raw.inbound;
|
|
60
|
+
if ("outbound" in raw)
|
|
61
|
+
structured.outbound = raw.outbound;
|
|
62
|
+
return structured;
|
|
63
|
+
}
|
|
44
64
|
|
|
45
65
|
export { resolveRoutingOptions };
|
|
@@ -3,30 +3,16 @@ import 'logry';
|
|
|
3
3
|
import 'keyv';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Validates the configured
|
|
6
|
+
* Validates that the configured defaultLocale is supported.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* - Ensures that `defaultLocale` is included in `supportedLocales`.
|
|
10
|
-
*
|
|
11
|
-
* This validation is part of the configuration initialization phase
|
|
12
|
-
* and is expected to fail fast when misconfigured.
|
|
8
|
+
* Fails fast if `defaultLocale` is not included in `supportedLocales`.
|
|
13
9
|
*/
|
|
14
|
-
const validateDefaultLocale = (
|
|
15
|
-
|
|
16
|
-
// Throw error if defaultLocale is undefined
|
|
17
|
-
if (!defaultLocale) {
|
|
18
|
-
throw new IntorError({
|
|
19
|
-
id,
|
|
20
|
-
code: IntorErrorCode.MISSING_DEFAULT_LOCALE,
|
|
21
|
-
message: `The defaultLocale is undefined`,
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
// Throw error if defaultLocale is not listed in supportedLocales
|
|
25
|
-
if (!supportedLocales.includes(defaultLocale)) {
|
|
10
|
+
const validateDefaultLocale = (id, defaultLocale, supportedSet) => {
|
|
11
|
+
if (!supportedSet.has(defaultLocale)) {
|
|
26
12
|
throw new IntorError({
|
|
27
13
|
id,
|
|
28
14
|
code: IntorErrorCode.UNSUPPORTED_DEFAULT_LOCALE,
|
|
29
|
-
message: `
|
|
15
|
+
message: `"defaultLocale" must be included in "supportedLocales".`,
|
|
30
16
|
});
|
|
31
17
|
}
|
|
32
18
|
return defaultLocale;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IntorError, IntorErrorCode } from '../../core/error/intor-error.js';
|
|
2
|
+
import 'logry';
|
|
3
|
+
import 'keyv';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validates that the given id is a non-empty string.
|
|
7
|
+
*
|
|
8
|
+
* Fails fast if the id is empty or whitespace-only.
|
|
9
|
+
*/
|
|
10
|
+
const validateId = (id) => {
|
|
11
|
+
if (id.trim() === "") {
|
|
12
|
+
throw new IntorError({
|
|
13
|
+
id,
|
|
14
|
+
code: IntorErrorCode.INVALID_CONFIG_ID,
|
|
15
|
+
message: `"id" must be a non-empty string.`,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return id;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export { validateId };
|
|
@@ -3,29 +3,19 @@ import 'logry';
|
|
|
3
3
|
import 'keyv';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Validates
|
|
6
|
+
* Validates that supportedLocales is provided and non-empty.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* - Falls back to inferring locales from static message keys when no loader
|
|
10
|
-
* is configured.
|
|
11
|
-
*
|
|
12
|
-
* This validation runs during configuration initialization and is expected
|
|
13
|
-
* to fail fast when required inputs are missing.
|
|
8
|
+
* Fails fast when missing.
|
|
14
9
|
*/
|
|
15
|
-
const validateSupportedLocales = (
|
|
16
|
-
|
|
17
|
-
const hasAnyLoader = !!config.loader || !!config.server?.loader || !!config.client?.loader;
|
|
18
|
-
// Ensure supportedLocales is set when using loader
|
|
19
|
-
if (hasAnyLoader && !supportedLocales) {
|
|
10
|
+
const validateSupportedLocales = (id, supportedLocales) => {
|
|
11
|
+
if (!supportedLocales || supportedLocales.length === 0) {
|
|
20
12
|
throw new IntorError({
|
|
21
13
|
id,
|
|
22
14
|
code: IntorErrorCode.MISSING_SUPPORTED_LOCALES,
|
|
23
|
-
message: `"supportedLocales"
|
|
24
|
-
Please specify all supported locales explicitly.`,
|
|
15
|
+
message: `"supportedLocales" must be specified.`,
|
|
25
16
|
});
|
|
26
17
|
}
|
|
27
|
-
|
|
28
|
-
return supportedLocales || Object.keys(config.messages || {});
|
|
18
|
+
return supportedLocales;
|
|
29
19
|
};
|
|
30
20
|
|
|
31
21
|
export { validateSupportedLocales };
|
|
@@ -13,8 +13,8 @@ class IntorError extends Error {
|
|
|
13
13
|
var IntorErrorCode;
|
|
14
14
|
(function (IntorErrorCode) {
|
|
15
15
|
// config
|
|
16
|
+
IntorErrorCode["INVALID_CONFIG_ID"] = "INTOR_INVALID_CONFIG_ID";
|
|
16
17
|
IntorErrorCode["MISSING_SUPPORTED_LOCALES"] = "INTOR_MISSING_SUPPORTED_LOCALES";
|
|
17
|
-
IntorErrorCode["MISSING_DEFAULT_LOCALE"] = "INTOR_MISSING_DEFAULT_LOCALE";
|
|
18
18
|
IntorErrorCode["UNSUPPORTED_DEFAULT_LOCALE"] = "INTOR_UNSUPPORTED_DEFAULT_LOCALE";
|
|
19
19
|
// runtime
|
|
20
20
|
IntorErrorCode["RUNTIME_NOT_INITIALIZED"] = "INTOR_RUNTIME_NOT_INITIALIZED";
|
|
@@ -5,35 +5,35 @@ import 'logry';
|
|
|
5
5
|
import 'keyv';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Applies
|
|
8
|
+
* Applies the configured locale prefix behavior to a standardized pathname.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```ts
|
|
12
|
-
* // config.routing.
|
|
12
|
+
* // config.routing.localePrefix: "all"
|
|
13
13
|
* localePrefixPathname({ config, pathname: "/app/{locale}/about", locale: "en-US" });
|
|
14
14
|
* // => /app/en-US/about
|
|
15
15
|
*
|
|
16
|
-
* // config.routing.
|
|
16
|
+
* // config.routing.localePrefix: "none"
|
|
17
17
|
* localePrefixPathname({ config, pathname: "/app/{locale}/about", locale: "en-US" });
|
|
18
18
|
* // => /app/about
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
21
|
const localePrefixPathname = (config, standardizedPathname, locale) => {
|
|
22
|
-
const {
|
|
23
|
-
if (
|
|
24
|
-
throw new Error('No locale when using
|
|
22
|
+
const { localePrefix } = config.routing;
|
|
23
|
+
if (localePrefix !== "none" && !locale) {
|
|
24
|
+
throw new Error('No locale when using localePrefix "all", "except-default"');
|
|
25
25
|
}
|
|
26
|
-
//
|
|
27
|
-
if (
|
|
26
|
+
// localePrefix: "all"
|
|
27
|
+
if (localePrefix === "all") {
|
|
28
28
|
return normalizePathname(standardizedPathname.replaceAll(PREFIX_PLACEHOLDER, locale));
|
|
29
29
|
}
|
|
30
|
-
//
|
|
31
|
-
if (
|
|
30
|
+
// localePrefix: "except-default"
|
|
31
|
+
if (localePrefix === "except-default") {
|
|
32
32
|
return locale === config.defaultLocale
|
|
33
33
|
? normalizePathname(standardizedPathname.replaceAll(`/${PREFIX_PLACEHOLDER}`, ""))
|
|
34
34
|
: normalizePathname(standardizedPathname.replaceAll(PREFIX_PLACEHOLDER, locale));
|
|
35
35
|
}
|
|
36
|
-
//
|
|
36
|
+
// localePrefix: "none"
|
|
37
37
|
return normalizePathname(standardizedPathname.replaceAll(`/${PREFIX_PLACEHOLDER}`, ""));
|
|
38
38
|
};
|
|
39
39
|
|
|
@@ -45,7 +45,6 @@ async function loadMessagesFromUrl(url, options) {
|
|
|
45
45
|
concurrency: options?.concurrency,
|
|
46
46
|
readOptions: options?.readOptions,
|
|
47
47
|
pool: options?.pool,
|
|
48
|
-
cacheOptions: options?.cacheOptions || { enabled: false, ttl: 0 },
|
|
49
48
|
allowCacheWrite: options?.allowCacheWrite,
|
|
50
49
|
loggerOptions: options?.loggerOptions || { id: "default" },
|
|
51
50
|
});
|
|
@@ -13,18 +13,22 @@ import { readLocaleMessages } from './read-locale-messages/read-locale-messages.
|
|
|
13
13
|
* It coordinates:
|
|
14
14
|
*
|
|
15
15
|
* - Locale resolution with fallbacks
|
|
16
|
-
* -
|
|
16
|
+
* - Process-level memoization (read by default, write by ownership)
|
|
17
17
|
* - Concurrency control for file system access
|
|
18
18
|
*
|
|
19
|
+
* Local messages are cached for the lifetime of the process.
|
|
20
|
+
* Cache writes are restricted to the primary initialization flow.
|
|
21
|
+
*
|
|
19
22
|
* File traversal, parsing, and validation are delegated to lower-level utilities.
|
|
20
23
|
*/
|
|
21
|
-
const loadLocalMessages = async ({ id, locale, fallbackLocales, namespaces, rootDir = "messages", concurrency = 10, readOptions, pool = getGlobalMessagesPool(),
|
|
24
|
+
const loadLocalMessages = async ({ id, locale, fallbackLocales, namespaces, rootDir = "messages", concurrency = 10, readOptions, pool = getGlobalMessagesPool(), allowCacheWrite = false, loggerOptions, }) => {
|
|
25
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
22
26
|
const baseLogger = getLogger(loggerOptions);
|
|
23
27
|
const logger = baseLogger.child({ scope: "load-local-messages" });
|
|
24
28
|
const start = performance.now();
|
|
25
29
|
logger.debug("Loading local messages.", {
|
|
26
30
|
rootDir,
|
|
27
|
-
resolvedRootDir: path.resolve(
|
|
31
|
+
resolvedRootDir: path.resolve(rootDir),
|
|
28
32
|
});
|
|
29
33
|
// ---------------------------------------------------------------------------
|
|
30
34
|
// Cache key resolution
|
|
@@ -40,13 +44,11 @@ const loadLocalMessages = async ({ id, locale, fallbackLocales, namespaces, root
|
|
|
40
44
|
// ---------------------------------------------------------------------------
|
|
41
45
|
// Cache read
|
|
42
46
|
// ---------------------------------------------------------------------------
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return cached;
|
|
49
|
-
}
|
|
47
|
+
if (cacheKey) {
|
|
48
|
+
const cached = await pool?.get(cacheKey);
|
|
49
|
+
if (cached) {
|
|
50
|
+
logger.debug("Messages cache hit.", { key: cacheKey });
|
|
51
|
+
return cached;
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
// ---------------------------------------------------------------------------
|
|
@@ -88,9 +90,9 @@ const loadLocalMessages = async ({ id, locale, fallbackLocales, namespaces, root
|
|
|
88
90
|
// ---------------------------------------------------------------------------
|
|
89
91
|
// Cache write (explicitly permitted)
|
|
90
92
|
// ---------------------------------------------------------------------------
|
|
91
|
-
if (
|
|
93
|
+
if (allowCacheWrite && isProd) {
|
|
92
94
|
if (cacheKey && messages) {
|
|
93
|
-
await pool?.set(cacheKey, messages
|
|
95
|
+
await pool?.set(cacheKey, messages);
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
// Final success log with resolved locale and timing
|
|
@@ -18,7 +18,7 @@ const readLocaleMessages = async ({ locale, namespaces, rootDir = "messages", li
|
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
19
19
|
const fileEntries = await collectFileEntries({
|
|
20
20
|
namespaces,
|
|
21
|
-
rootDir: path.resolve(
|
|
21
|
+
rootDir: path.resolve(rootDir, locale),
|
|
22
22
|
limit,
|
|
23
23
|
exts,
|
|
24
24
|
loggerOptions,
|
|
@@ -16,8 +16,8 @@ import { getTranslator } from '../../../server/helpers/get-translator.js';
|
|
|
16
16
|
*
|
|
17
17
|
* The resolved routing state is exposed via response headers.
|
|
18
18
|
*
|
|
19
|
-
* - Convenience routing shortcuts are also bound to the request for downstream consumption.
|
|
20
19
|
* - Acts as the bootstrap entry where cache writes are permitted.
|
|
20
|
+
* - Convenience routing shortcuts are also bound to the request for downstream consumption.
|
|
21
21
|
*
|
|
22
22
|
* @platform Express
|
|
23
23
|
*/
|
|
@@ -26,14 +26,18 @@ function createIntor(config, options) {
|
|
|
26
26
|
// locale from accept-language header
|
|
27
27
|
const acceptLanguage = req.headers["accept-language"];
|
|
28
28
|
const localeFromAcceptLanguage = getLocaleFromAcceptLanguage(config, acceptLanguage);
|
|
29
|
-
//
|
|
30
|
-
|
|
29
|
+
// ----------------------------------------------------------
|
|
30
|
+
// Resolve inbound routing decision (pure computation)
|
|
31
|
+
// ----------------------------------------------------------
|
|
32
|
+
const { locale, localeSource, pathname } = await resolveInbound(config, req.path, false, {
|
|
31
33
|
host: req.hostname,
|
|
32
34
|
query: normalizeQuery(req.query),
|
|
33
35
|
cookie: req.cookies?.[config.cookie.name],
|
|
34
36
|
detected: localeFromAcceptLanguage || config.defaultLocale,
|
|
35
37
|
});
|
|
36
|
-
//
|
|
38
|
+
// --------------------------------------------------
|
|
39
|
+
// Attach routing metadata to response headers
|
|
40
|
+
// --------------------------------------------------
|
|
37
41
|
req.headers[INTOR_HEADERS.LOCALE] = locale;
|
|
38
42
|
req.headers[INTOR_HEADERS.LOCALE_SOURCE] = localeSource;
|
|
39
43
|
req.headers[INTOR_HEADERS.PATHNAME] = pathname;
|
|
@@ -13,8 +13,8 @@ class IntorError extends Error {
|
|
|
13
13
|
var IntorErrorCode;
|
|
14
14
|
(function (IntorErrorCode) {
|
|
15
15
|
// config
|
|
16
|
+
IntorErrorCode["INVALID_CONFIG_ID"] = "INTOR_INVALID_CONFIG_ID";
|
|
16
17
|
IntorErrorCode["MISSING_SUPPORTED_LOCALES"] = "INTOR_MISSING_SUPPORTED_LOCALES";
|
|
17
|
-
IntorErrorCode["MISSING_DEFAULT_LOCALE"] = "INTOR_MISSING_DEFAULT_LOCALE";
|
|
18
18
|
IntorErrorCode["UNSUPPORTED_DEFAULT_LOCALE"] = "INTOR_UNSUPPORTED_DEFAULT_LOCALE";
|
|
19
19
|
// runtime
|
|
20
20
|
IntorErrorCode["RUNTIME_NOT_INITIALIZED"] = "INTOR_RUNTIME_NOT_INITIALIZED";
|
|
@@ -16,25 +16,26 @@ import { resolvePathname } from './resolve-pathname/resolve-pathname.js';
|
|
|
16
16
|
*
|
|
17
17
|
* No side effects. No navigation.
|
|
18
18
|
*/
|
|
19
|
-
function resolveInbound(config, rawPathname, localeInputs) {
|
|
19
|
+
async function resolveInbound(config, rawPathname, hasRedirected, localeInputs) {
|
|
20
20
|
const { host, query, cookie, detected } = localeInputs;
|
|
21
|
-
//
|
|
21
|
+
// ------------------------------------------------------
|
|
22
22
|
// Resolve locale from inbound inputs
|
|
23
|
-
//
|
|
23
|
+
// ------------------------------------------------------
|
|
24
|
+
const pathLocale = getLocaleFromPathname(config, rawPathname);
|
|
24
25
|
const { locale, localeSource } = resolveLocale(config, {
|
|
25
|
-
path: { locale:
|
|
26
|
+
path: { locale: pathLocale },
|
|
26
27
|
host: { locale: getLocaleFromHost(config, host) },
|
|
27
28
|
query: { locale: getLocaleFromQuery(config, query) },
|
|
28
29
|
cookie: { locale: cookie },
|
|
29
30
|
detected: { locale: detected },
|
|
30
31
|
});
|
|
31
|
-
//
|
|
32
|
+
// ------------------------------------------------------
|
|
32
33
|
// Resolve localized pathname and redirect requirement
|
|
33
|
-
//
|
|
34
|
+
// ------------------------------------------------------
|
|
34
35
|
const { pathname, shouldRedirect } = resolvePathname(config, rawPathname, {
|
|
35
36
|
locale,
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
hasPathLocale: !!pathLocale,
|
|
38
|
+
hasPersisted: !!cookie});
|
|
38
39
|
return {
|
|
39
40
|
locale,
|
|
40
41
|
localeSource,
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Resolves the active locale from inbound routing configuration.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* matching locale found in the provided context.
|
|
7
|
-
*
|
|
8
|
-
* - If no configured source yields a locale, the detected
|
|
9
|
-
* locale is used as a guaranteed fallback.
|
|
10
|
-
*
|
|
11
|
-
* - The returned locale represents the single source of truth
|
|
12
|
-
* for the remainder of the routing flow.
|
|
4
|
+
* The first matching locale from the configured sources is used,
|
|
5
|
+
* with the detected locale as a guaranteed fallback.
|
|
13
6
|
*/
|
|
14
7
|
function resolveLocale(config, context) {
|
|
15
|
-
const {
|
|
16
|
-
for (const source of
|
|
8
|
+
const { localeSources } = config.routing.inbound;
|
|
9
|
+
for (const source of localeSources) {
|
|
17
10
|
const locale = context[source]?.locale;
|
|
18
11
|
if (!locale)
|
|
19
12
|
continue;
|