intor 2.3.9 → 2.3.11
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 +0 -6
- package/dist/core/export/index.js +2 -0
- package/dist/core/export/server/index.js +0 -2
- 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/core/messages/load-remote-messages/load-remote-messages.js +24 -14
- package/dist/core/src/core/messages/merge-messages.js +33 -0
- package/dist/core/src/core/utils/deep-merge.js +35 -19
- package/dist/core/src/core/utils/resolve-loader-options.js +9 -0
- package/dist/core/src/routing/pathname/locale-prefix-pathname.js +11 -11
- package/dist/core/src/server/helpers/local-messages-from-url.js +1 -1
- package/dist/core/src/server/messages/load-local-messages/load-local-messages.js +14 -13
- 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 +7 -6
- package/dist/core/src/server/translator/create-translator.js +5 -5
- 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/core/messages/load-remote-messages/load-remote-messages.js +24 -14
- package/dist/express/src/core/messages/merge-messages.js +33 -0
- package/dist/express/src/core/utils/deep-merge.js +35 -19
- 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 -13
- 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 +6 -5
- package/dist/express/src/server/translator/create-translator.js +5 -5
- package/dist/next/src/adapters/next/navigation/link.js +9 -10
- package/dist/next/src/adapters/next/navigation/redirect.js +7 -11
- package/dist/next/src/adapters/next/navigation/use-router.js +18 -14
- 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/core/messages/load-remote-messages/load-remote-messages.js +24 -14
- package/dist/next/src/core/messages/merge-messages.js +33 -0
- package/dist/next/src/core/utils/deep-merge.js +35 -19
- package/dist/next/src/core/utils/resolve-loader-options.js +9 -0
- package/dist/next/src/policies/shoud-full-reload.js +14 -0
- 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/decide-strategy.js +17 -0
- package/dist/next/src/routing/navigation/derive-target.js +58 -0
- package/dist/next/src/routing/navigation/resolve-navigation.js +26 -0
- package/dist/next/src/routing/navigation/utils/derive-host-destination.js +13 -0
- package/dist/next/src/routing/navigation/utils/derive-query-destination.js +11 -0
- 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 -13
- 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 +7 -6
- package/dist/next/src/server/translator/create-translator.js +5 -5
- package/dist/react/export/react/index.js +2 -1
- package/dist/react/src/client/react/helpers/{use-load-messages.js → use-runtime-state.js} +11 -10
- package/dist/react/src/client/react/navigation/use-execute-navigation.js +41 -0
- package/dist/react/src/client/react/navigation/use-resolve-navigation.js +4 -4
- package/dist/react/src/client/react/provider/effects/use-locale-effects.js +5 -2
- package/dist/react/src/client/shared/messages/create-refetch-messages.js +6 -6
- package/dist/react/src/core/error/intor-error.js +1 -1
- package/dist/react/src/core/messages/load-remote-messages/load-remote-messages.js +24 -14
- package/dist/react/src/core/messages/merge-messages.js +33 -0
- package/dist/react/src/core/utils/deep-merge.js +35 -19
- 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 +15 -15
- package/dist/react/src/routing/navigation/resolve-navigation.js +3 -2
- 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/export/svelte/index.js +1 -1
- package/dist/svelte/src/client/shared/messages/create-refetch-messages.js +6 -6
- package/dist/svelte/src/client/svelte/helpers/{create-messages.js → create-runtime-state.js} +9 -8
- 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/svelte/src/core/messages/load-remote-messages/load-remote-messages.js +24 -14
- package/dist/svelte/src/core/messages/merge-messages.js +33 -0
- package/dist/svelte/src/core/utils/deep-merge.js +35 -19
- package/dist/types/export/index.d.ts +1 -1
- package/dist/types/export/react/index.d.ts +1 -1
- package/dist/types/export/svelte/index.d.ts +1 -1
- package/dist/types/export/vue/index.d.ts +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/helpers/index.d.ts +1 -1
- package/dist/types/src/client/react/helpers/use-runtime-state.d.ts +10 -0
- package/dist/types/src/client/react/index.d.ts +2 -2
- package/dist/types/src/client/react/navigation/index.d.ts +1 -0
- package/dist/types/src/client/react/navigation/use-execute-navigation.d.ts +5 -0
- package/dist/types/src/client/react/navigation/use-resolve-navigation.d.ts +4 -6
- package/dist/types/src/client/react/provider/types.d.ts +6 -3
- package/dist/types/src/client/shared/types/index.d.ts +1 -1
- package/dist/types/src/client/shared/types/runtime-state.d.ts +13 -0
- package/dist/types/src/client/svelte/helpers/create-runtime-state.d.ts +11 -0
- package/dist/types/src/client/svelte/helpers/index.d.ts +1 -1
- package/dist/types/src/client/svelte/index.d.ts +1 -1
- package/dist/types/src/client/svelte/runtime/types.d.ts +6 -3
- package/dist/types/src/client/vue/helpers/index.d.ts +1 -1
- package/dist/types/src/client/vue/helpers/use-runtime-state.d.ts +10 -0
- package/dist/types/src/client/vue/index.d.ts +1 -1
- package/dist/types/src/client/vue/provider/resolver/resolve-runtime.d.ts +10 -0
- package/dist/types/src/client/vue/provider/types.d.ts +8 -3
- 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 +3 -3
- package/dist/types/src/core/messages/index.d.ts +1 -0
- package/dist/types/src/core/messages/load-remote-messages/load-remote-messages.d.ts +5 -5
- package/dist/types/src/core/messages/load-remote-messages/types.d.ts +1 -0
- package/dist/types/src/core/messages/merge-messages.d.ts +19 -0
- package/dist/types/src/core/types/bootstrap.d.ts +13 -0
- package/dist/types/src/core/types/index.d.ts +2 -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/core/utils/deep-merge.d.ts +20 -6
- package/dist/types/src/core/utils/index.d.ts +1 -2
- 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/navigation/derive-target.d.ts +1 -1
- package/dist/types/src/routing/navigation/resolve-navigation.d.ts +4 -1
- package/dist/types/src/routing/pathname/locale-prefix-pathname.d.ts +3 -3
- package/dist/types/src/server/intor/index.d.ts +1 -1
- package/dist/types/src/server/intor/intor.d.ts +2 -2
- package/dist/types/src/server/intor/types.d.ts +3 -5
- 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 +2 -2
- package/dist/vue/export/vue/index.js +1 -1
- package/dist/vue/src/client/shared/messages/create-refetch-messages.js +6 -6
- package/dist/vue/src/client/vue/helpers/{use-load-messages.js → use-runtime-state.js} +12 -8
- package/dist/vue/src/client/vue/provider/effects/use-locale-effects.js +4 -1
- package/dist/vue/src/client/vue/provider/intor-provider.js +8 -6
- package/dist/vue/src/client/vue/provider/resolver/resolve-runtime.js +32 -0
- package/dist/vue/src/core/error/intor-error.js +1 -1
- package/dist/vue/src/core/messages/load-remote-messages/load-remote-messages.js +24 -14
- package/dist/vue/src/core/messages/merge-messages.js +33 -0
- package/dist/vue/src/core/utils/deep-merge.js +35 -19
- package/package.json +2 -2
- package/dist/types/src/client/react/helpers/use-load-messages.d.ts +0 -11
- package/dist/types/src/client/shared/types/bootstrap.d.ts +0 -16
- package/dist/types/src/client/svelte/helpers/create-messages.d.ts +0 -12
- package/dist/types/src/client/vue/helpers/use-load-messages.d.ts +0 -12
- /package/dist/next/src/{core → routing/navigation}/utils/is-external-destination.js +0 -0
- /package/dist/react/src/{core → routing/navigation}/utils/is-external-destination.js +0 -0
- /package/dist/types/src/{core → routing/navigation}/utils/is-external-destination.d.ts +0 -0
package/README.md
CHANGED
|
@@ -15,9 +15,3 @@ Fast to start, easy to extend, and free from the usual i18n heaviness.
|
|
|
15
15
|
[](LICENSE)
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
|
-
|
|
19
|
-
<div align="center">
|
|
20
|
-
|
|
21
|
-
#### 🍳 Cooking the Intor v2 docs, crafting them to perfection...
|
|
22
|
-
|
|
23
|
-
</div>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export { PREFIX_PLACEHOLDER } from '../src/core/constants/prefix-placeholder.js';
|
|
2
2
|
export { IntorError, IntorErrorCode } from '../src/core/error/intor-error.js';
|
|
3
3
|
export { deepMerge } from '../src/core/utils/deep-merge.js';
|
|
4
|
+
export { resolveLoaderOptions } from '../src/core/utils/resolve-loader-options.js';
|
|
4
5
|
import 'logry';
|
|
5
6
|
export { clearLoggerPool } from '../src/core/logger/global-logger-pool.js';
|
|
6
7
|
export { isValidMessages } from '../src/core/messages/utils/is-valid-messages.js';
|
|
7
8
|
export { clearMessagesPool, setGlobalMessagesPool } from '../src/core/messages/global-messages-pool.js';
|
|
9
|
+
export { mergeMessages } from '../src/core/messages/merge-messages.js';
|
|
8
10
|
export { defineIntorConfig } from '../src/config/define-intor-config.js';
|
|
9
11
|
import '../src/config/constants/cookie.js';
|
|
10
12
|
import '../src/config/constants/cache.js';
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export { intor } from '../../src/server/intor/intor.js';
|
|
2
2
|
export { loadMessages } from '../../src/server/messages/load-messages.js';
|
|
3
3
|
import 'intor-translator';
|
|
4
|
-
import '../../src/core/error/intor-error.js';
|
|
5
4
|
import 'logry';
|
|
6
|
-
import 'keyv';
|
|
7
5
|
export { getTranslator } from '../../src/server/helpers/get-translator.js';
|
|
8
6
|
export { loadMessagesFromUrl } from '../../src/server/helpers/local-messages-from-url.js';
|
|
@@ -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,16 +5,16 @@ import { fetchLocaleMessages } from './fetch-locale-messages/fetch-locale-messag
|
|
|
5
5
|
/**
|
|
6
6
|
* Load locale messages from a remote API.
|
|
7
7
|
*
|
|
8
|
-
* This function
|
|
9
|
-
* It
|
|
8
|
+
* This function serves as the orchestration layer for remote message loading.
|
|
9
|
+
* It coordinates:
|
|
10
10
|
*
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
11
|
+
* - Locale resolution with fallbacks
|
|
12
|
+
* - Cache read / write behavior
|
|
13
13
|
* - Respecting abort signals across the entire async flow
|
|
14
14
|
*
|
|
15
15
|
* Network fetching and data validation are delegated to lower-level utilities.
|
|
16
16
|
*/
|
|
17
|
-
const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, rootDir, url, headers, signal, pool, cacheOptions, allowCacheWrite = false, loggerOptions, }) => {
|
|
17
|
+
const loadRemoteMessages = async ({ id, locale, fallbackLocales, namespaces, rootDir, url, headers, signal, pool, cacheOptions, allowCacheWrite = false, loggerOptions, }) => {
|
|
18
18
|
const baseLogger = getLogger(loggerOptions);
|
|
19
19
|
const logger = baseLogger.child({ scope: "load-remote-messages" });
|
|
20
20
|
// Abort early if the request has already been cancelled
|
|
@@ -24,16 +24,20 @@ const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, rootDir
|
|
|
24
24
|
}
|
|
25
25
|
const start = performance.now();
|
|
26
26
|
logger.debug("Loading remote messages.", { url });
|
|
27
|
-
//
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Cache key resolution
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
28
30
|
const cacheKey = normalizeCacheKey([
|
|
29
|
-
|
|
31
|
+
id,
|
|
30
32
|
"loaderType:remote",
|
|
31
33
|
rootDir,
|
|
32
34
|
locale,
|
|
33
|
-
(fallbackLocales
|
|
34
|
-
(namespaces
|
|
35
|
+
(fallbackLocales || []).toSorted().join(","),
|
|
36
|
+
(namespaces || []).toSorted().join(","),
|
|
35
37
|
]);
|
|
36
|
-
//
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Cache read
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
37
41
|
if (cacheOptions.enabled && cacheKey) {
|
|
38
42
|
const cached = await pool?.get(cacheKey);
|
|
39
43
|
if (signal?.aborted) {
|
|
@@ -45,9 +49,11 @@ const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, rootDir
|
|
|
45
49
|
return cached;
|
|
46
50
|
}
|
|
47
51
|
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Resolve locale messages with ordered fallback strategy
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
48
55
|
const candidateLocales = [locale, ...(fallbackLocales || [])];
|
|
49
56
|
let messages;
|
|
50
|
-
// Try each candidate locale in order and stop at the first successful result
|
|
51
57
|
for (let i = 0; i < candidateLocales.length; i++) {
|
|
52
58
|
const candidateLocale = candidateLocales[i];
|
|
53
59
|
const isLast = i === candidateLocales.length - 1;
|
|
@@ -87,13 +93,17 @@ const loadRemoteMessages = async ({ locale, fallbackLocales, namespaces, rootDir
|
|
|
87
93
|
});
|
|
88
94
|
}
|
|
89
95
|
}
|
|
90
|
-
//
|
|
91
|
-
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Cache write (explicitly permitted)
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
if (cacheOptions.enabled && allowCacheWrite) {
|
|
92
100
|
if (signal?.aborted) {
|
|
93
101
|
logger.debug("Remote message loading aborted before cache write.");
|
|
94
102
|
return;
|
|
95
103
|
}
|
|
96
|
-
|
|
104
|
+
if (cacheKey && messages) {
|
|
105
|
+
await pool?.set(cacheKey, messages, cacheOptions.ttl);
|
|
106
|
+
}
|
|
97
107
|
}
|
|
98
108
|
// Final success log with resolved locale and timing
|
|
99
109
|
if (messages) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getLogger } from '../logger/get-logger.js';
|
|
2
|
+
import { deepMerge } from '../utils/deep-merge.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Merge locale-specific messages with runtime overrides.
|
|
6
|
+
*
|
|
7
|
+
* - Only merges messages under the given locale
|
|
8
|
+
* - Emits debug logs for add / override events
|
|
9
|
+
*/
|
|
10
|
+
function mergeMessages(a, b, { config, locale, onEvent }) {
|
|
11
|
+
const baseLogger = getLogger({ ...config.logger, id: config.id });
|
|
12
|
+
const logger = baseLogger.child({ scope: "merge-messages" });
|
|
13
|
+
// Merge messages for the active locale only
|
|
14
|
+
const merged = deepMerge(a?.[locale] ?? {}, b?.[locale] ?? {}, {
|
|
15
|
+
onOverride: (event) => {
|
|
16
|
+
if (onEvent) {
|
|
17
|
+
onEvent(event);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const { kind, path, next, prev } = event;
|
|
21
|
+
if (kind === "add")
|
|
22
|
+
return;
|
|
23
|
+
logger.debug(`Override | ${locale}: "${path}"`, { prev, next });
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
// Preserve other locales, update only the target one
|
|
27
|
+
return {
|
|
28
|
+
...a,
|
|
29
|
+
[locale]: merged,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { mergeMessages };
|
|
@@ -1,28 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Deeply merges two objects.
|
|
2
|
+
* Deeply merges two plain objects.
|
|
3
3
|
*
|
|
4
|
-
* - Nested objects → merged recursively
|
|
5
|
-
* -
|
|
4
|
+
* - Nested plain objects → merged recursively
|
|
5
|
+
* - Arrays / primitives → `b` overwrites `a`
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Debug behavior (optional):
|
|
8
|
+
* - Emits override events via `onOverride`
|
|
9
|
+
* - Zero overhead when no options are provided
|
|
10
|
+
*
|
|
11
|
+
* This function always returns a new plain object.
|
|
8
12
|
*/
|
|
9
|
-
const deepMerge = (a = {}, b = {}) => {
|
|
13
|
+
const deepMerge = (a = {}, b = {}, options) => {
|
|
10
14
|
const result = { ...a };
|
|
15
|
+
const basePath = options?._path ?? [];
|
|
16
|
+
// Iterate only over b's own enumerable properties
|
|
11
17
|
for (const key in b) {
|
|
12
|
-
if (Object.prototype.hasOwnProperty.call(b, key))
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
if (!Object.prototype.hasOwnProperty.call(b, key))
|
|
19
|
+
continue;
|
|
20
|
+
const aValue = a[key];
|
|
21
|
+
const bValue = b[key];
|
|
22
|
+
const nextPath = [...basePath, key];
|
|
23
|
+
// Recursively merge when both sides are plain objects
|
|
24
|
+
if (aValue &&
|
|
25
|
+
bValue &&
|
|
26
|
+
typeof aValue === "object" &&
|
|
27
|
+
typeof bValue === "object" &&
|
|
28
|
+
!Array.isArray(aValue) &&
|
|
29
|
+
!Array.isArray(bValue)) {
|
|
30
|
+
result[key] = deepMerge(aValue, bValue, options ? { ...options, _path: nextPath } : undefined);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Emit override event only when debugging is enabled
|
|
34
|
+
const isAdd = aValue === undefined;
|
|
35
|
+
options?.onOverride?.({
|
|
36
|
+
path: nextPath.join("."),
|
|
37
|
+
prev: aValue,
|
|
38
|
+
next: bValue,
|
|
39
|
+
kind: isAdd ? "add" : "override",
|
|
40
|
+
});
|
|
41
|
+
result[key] = bValue;
|
|
26
42
|
}
|
|
27
43
|
}
|
|
28
44
|
return result;
|
|
@@ -18,6 +18,15 @@
|
|
|
18
18
|
* for the given runtime.
|
|
19
19
|
*/
|
|
20
20
|
const resolveLoaderOptions = (config, runtime) => {
|
|
21
|
+
// --- runtime: client ---
|
|
22
|
+
if (runtime === "client") {
|
|
23
|
+
const client = config.client?.loader;
|
|
24
|
+
if (client) {
|
|
25
|
+
// Client loader is always remote by design
|
|
26
|
+
return { type: "remote", ...client };
|
|
27
|
+
}
|
|
28
|
+
return config.server?.loader ?? config.loader;
|
|
29
|
+
}
|
|
21
30
|
// --- runtime: server ---
|
|
22
31
|
return config.server?.loader ?? config.loader;
|
|
23
32
|
};
|