intor 2.3.10 → 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.
Files changed (90) hide show
  1. package/dist/core/src/config/constants/routing.js +11 -15
  2. package/dist/core/src/config/define-intor-config.js +16 -16
  3. package/dist/core/src/config/resolvers/resolve-fallback-locales.js +45 -35
  4. package/dist/core/src/config/resolvers/resolve-routing-options.js +56 -36
  5. package/dist/core/src/config/validators/validate-default-locale.js +5 -19
  6. package/dist/core/src/config/validators/validate-id.js +21 -0
  7. package/dist/core/src/config/validators/validate-supported-locales.js +6 -16
  8. package/dist/core/src/core/error/intor-error.js +1 -1
  9. package/dist/core/src/routing/pathname/locale-prefix-pathname.js +11 -11
  10. package/dist/core/src/server/helpers/local-messages-from-url.js +0 -1
  11. package/dist/core/src/server/messages/load-local-messages/load-local-messages.js +13 -12
  12. package/dist/core/src/server/messages/load-local-messages/read-locale-messages/read-locale-messages.js +1 -1
  13. package/dist/core/src/server/messages/load-messages.js +0 -1
  14. package/dist/express/src/adapters/express/middleware/create-intor.js +8 -4
  15. package/dist/express/src/core/constants/headers.js +1 -2
  16. package/dist/express/src/core/error/intor-error.js +1 -1
  17. package/dist/express/src/routing/inbound/resolve-inbound.js +9 -8
  18. package/dist/express/src/routing/inbound/resolve-locale/resolve-locale.js +5 -12
  19. package/dist/express/src/routing/inbound/resolve-pathname/resolve-pathname.js +9 -19
  20. package/dist/express/src/routing/inbound/resolve-pathname/strategies/all.js +18 -13
  21. package/dist/express/src/routing/inbound/resolve-pathname/strategies/except-default.js +20 -24
  22. package/dist/express/src/routing/inbound/resolve-pathname/strategies/none.js +1 -7
  23. package/dist/express/src/routing/locale/get-locale-from-query.js +2 -2
  24. package/dist/express/src/routing/pathname/locale-prefix-pathname.js +11 -11
  25. package/dist/express/src/server/messages/load-local-messages/load-local-messages.js +13 -12
  26. package/dist/express/src/server/messages/load-local-messages/read-locale-messages/read-locale-messages.js +1 -1
  27. package/dist/express/src/server/messages/load-messages.js +0 -1
  28. package/dist/next/src/adapters/next/proxy/intor-proxy.js +17 -6
  29. package/dist/next/src/core/constants/headers.js +1 -0
  30. package/dist/next/src/core/error/intor-error.js +1 -1
  31. package/dist/next/src/policies/shoud-full-reload.js +1 -1
  32. package/dist/next/src/routing/inbound/resolve-inbound.js +10 -7
  33. package/dist/next/src/routing/inbound/resolve-locale/resolve-locale.js +5 -12
  34. package/dist/next/src/routing/inbound/resolve-pathname/resolve-pathname.js +9 -19
  35. package/dist/next/src/routing/inbound/resolve-pathname/strategies/all.js +24 -13
  36. package/dist/next/src/routing/inbound/resolve-pathname/strategies/except-default.js +25 -23
  37. package/dist/next/src/routing/inbound/resolve-pathname/strategies/none.js +1 -7
  38. package/dist/next/src/routing/locale/get-locale-from-query.js +2 -2
  39. package/dist/next/src/routing/navigation/derive-target.js +11 -8
  40. package/dist/next/src/routing/navigation/utils/derive-host-destination.js +3 -3
  41. package/dist/next/src/routing/navigation/utils/derive-query-destination.js +2 -2
  42. package/dist/next/src/routing/pathname/locale-prefix-pathname.js +11 -11
  43. package/dist/next/src/server/messages/load-local-messages/load-local-messages.js +13 -12
  44. package/dist/next/src/server/messages/load-local-messages/read-locale-messages/read-locale-messages.js +1 -1
  45. package/dist/next/src/server/messages/load-messages.js +0 -1
  46. package/dist/react/src/client/react/navigation/use-execute-navigation.js +15 -3
  47. package/dist/react/src/client/react/provider/effects/use-locale-effects.js +5 -2
  48. package/dist/react/src/core/error/intor-error.js +1 -1
  49. package/dist/react/src/policies/shoud-full-reload.js +1 -1
  50. package/dist/react/src/policies/should-sync-locale.js +8 -0
  51. package/dist/react/src/routing/navigation/derive-target.js +11 -8
  52. package/dist/react/src/routing/navigation/utils/derive-host-destination.js +3 -3
  53. package/dist/react/src/routing/navigation/utils/derive-query-destination.js +2 -2
  54. package/dist/react/src/routing/pathname/locale-prefix-pathname.js +11 -11
  55. package/dist/svelte/src/client/svelte/runtime/effects/locale-effects.js +4 -1
  56. package/dist/svelte/src/core/error/intor-error.js +1 -1
  57. package/dist/types/src/adapters/express/middleware/create-intor.d.ts +1 -1
  58. package/dist/types/src/adapters/next/proxy/intor-proxy.d.ts +2 -0
  59. package/dist/types/src/client/react/navigation/use-execute-navigation.d.ts +1 -1
  60. package/dist/types/src/config/define-intor-config.d.ts +6 -12
  61. package/dist/types/src/config/resolvers/resolve-fallback-locales.d.ts +4 -16
  62. package/dist/types/src/config/resolvers/resolve-routing-options.d.ts +9 -2
  63. package/dist/types/src/config/types/index.d.ts +1 -1
  64. package/dist/types/src/config/types/intor-config.d.ts +16 -3
  65. package/dist/types/src/config/types/routing.d.ts +60 -73
  66. package/dist/types/src/config/types/translator.d.ts +2 -2
  67. package/dist/types/src/config/validators/validate-default-locale.d.ts +3 -9
  68. package/dist/types/src/config/validators/validate-id.d.ts +6 -0
  69. package/dist/types/src/config/validators/validate-supported-locales.d.ts +3 -8
  70. package/dist/types/src/core/constants/headers.d.ts +1 -0
  71. package/dist/types/src/core/error/intor-error.d.ts +1 -1
  72. package/dist/types/src/core/index.d.ts +1 -1
  73. package/dist/types/src/core/types/index.d.ts +1 -1
  74. package/dist/types/src/core/types/routing.d.ts +5 -6
  75. package/dist/types/src/core/types/translator-instance.d.ts +8 -3
  76. package/dist/types/src/policies/index.d.ts +2 -0
  77. package/dist/types/src/policies/should-sync-locale.d.ts +4 -0
  78. package/dist/types/src/routing/inbound/resolve-inbound.d.ts +2 -2
  79. package/dist/types/src/routing/inbound/resolve-locale/resolve-locale.d.ts +3 -10
  80. package/dist/types/src/routing/inbound/resolve-pathname/resolve-pathname.d.ts +3 -13
  81. package/dist/types/src/routing/inbound/resolve-pathname/strategies/all.d.ts +1 -1
  82. package/dist/types/src/routing/inbound/resolve-pathname/strategies/except-default.d.ts +1 -1
  83. package/dist/types/src/routing/inbound/resolve-pathname/strategies/none.d.ts +2 -2
  84. package/dist/types/src/routing/inbound/resolve-pathname/types.d.ts +7 -4
  85. package/dist/types/src/routing/pathname/locale-prefix-pathname.d.ts +3 -3
  86. package/dist/types/src/server/messages/load-local-messages/load-local-messages.d.ts +5 -2
  87. package/dist/types/src/server/messages/load-local-messages/types.d.ts +1 -2
  88. package/dist/vue/src/client/vue/provider/effects/use-locale-effects.js +4 -1
  89. package/dist/vue/src/core/error/intor-error.js +1 -1
  90. package/package.json +1 -1
@@ -1,22 +1,18 @@
1
1
  // Default routing options
2
2
  const DEFAULT_ROUTING_OPTIONS = {
3
- locale: {
4
- sources: ["path", "query", "cookie", "detected"],
5
- query: { key: "locale" },
6
- },
7
- navigation: {
8
- carrier: "path",
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
- firstVisit: {
14
- localeSource: "browser",
15
- redirect: true,
16
- persist: true,
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 and resolves an Intor configuration.
10
+ * Defines the canonical Intor configuration.
10
11
  *
11
- * This is the canonical entry point for transforming a raw configuration
12
- * into a fully validated and resolved Intor config.
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
- * Responsibilities:
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
- const supportedLocales = validateSupportedLocales(config);
27
- const defaultLocale = validateDefaultLocale(config, supportedLocales);
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
- const fallbackLocales = resolveFallbackLocales(config, supportedLocales);
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 and normalizes fallback locale mappings.
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
- * Notes:
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, supportedLocales) => {
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 supportedSet = new Set(supportedLocales);
24
- const validMap = {}; // Collected valid fallbacks
25
- const invalidFallbackMap = new Map(); // Track invalid ones
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
- const fallbackArray = Array.isArray(fallbacks) ? fallbacks : [];
28
- // Valid: in supported list, matches default, or literal "default"
29
- const validFallbacks = fallbackArray.filter((fallback) => fallback === "default" ||
30
- supportedSet.has(fallback) ||
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
- if (validFallbacks.length > 0) {
40
- validMap[locale] = validFallbacks;
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
- // Log out invalid fallback locales
44
- for (const [locale, invalids] of invalidFallbackMap.entries()) {
45
- console.warn(`Invalid fallback locales for "${locale}"`, { invalids: [...invalids] }, { scope: "resolveFallbackLocales" });
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 validMap;
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 { normalizePathname } from '../../core/utils/normalizers/normalize-pathname.js';
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
- const resolveRoutingOptions = (routing = {}) => {
10
- return {
11
- ...DEFAULT_ROUTING_OPTIONS,
12
- ...routing,
13
- locale: {
14
- ...DEFAULT_ROUTING_OPTIONS.locale,
15
- ...routing.locale,
16
- query: {
17
- ...DEFAULT_ROUTING_OPTIONS.locale.query,
18
- ...routing.locale?.query,
19
- },
20
- },
21
- navigation: {
22
- ...DEFAULT_ROUTING_OPTIONS.navigation,
23
- ...routing.navigation,
24
- path: {
25
- ...DEFAULT_ROUTING_OPTIONS.navigation.path,
26
- ...routing.navigation?.path,
27
- },
28
- query: {
29
- ...DEFAULT_ROUTING_OPTIONS.navigation.query,
30
- ...routing.navigation?.query,
31
- },
32
- host: {
33
- ...DEFAULT_ROUTING_OPTIONS.navigation.host,
34
- ...routing.navigation?.host,
35
- },
36
- },
37
- firstVisit: {
38
- ...DEFAULT_ROUTING_OPTIONS.firstVisit,
39
- ...routing.firstVisit,
40
- },
41
- basePath: normalizePathname(routing?.basePath || ""),
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 default locale.
6
+ * Validates that the configured defaultLocale is supported.
7
7
  *
8
- * - Ensures that `defaultLocale` is explicitly defined.
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 = (config, supportedLocales) => {
15
- const { id, defaultLocale } = config;
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: `The defaultLocale "${defaultLocale}" is not included in the supportedLocales.`,
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 and resolves the list of supported locales.
6
+ * Validates that supportedLocales is provided and non-empty.
7
7
  *
8
- * - Ensures `supportedLocales` is explicitly provided when a loader is used.
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 = (config) => {
16
- const { id, supportedLocales } = config;
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" is required when using message loaders.
24
- Please specify all supported locales explicitly.`,
15
+ message: `"supportedLocales" must be specified.`,
25
16
  });
26
17
  }
27
- // Return supportedLocales or infer from message keys
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 routing prefix strategy by resolving the locale placeholder.
8
+ * Applies the configured locale prefix behavior to a standardized pathname.
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
- * // config.routing.prefix: "all"
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.prefix: "none"
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 { prefix } = config.routing.navigation.path;
23
- if (prefix !== "none" && !locale) {
24
- throw new Error('No locale when using prefix "all", "except-default"');
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
- // prefix: "all"
27
- if (prefix === "all") {
26
+ // localePrefix: "all"
27
+ if (localePrefix === "all") {
28
28
  return normalizePathname(standardizedPathname.replaceAll(PREFIX_PLACEHOLDER, locale));
29
29
  }
30
- // prefix: "except-default"
31
- if (prefix === "except-default") {
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
- // prefix: "none"
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,21 @@ import { readLocaleMessages } from './read-locale-messages/read-locale-messages.
13
13
  * It coordinates:
14
14
  *
15
15
  * - Locale resolution with fallbacks
16
- * - Cache read / write behavior
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(), cacheOptions, allowCacheWrite = false, loggerOptions, }) => {
24
+ const loadLocalMessages = async ({ id, locale, fallbackLocales, namespaces, rootDir = "messages", concurrency = 10, readOptions, pool = getGlobalMessagesPool(), allowCacheWrite = false, loggerOptions, }) => {
22
25
  const baseLogger = getLogger(loggerOptions);
23
26
  const logger = baseLogger.child({ scope: "load-local-messages" });
24
27
  const start = performance.now();
25
28
  logger.debug("Loading local messages.", {
26
29
  rootDir,
27
- resolvedRootDir: path.resolve(process.cwd(), rootDir),
30
+ resolvedRootDir: path.resolve(rootDir),
28
31
  });
29
32
  // ---------------------------------------------------------------------------
30
33
  // Cache key resolution
@@ -40,13 +43,11 @@ const loadLocalMessages = async ({ id, locale, fallbackLocales, namespaces, root
40
43
  // ---------------------------------------------------------------------------
41
44
  // Cache read
42
45
  // ---------------------------------------------------------------------------
43
- if (cacheOptions.enabled) {
44
- if (cacheKey) {
45
- const cached = await pool?.get(cacheKey);
46
- if (cached) {
47
- logger.debug("Messages cache hit.", { key: cacheKey });
48
- return cached;
49
- }
46
+ if (cacheKey) {
47
+ const cached = await pool?.get(cacheKey);
48
+ if (cached) {
49
+ logger.debug("Messages cache hit.", { key: cacheKey });
50
+ return cached;
50
51
  }
51
52
  }
52
53
  // ---------------------------------------------------------------------------
@@ -88,9 +89,9 @@ const loadLocalMessages = async ({ id, locale, fallbackLocales, namespaces, root
88
89
  // ---------------------------------------------------------------------------
89
90
  // Cache write (explicitly permitted)
90
91
  // ---------------------------------------------------------------------------
91
- if (cacheOptions.enabled && allowCacheWrite) {
92
+ if (allowCacheWrite) {
92
93
  if (cacheKey && messages) {
93
- await pool?.set(cacheKey, messages, cacheOptions.ttl);
94
+ await pool?.set(cacheKey, messages);
94
95
  }
95
96
  }
96
97
  // 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(process.cwd(), rootDir, locale),
21
+ rootDir: path.resolve(rootDir, locale),
22
22
  limit,
23
23
  exts,
24
24
  loggerOptions,
@@ -52,7 +52,6 @@ const loadMessages = async ({ config, locale, readOptions, allowCacheWrite = fal
52
52
  rootDir: loader.rootDir,
53
53
  concurrency: loader.concurrency,
54
54
  readOptions,
55
- cacheOptions: config.cache,
56
55
  allowCacheWrite,
57
56
  loggerOptions: config.logger,
58
57
  });
@@ -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
- // Resolve routing decision (locale + pathname)
30
- const { locale, localeSource, pathname } = resolveInbound(config, req.path, {
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
- // Attach resolved routing metadata to response headers
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;
@@ -1,7 +1,6 @@
1
1
  const INTOR_HEADERS = {
2
2
  LOCALE: "x-intor-locale",
3
3
  LOCALE_SOURCE: "x-intor-locale-source",
4
- PATHNAME: "x-intor-pathname",
5
- };
4
+ PATHNAME: "x-intor-pathname"};
6
5
 
7
6
  export { INTOR_HEADERS };
@@ -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: getLocaleFromPathname(config, rawPathname) },
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
- localeSource,
37
- });
37
+ hasPathLocale: !!pathLocale,
38
+ hasPersisted: !!cookie});
38
39
  return {
39
40
  locale,
40
41
  localeSource,
@@ -1,19 +1,12 @@
1
1
  /**
2
- * Resolve the final locale based on routing configuration.
2
+ * Resolves the active locale from inbound routing configuration.
3
3
  *
4
- * - This function iterates through locale sources defined in
5
- * `config.routing.locale.sources` and returns the first
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 { sources } = config.routing.locale;
16
- for (const source of sources) {
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;