@intlayer/core 8.12.1 → 8.12.2
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/cjs/localization/getLocaleLang.cjs.map +1 -1
- package/dist/cjs/localization/getLocalizedUrl.cjs +5 -0
- package/dist/cjs/localization/getLocalizedUrl.cjs.map +1 -1
- package/dist/cjs/localization/getPathWithoutLocale.cjs +5 -0
- package/dist/cjs/localization/getPathWithoutLocale.cjs.map +1 -1
- package/dist/cjs/localization/getPrefix.cjs.map +1 -1
- package/dist/cjs/utils/localeStorage.cjs +15 -4
- package/dist/cjs/utils/localeStorage.cjs.map +1 -1
- package/dist/esm/localization/getLocaleLang.mjs.map +1 -1
- package/dist/esm/localization/getLocalizedUrl.mjs +5 -0
- package/dist/esm/localization/getLocalizedUrl.mjs.map +1 -1
- package/dist/esm/localization/getPathWithoutLocale.mjs +5 -0
- package/dist/esm/localization/getPathWithoutLocale.mjs.map +1 -1
- package/dist/esm/localization/getPrefix.mjs.map +1 -1
- package/dist/esm/utils/localeStorage.mjs +15 -4
- package/dist/esm/utils/localeStorage.mjs.map +1 -1
- package/dist/types/interpreter/getPlural.d.ts +1 -1
- package/dist/types/localization/getLocaleLang.d.ts +2 -2
- package/dist/types/localization/getLocaleLang.d.ts.map +1 -1
- package/dist/types/localization/getLocalizedUrl.d.ts +7 -2
- package/dist/types/localization/getLocalizedUrl.d.ts.map +1 -1
- package/dist/types/localization/getPathWithoutLocale.d.ts +7 -2
- package/dist/types/localization/getPathWithoutLocale.d.ts.map +1 -1
- package/dist/types/localization/getPrefix.d.ts +31 -13
- package/dist/types/localization/getPrefix.d.ts.map +1 -1
- package/dist/types/utils/localeStorage.d.ts +2 -1
- package/dist/types/utils/localeStorage.d.ts.map +1 -1
- package/package.json +6 -6
- package/dist/types/@intlayer/core/dist/types/formatters/compact.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/formatters/currency.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/formatters/date.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/formatters/index.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/formatters/list.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/formatters/number.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/formatters/percentage.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/formatters/relativeTime.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/formatters/units.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/interpreter/getCondition.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/interpreter/getContent/deepTransform.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/interpreter/getContent/getContent.d.ts +0 -2
- package/dist/types/@intlayer/core/dist/types/interpreter/getContent/plugins.d.ts +0 -4
- package/dist/types/@intlayer/core/dist/types/interpreter/getDictionary.d.ts +0 -2
- package/dist/types/@intlayer/core/dist/types/interpreter/getEnumeration.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/interpreter/getIntlayer.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/interpreter/getNesting.d.ts +0 -2
- package/dist/types/@intlayer/core/dist/types/interpreter/getPlural.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/interpreter/getTranslation.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/interpreter/index.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/localization/generateSitemap.d.ts +0 -2
- package/dist/types/@intlayer/core/dist/types/localization/getBrowserLocale.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/localization/getHTMLTextDir.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/localization/getLocale.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/localization/getLocaleFromPath.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/localization/getLocaleLang.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/localization/getLocaleName.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/localization/getLocalizedUrl.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/localization/getMultilingualUrls.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/localization/getPathWithoutLocale.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/localization/getPrefix.d.ts +0 -3
- package/dist/types/@intlayer/core/dist/types/localization/index.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/localization/localeDetector.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/localization/localeMapper.d.ts +0 -2
- package/dist/types/@intlayer/core/dist/types/localization/localeResolver.d.ts +0 -2
- package/dist/types/@intlayer/core/dist/types/localization/rewriteUtils.d.ts +0 -3
- package/dist/types/@intlayer/core/dist/types/localization/validatePrefix.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/markdown/index.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/transpiler/condition/condition.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/transpiler/enumeration/enumeration.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/transpiler/file/file.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/transpiler/gender/gender.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/transpiler/html/html.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/transpiler/index.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/transpiler/insertion/insertion.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/transpiler/markdown/markdown.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/transpiler/nesting/nesting.d.ts +0 -2
- package/dist/types/@intlayer/core/dist/types/transpiler/plural/plural.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/transpiler/translation/translation.d.ts +0 -2
- package/dist/types/@intlayer/core/dist/types/utils/index.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/utils/intl.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/utils/isSameKeyPath.d.ts +0 -1
- package/dist/types/@intlayer/core/dist/types/utils/localeStorage.d.ts +0 -2
- package/dist/types/intlayer/dist/types/index.d.ts +0 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getLocaleLang.cjs","names":[],"sources":["../../../src/localization/getLocaleLang.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"getLocaleLang.cjs","names":[],"sources":["../../../src/localization/getLocaleLang.ts"],"sourcesContent":["import type {\n GetLocaleLang,\n LocalesValues,\n} from '@intlayer/types/module_augmentation';\n\n/**\n * Returns the language code of the given locale for locales including the country code.\n *\n * Example:\n *\n * getLocaleLang('en-US') // 'en'\n * getLocaleLang('en') // 'en'\n * getLocaleLang('fr-CA') // 'fr'\n * getLocaleLang('fr') // 'fr'\n *\n * @param locale The locale to get the language code for.\n * @returns The language code of the given locale.\n */\nexport const getLocaleLang = <const L extends LocalesValues>(\n locale?: L\n): GetLocaleLang<L & string> =>\n (locale?.split('-')[0] ?? '') as GetLocaleLang<L & string>;\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAa,iBACX,WAEC,QAAQ,MAAM,GAAG,EAAE,MAAM"}
|
|
@@ -53,6 +53,11 @@ const extractHostname = (domain) => {
|
|
|
53
53
|
* Auto-detected from the input URL or `window.location` when omitted.
|
|
54
54
|
* @returns The localized URL for the current locale.
|
|
55
55
|
*/
|
|
56
|
+
/**
|
|
57
|
+
* The return type is narrowed to a template-literal type when both `url` and
|
|
58
|
+
* `currentLocale` are string literals and the routing mode / defaultLocale are
|
|
59
|
+
* not overridden via `options`.
|
|
60
|
+
*/
|
|
56
61
|
const getLocalizedUrl = (url, currentLocale = _intlayer_config_built.internationalization?.defaultLocale, options = {}) => {
|
|
57
62
|
const { defaultLocale, mode, locales, rewrite, domains, currentDomain } = require_localization_getPrefix.resolveRoutingConfig(options);
|
|
58
63
|
const urlWithoutLocale = require_localization_getPathWithoutLocale.getPathWithoutLocale(url, locales);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getLocalizedUrl.cjs","names":["internationalization","resolveRoutingConfig","getPathWithoutLocale","getRewriteRules","getLocalizedPath","getCanonicalPath","checkIsURLAbsolute","getPrefix"],"sources":["../../../src/localization/getLocalizedUrl.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type {
|
|
1
|
+
{"version":3,"file":"getLocalizedUrl.cjs","names":["internationalization","resolveRoutingConfig","getPathWithoutLocale","getRewriteRules","getLocalizedPath","getCanonicalPath","checkIsURLAbsolute","getPrefix"],"sources":["../../../src/localization/getLocalizedUrl.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type {\n LocalesValues,\n LocalizedUrl,\n ResolvedDefaultLocale,\n} from '@intlayer/types/module_augmentation';\nimport { checkIsURLAbsolute } from '../utils/checkIsURLAbsolute';\nimport { getPathWithoutLocale } from './getPathWithoutLocale';\nimport {\n getPrefix,\n type RoutingOptions,\n resolveRoutingConfig,\n} from './getPrefix';\nimport {\n getCanonicalPath,\n getLocalizedPath,\n getRewriteRules,\n} from './rewriteUtils';\n\nexport type { RoutingOptions };\n\n/** Strips the protocol and returns the bare hostname of a domain string. */\nconst extractHostname = (domain: string): string => {\n try {\n return /^https?:\\/\\//.test(domain) ? new URL(domain).hostname : domain;\n } catch {\n return domain;\n }\n};\n\n/**\n * Generate URL by prefixing the given URL with the referenced locale or adding search parameters\n * based on the routing mode. Handles both absolute and relative URLs appropriately.\n *\n * This function gets the locales, default locale, and routing mode from the configuration if not provided.\n *\n * Example:\n *\n * ```ts\n * // prefix-no-default mode\n * getLocalizedUrl('/about', 'fr', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'prefix-no-default' });\n * // Returns '/fr/about' for the French locale\n * // Returns '/about' for the English locale (default)\n *\n * // prefix-all mode\n * getLocalizedUrl('/about', 'en', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'prefix-all' });\n * // Returns '/en/about' for the English locale\n * // Returns '/fr/about' for the French locale\n *\n * // search-params mode\n * getLocalizedUrl('/about', 'fr', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'search-params' });\n * // Returns '/about?locale=fr' for the French locale\n *\n * // no-prefix mode\n * getLocalizedUrl('/about', 'fr', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'no-prefix' });\n * // Returns '/about' for any locale\n * ```\n *\n * @param url - The original URL string to be processed.\n * @param currentLocale - The current locale.\n * @param options - Configuration options\n * @param options.locales - Optional array of supported locales. Defaults to configured locales.\n * @param options.defaultLocale - The default locale. Defaults to configured default locale.\n * @param options.mode - URL routing mode for locale handling. Defaults to configured mode.\n * @param options.currentDomain - Hostname of the page being rendered. Used to decide\n * whether to emit a relative URL (same domain) or an absolute URL (cross-domain).\n * Auto-detected from the input URL or `window.location` when omitted.\n * @returns The localized URL for the current locale.\n */\n/**\n * The return type is narrowed to a template-literal type when both `url` and\n * `currentLocale` are string literals and the routing mode / defaultLocale are\n * not overridden via `options`.\n */\nexport const getLocalizedUrl = <\n const T extends string,\n const L extends LocalesValues = ResolvedDefaultLocale,\n>(\n url: T,\n currentLocale: L = internationalization?.defaultLocale as L,\n options: RoutingOptions = {}\n): LocalizedUrl<T, L> => {\n const { defaultLocale, mode, locales, rewrite, domains, currentDomain } =\n resolveRoutingConfig(options);\n\n const urlWithoutLocale = getPathWithoutLocale(url, locales);\n const rewriteRules = getRewriteRules(rewrite, 'url');\n\n if (\n !(\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'no-prefix'\n ) &&\n mode === 'no-prefix'\n ) {\n return getLocalizedPath(\n getCanonicalPath(urlWithoutLocale, undefined, rewriteRules),\n currentLocale as Locale,\n rewriteRules\n ).path as LocalizedUrl<T, L>;\n }\n\n const isAbsoluteUrl = checkIsURLAbsolute(urlWithoutLocale);\n const parsedUrl = isAbsoluteUrl\n ? new URL(urlWithoutLocale)\n : new URL(urlWithoutLocale, 'http://example.com');\n\n const translatedPathname = getLocalizedPath(\n getCanonicalPath(parsedUrl.pathname, undefined, rewriteRules),\n currentLocale as Locale,\n rewriteRules\n ).path;\n\n // ── Domain routing ────────────────────────────────────────────────────────\n // Resolve the \"current\" hostname so we can choose between a relative URL\n // (same domain) and an absolute URL (cross-domain).\n //\n // Detection priority:\n // 1. Explicit `currentDomain` option passed by the caller.\n // 2. Hostname extracted from an absolute input URL.\n // 3. `window.location.hostname` in browser environments.\n // When none of these is available we fall back to always generating an\n // absolute URL (the previous behaviour, safe for SSR/static generation).\n const detectedCurrentHostname =\n process.env['INTLAYER_ROUTING_DOMAINS'] !== 'false'\n ? extractHostname(\n currentDomain ??\n (isAbsoluteUrl ? parsedUrl.hostname : undefined) ??\n (typeof window !== 'undefined'\n ? window?.location?.hostname\n : undefined) ??\n ''\n ) || null\n : null;\n\n const localeDomain =\n process.env['INTLAYER_ROUTING_DOMAINS'] !== 'false'\n ? domains?.[currentLocale as LocalesValues]\n : undefined;\n\n const localeDomainHostname = localeDomain\n ? extractHostname(localeDomain)\n : null;\n\n // Only prepend the locale's domain when it differs from the current hostname.\n const isCrossDomain =\n localeDomainHostname !== null &&\n detectedCurrentHostname !== null &&\n localeDomainHostname !== detectedCurrentHostname;\n\n const normalizedDomain =\n isCrossDomain && localeDomain\n ? /^https?:\\/\\//.test(localeDomain)\n ? localeDomain\n : `https://${localeDomain}`\n : null;\n\n const baseUrl = normalizedDomain\n ? normalizedDomain\n : isAbsoluteUrl\n ? `${parsedUrl.protocol}//${parsedUrl.host}`\n : '';\n // ─────────────────────────────────────────────────────────────────────────\n\n if (\n !(\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'search-params'\n ) &&\n mode === 'search-params'\n ) {\n const searchParams = new URLSearchParams(parsedUrl.search);\n\n searchParams.set('locale', currentLocale.toString());\n\n const queryParams = searchParams.toString();\n const path = queryParams\n ? `${translatedPathname}?${queryParams}`\n : translatedPathname;\n\n return `${baseUrl}${path}${parsedUrl.hash}` as LocalizedUrl<T, L>;\n }\n\n const { prefix } = getPrefix(currentLocale, {\n defaultLocale,\n mode,\n locales,\n domains,\n });\n\n let localizedPath = `/${prefix}${translatedPathname}`.replace(/\\/+/g, '/');\n\n if (localizedPath.length > 1 && localizedPath.endsWith('/')) {\n localizedPath = localizedPath.slice(0, -1);\n }\n\n return `${baseUrl}${localizedPath}${parsedUrl.search}${parsedUrl.hash}` as LocalizedUrl<\n T,\n L\n >;\n};\n"],"mappings":";;;;;;;;;AA4BA,MAAM,mBAAmB,WAA2B;CAClD,IAAI;EACF,OAAO,eAAe,KAAK,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,WAAW;CAClE,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAa,mBAIX,KACA,gBAAmBA,6CAAsB,eACzC,UAA0B,CAAC,MACJ;CACvB,MAAM,EAAE,eAAe,MAAM,SAAS,SAAS,SAAS,kBACtDC,oDAAqB,OAAO;CAE9B,MAAM,mBAAmBC,+DAAqB,KAAK,OAAO;CAC1D,MAAM,eAAeC,kDAAgB,SAAS,KAAK;CAEnD,IACE,EACE,QAAQ,IAAI,4BACZ,QAAQ,IAAI,6BAA6B,gBAE3C,SAAS,aAET,OAAOC,mDACLC,mDAAiB,kBAAkB,QAAW,YAAY,GAC1D,eACA,YACF,EAAE;CAGJ,MAAM,gBAAgBC,oDAAmB,gBAAgB;CACzD,MAAM,YAAY,gBACd,IAAI,IAAI,gBAAgB,IACxB,IAAI,IAAI,kBAAkB,oBAAoB;CAElD,MAAM,qBAAqBF,mDACzBC,mDAAiB,UAAU,UAAU,QAAW,YAAY,GAC5D,eACA,YACF,EAAE;CAYF,MAAM,0BACJ,QAAQ,IAAI,gCAAgC,UACxC,gBACE,kBACG,gBAAgB,UAAU,WAAW,YACrC,OAAO,WAAW,cACf,QAAQ,UAAU,WAClB,WACJ,EACJ,KAAK,OACL;CAEN,MAAM,eACJ,QAAQ,IAAI,gCAAgC,UACxC,UAAU,iBACV;CAEN,MAAM,uBAAuB,eACzB,gBAAgB,YAAY,IAC5B;CAQJ,MAAM,mBAJJ,yBAAyB,QACzB,4BAA4B,QAC5B,yBAAyB,2BAGR,eACb,eAAe,KAAK,YAAY,IAC9B,eACA,WAAW,iBACb;CAEN,MAAM,UAAU,mBACZ,mBACA,gBACE,GAAG,UAAU,SAAS,IAAI,UAAU,SACpC;CAGN,IACE,EACE,QAAQ,IAAI,4BACZ,QAAQ,IAAI,6BAA6B,oBAE3C,SAAS,iBACT;EACA,MAAM,eAAe,IAAI,gBAAgB,UAAU,MAAM;EAEzD,aAAa,IAAI,UAAU,cAAc,SAAS,CAAC;EAEnD,MAAM,cAAc,aAAa,SAAS;EAK1C,OAAO,GAAG,UAJG,cACT,GAAG,mBAAmB,GAAG,gBACzB,qBAEuB,UAAU;CACvC;CAEA,MAAM,EAAE,WAAWE,yCAAU,eAAe;EAC1C;EACA;EACA;EACA;CACF,CAAC;CAED,IAAI,gBAAgB,IAAI,SAAS,qBAAqB,QAAQ,QAAQ,GAAG;CAEzE,IAAI,cAAc,SAAS,KAAK,cAAc,SAAS,GAAG,GACxD,gBAAgB,cAAc,MAAM,GAAG,EAAE;CAG3C,OAAO,GAAG,UAAU,gBAAgB,UAAU,SAAS,UAAU;AAInE"}
|
|
@@ -36,6 +36,11 @@ const TREE_SHAKE_SEARCH_PARAMS = process.env["INTLAYER_ROUTING_MODE"] && process
|
|
|
36
36
|
* @param locales - Optional array of supported locales. Defaults to `localesDefault`.
|
|
37
37
|
* @returns The URL string or pathname without the locale segment or locale search parameter.
|
|
38
38
|
*/
|
|
39
|
+
/**
|
|
40
|
+
* The return type is narrowed to a template-literal type when both `inputUrl`
|
|
41
|
+
* and `locales` are string literals known at compile time. Absolute URLs fall
|
|
42
|
+
* back to `string`.
|
|
43
|
+
*/
|
|
39
44
|
const getPathWithoutLocale = (inputUrl, locales = _intlayer_config_built.internationalization?.locales) => {
|
|
40
45
|
const isAbsoluteUrl = require_utils_checkIsURLAbsolute.checkIsURLAbsolute(inputUrl);
|
|
41
46
|
let fixedInputUrl = inputUrl;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getPathWithoutLocale.cjs","names":["internationalization","checkIsURLAbsolute"],"sources":["../../../src/localization/getPathWithoutLocale.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\n/**\n * True when the build-time routing mode is known and is not a prefix-based\n * mode (neither 'prefix-all' nor 'prefix-no-default').\n */\nconst TREE_SHAKE_PREFIX_MODES =\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'prefix-all' &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'prefix-no-default';\n\n/**\n * True when the build-time routing mode is known and is NOT 'search-params'.\n */\nconst TREE_SHAKE_SEARCH_PARAMS =\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'search-params';\n\nimport type {
|
|
1
|
+
{"version":3,"file":"getPathWithoutLocale.cjs","names":["internationalization","checkIsURLAbsolute"],"sources":["../../../src/localization/getPathWithoutLocale.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\n/**\n * True when the build-time routing mode is known and is not a prefix-based\n * mode (neither 'prefix-all' nor 'prefix-no-default').\n */\nconst TREE_SHAKE_PREFIX_MODES =\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'prefix-all' &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'prefix-no-default';\n\n/**\n * True when the build-time routing mode is known and is NOT 'search-params'.\n */\nconst TREE_SHAKE_SEARCH_PARAMS =\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'search-params';\n\nimport type {\n DeclaredLocales,\n LocalesValues,\n PathWithoutLocale,\n} from '@intlayer/types/module_augmentation';\nimport { checkIsURLAbsolute } from '../utils/checkIsURLAbsolute';\n\n/**\n * Removes the locale segment from the given URL or pathname if present.\n * Also removes locale from search parameters if present.\n *\n * This function get the locales from the configuration if not provided.\n *\n * Example:\n *\n * ```ts\n * getPathWithoutLocale('/en/dashboard') // Returns '/dashboard'\n * getPathWithoutLocale('/fr/dashboard') // Returns '/dashboard'\n * getPathWithoutLocale('/dashboard') // Returns '/dashboard'\n * getPathWithoutLocale('dashboard') // Returns 'dashboard'\n * getPathWithoutLocale('/dashboard?locale=fr') // Returns '/dashboard'\n * getPathWithoutLocale('https://example.com/en/dashboard') // Returns 'https://example.com/dashboard'\n * getPathWithoutLocale('https://example.com/fr/dashboard') // Returns 'https://example.com/dashboard'\n * getPathWithoutLocale('https://example.com/dashboard') // Returns 'https://example.com/dashboard'\n * getPathWithoutLocale('https://example.com/dashboard?locale=fr') // Returns 'https://example.com/dashboard'\n * ```\n *\n * @param inputUrl - The complete URL string or pathname to process.\n * @param locales - Optional array of supported locales. Defaults to `localesDefault`.\n * @returns The URL string or pathname without the locale segment or locale search parameter.\n */\n/**\n * The return type is narrowed to a template-literal type when both `inputUrl`\n * and `locales` are string literals known at compile time. Absolute URLs fall\n * back to `string`.\n */\nexport const getPathWithoutLocale = <\n const T extends string,\n const L extends LocalesValues = DeclaredLocales,\n>(\n inputUrl: T,\n locales: L[] = internationalization?.locales as L[]\n): PathWithoutLocale<T, L> => {\n // Determine if the original URL is absolute (includes protocol)\n const isAbsoluteUrl = checkIsURLAbsolute(inputUrl);\n\n let fixedInputUrl = inputUrl;\n\n if (inputUrl?.endsWith('/')) {\n fixedInputUrl = inputUrl.slice(0, -1);\n }\n\n // Initialize a URL object if the URL is absolute\n // For relative URLs, use a dummy base to leverage the URL API\n const url = isAbsoluteUrl\n ? new URL(fixedInputUrl)\n : new URL(fixedInputUrl, 'http://example.com');\n\n const pathname = url.pathname;\n\n // Ensure the pathname starts with '/'\n if (!pathname.startsWith('/')) {\n // If not, return the URL as is\n url.pathname = `/${pathname}`;\n }\n\n // Only strip locale path prefix in prefix-based routing modes\n if (!TREE_SHAKE_PREFIX_MODES) {\n // Split the pathname to extract the first segment\n const pathSegments = pathname.split('/');\n const firstSegment = pathSegments[1]; // The segment after the first '/'\n\n // Check if the first segment is a supported locale\n if (locales?.includes(firstSegment as L)) {\n // Remove the locale segment from the pathname\n pathSegments.splice(1, 1); // Remove the first segment\n\n // Reconstruct the pathname\n const newPathname = pathSegments.join('/') ?? '/';\n url.pathname = newPathname;\n }\n }\n\n // Only strip locale from search parameters in search-params routing mode\n if (!TREE_SHAKE_SEARCH_PARAMS) {\n const searchParams = new URLSearchParams(url.search);\n if (searchParams.has('locale')) {\n searchParams.delete('locale');\n url.search = searchParams.toString();\n }\n }\n\n if (isAbsoluteUrl) {\n return url.toString() as PathWithoutLocale<T, L>;\n }\n\n return url.toString().replace('http://example.com', '') as PathWithoutLocale<\n T,\n L\n >;\n};\n"],"mappings":";;;;;;;;;AAUA,MAAM,0BACJ,QAAQ,IAAI,4BACZ,QAAQ,IAAI,6BAA6B,gBACzC,QAAQ,IAAI,6BAA6B;;;;AAK3C,MAAM,2BACJ,QAAQ,IAAI,4BACZ,QAAQ,IAAI,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsC3C,MAAa,wBAIX,UACA,UAAeA,6CAAsB,YACT;CAE5B,MAAM,gBAAgBC,oDAAmB,QAAQ;CAEjD,IAAI,gBAAgB;CAEpB,IAAI,UAAU,SAAS,GAAG,GACxB,gBAAgB,SAAS,MAAM,GAAG,EAAE;CAKtC,MAAM,MAAM,gBACR,IAAI,IAAI,aAAa,IACrB,IAAI,IAAI,eAAe,oBAAoB;CAE/C,MAAM,WAAW,IAAI;CAGrB,IAAI,CAAC,SAAS,WAAW,GAAG,GAE1B,IAAI,WAAW,IAAI;CAIrB,IAAI,CAAC,yBAAyB;EAE5B,MAAM,eAAe,SAAS,MAAM,GAAG;EACvC,MAAM,eAAe,aAAa;EAGlC,IAAI,SAAS,SAAS,YAAiB,GAAG;GAExC,aAAa,OAAO,GAAG,CAAC;GAIxB,IAAI,WADgB,aAAa,KAAK,GAAG,KAAK;EAEhD;CACF;CAGA,IAAI,CAAC,0BAA0B;EAC7B,MAAM,eAAe,IAAI,gBAAgB,IAAI,MAAM;EACnD,IAAI,aAAa,IAAI,QAAQ,GAAG;GAC9B,aAAa,OAAO,QAAQ;GAC5B,IAAI,SAAS,aAAa,SAAS;EACrC;CACF;CAEA,IAAI,eACF,OAAO,IAAI,SAAS;CAGtB,OAAO,IAAI,SAAS,EAAE,QAAQ,sBAAsB,EAAE;AAIxD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getPrefix.cjs","names":["internationalization","DEFAULT_LOCALE","routing","ROUTING_MODE","LOCALES"],"sources":["../../../src/localization/getPrefix.ts"],"sourcesContent":["import { internationalization, routing } from '@intlayer/config/built';\nimport {\n DEFAULT_LOCALE,\n LOCALES,\n ROUTING_MODE,\n} from '@intlayer/config/defaultValues';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { RoutingConfig } from '@intlayer/types/config';\nimport type {
|
|
1
|
+
{"version":3,"file":"getPrefix.cjs","names":["internationalization","DEFAULT_LOCALE","routing","ROUTING_MODE","LOCALES"],"sources":["../../../src/localization/getPrefix.ts"],"sourcesContent":["import { internationalization, routing } from '@intlayer/config/built';\nimport {\n DEFAULT_LOCALE,\n LOCALES,\n ROUTING_MODE,\n} from '@intlayer/config/defaultValues';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { RoutingConfig } from '@intlayer/types/config';\nimport type {\n DeclaredLocales,\n LocalesValues,\n ResolvedDefaultLocale,\n ResolvedRoutingMode,\n} from '@intlayer/types/module_augmentation';\n\n/**\n * Shared routing options used across all URL localization functions.\n */\nexport type RoutingOptions = {\n locales?: LocalesValues[];\n defaultLocale?: LocalesValues;\n mode?: RoutingConfig['mode'];\n rewrite?: RoutingConfig['rewrite'];\n domains?: RoutingConfig['domains'];\n /**\n * The hostname of the page currently being rendered (e.g. `'intlayer.org'`).\n * When provided, `getLocalizedUrl` returns a relative URL for locales whose\n * configured domain matches `currentDomain`, and an absolute URL only when\n * the target locale lives on a different domain.\n *\n * When omitted the function tries to infer it from:\n * 1. The domain of an absolute input URL.\n * 2. `window.location.hostname` in browser environments.\n * Falls back to always generating absolute URLs when neither is available.\n */\n currentDomain?: string;\n};\n\n/**\n * Resolves routing configuration by merging provided options with configuration defaults.\n * Single source of truth for default routing config resolution across all localization functions.\n */\nexport const resolveRoutingConfig = (options: RoutingOptions = {}) => ({\n defaultLocale: internationalization?.defaultLocale ?? DEFAULT_LOCALE,\n mode: routing?.mode ?? ROUTING_MODE,\n locales: internationalization?.locales ?? LOCALES,\n rewrite: routing?.rewrite,\n domains: routing?.domains,\n ...options,\n});\n\nexport type GetPrefixOptions = {\n defaultLocale?: LocalesValues;\n mode?: RoutingConfig['mode'];\n};\n\nexport type GetPrefixResult = {\n /**\n * The locale path segment appended to `/`, with a trailing slash (e.g. `'fr/'`).\n * Empty string when no prefix is needed.\n */\n prefix: string;\n /**\n * The bare locale identifier (e.g. `'fr'`), or `undefined` when no prefix is applied.\n */\n localePrefix: Locale | undefined;\n};\n\n/**\n * Narrowed return type for {@link getPrefix} that carries the locale literal through.\n *\n * Distributes over union locales — calling `getPrefix('fr')` in `prefix-no-default`\n * mode with `defaultLocale = 'en'` resolves to `{ prefix: 'fr/'; localePrefix: 'fr' }`.\n *\n * Note: domain-based routing and \"locale not in locales\" edge cases may return an\n * empty result at runtime regardless of what this type reports.\n */\nexport type GetPrefixResultNarrowed<\n L extends LocalesValues | undefined,\n Mode extends string = ResolvedRoutingMode,\n Default extends LocalesValues = ResolvedDefaultLocale,\n> = L extends string\n ? [string] extends [L] // L is wide (string / LocalesValues) → distribute over declared locales\n ? GetPrefixResultNarrowed<DeclaredLocales, Mode, Default>\n : [string] extends [Mode]\n ? GetPrefixResult // mode is wide → fall back to generic result\n : Mode extends 'prefix-all'\n ? { prefix: `${L}/`; localePrefix: L }\n : Mode extends 'prefix-no-default'\n ? L extends Default\n ? { prefix: ''; localePrefix: undefined }\n : { prefix: `${L}/`; localePrefix: L }\n : { prefix: ''; localePrefix: undefined } // no-prefix / search-params\n : { prefix: ''; localePrefix: undefined }; // locale is undefined\n\n/**\n * Determines the URL prefix for a given locale based on the routing mode configuration.\n *\n * Example:\n *\n * ```ts\n * // prefix-no-default mode with default locale\n * getPrefix('en', { defaultLocale: 'en', mode: 'prefix-no-default' })\n * // Returns { prefix: '', localePrefix: undefined }\n *\n * // prefix-no-default mode with non-default locale\n * getPrefix('fr', { defaultLocale: 'en', mode: 'prefix-no-default' })\n * // Returns { prefix: '/fr', localePrefix: 'fr' }\n *\n * // prefix-all mode\n * getPrefix('en', { defaultLocale: 'en', mode: 'prefix-all' })\n * // Returns { prefix: '/en', localePrefix: locale }\n *\n * // search-params mode\n * getPrefix('en', { defaultLocale: 'en', mode: 'search-params' })\n * // Returns { prefix: '', localePrefix: undefined }\n *\n * // no-prefix mode\n * getPrefix('en', { defaultLocale: 'en', mode: 'no-prefix' })\n * // Returns { prefix: '', localePrefix: undefined }\n * ```\n *\n * @param locale - The locale to check for prefix. If not provided, uses configured default locale.\n * @param options - Configuration options\n * @param options.defaultLocale - The default locale. Defaults to configured default locale.\n * @param options.mode - URL routing mode for locale handling. Defaults to configured mode.\n * @returns An object containing pathPrefix, prefix, and localePrefix for the given locale.\n */\nexport const getPrefix = <const L extends LocalesValues | undefined>(\n locale: L,\n options: RoutingOptions = {}\n): GetPrefixResultNarrowed<L> => {\n const { defaultLocale, mode, locales, domains } =\n resolveRoutingConfig(options);\n\n if (\n (process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'prefix-all' &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'prefix-no-default') ||\n !locale ||\n !locales.includes(locale)\n ) {\n return {\n prefix: '',\n localePrefix: undefined,\n } as GetPrefixResultNarrowed<L>;\n }\n\n // If this locale is the only one assigned to its domain, no URL prefix is needed\n // (the domain itself identifies the locale). Shared domains use normal prefix logic.\n if (process.env['INTLAYER_ROUTING_DOMAINS'] !== 'false' && domains) {\n const localeDomain = domains[locale as LocalesValues];\n\n if (localeDomain) {\n const localesOnSameDomain = Object.values(domains).filter(\n (domain) => domain === localeDomain\n ).length;\n\n if (localesOnSameDomain === 1) {\n return {\n prefix: '',\n localePrefix: undefined,\n } as GetPrefixResultNarrowed<L>;\n }\n }\n }\n\n // Handle prefix-based modes (prefix-all or prefix-no-default)\n const shouldPrefix =\n mode === 'prefix-all' ||\n (mode === 'prefix-no-default' && defaultLocale !== locale);\n\n if (shouldPrefix) {\n return {\n prefix: `${locale}/`,\n localePrefix: locale as Locale,\n } as GetPrefixResultNarrowed<L>;\n }\n\n return {\n prefix: '',\n localePrefix: undefined,\n } as GetPrefixResultNarrowed<L>;\n};\n"],"mappings":";;;;;;;;;AA+CA,MAAa,wBAAwB,UAA0B,CAAC,OAAO;CACrE,eAAeA,6CAAsB,iBAAiBC;CACtD,MAAMC,gCAAS,QAAQC;CACvB,SAASH,6CAAsB,WAAWI;CAC1C,SAASF,gCAAS;CAClB,SAASA,gCAAS;CAClB,GAAG;AACL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+EA,MAAa,aACX,QACA,UAA0B,CAAC,MACI;CAC/B,MAAM,EAAE,eAAe,MAAM,SAAS,YACpC,qBAAqB,OAAO;CAE9B,IACG,QAAQ,IAAI,4BACX,QAAQ,IAAI,6BAA6B,gBACzC,QAAQ,IAAI,6BAA6B,uBAC3C,CAAC,UACD,CAAC,QAAQ,SAAS,MAAM,GAExB,OAAO;EACL,QAAQ;EACR,cAAc;CAChB;CAKF,IAAI,QAAQ,IAAI,gCAAgC,WAAW,SAAS;EAClE,MAAM,eAAe,QAAQ;EAE7B,IAAI,cAKF;OAJ4B,OAAO,OAAO,OAAO,EAAE,QAChD,WAAW,WAAW,YACzB,EAAE,WAE0B,GAC1B,OAAO;IACL,QAAQ;IACR,cAAc;GAChB;EACF;CAEJ;CAOA,IAHE,SAAS,gBACR,SAAS,uBAAuB,kBAAkB,QAGnD,OAAO;EACL,QAAQ,GAAG,OAAO;EAClB,cAAc;CAChB;CAGF,OAAO;EACL,QAAQ;EACR,cAAc;CAChB;AACF"}
|
|
@@ -23,11 +23,22 @@ const buildCookieString = (name, value, attributes) => {
|
|
|
23
23
|
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
24
24
|
if (attributes.path) parts.push(`Path=${attributes.path}`);
|
|
25
25
|
if (attributes.domain) parts.push(`Domain=${attributes.domain}`);
|
|
26
|
-
if (attributes.
|
|
26
|
+
if (typeof attributes.maxAge === "number") parts.push(`Max-Age=${Math.floor(attributes.maxAge)}`);
|
|
27
|
+
else if (attributes.expires instanceof Date) parts.push(`Expires=${attributes.expires.toUTCString()}`);
|
|
27
28
|
if (attributes.secure) parts.push("Secure");
|
|
28
29
|
if (attributes.sameSite) parts.push(`SameSite=${attributes.sameSite}`);
|
|
29
30
|
return parts.join("; ");
|
|
30
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* Resolves the cookie expiry for the Cookie Store API (epoch milliseconds).
|
|
34
|
+
* `maxAge` takes precedence and is converted to an absolute timestamp, as the
|
|
35
|
+
* native `maxAge` option of `cookieStore.set()` is not yet supported in all
|
|
36
|
+
* browsers that support `cookieStore` itself.
|
|
37
|
+
*/
|
|
38
|
+
const getCookieStoreExpires = (attributes) => {
|
|
39
|
+
if (typeof attributes.maxAge === "number") return Date.now() + attributes.maxAge * 1e3;
|
|
40
|
+
return attributes.expires instanceof Date ? attributes.expires.getTime() : attributes.expires;
|
|
41
|
+
};
|
|
31
42
|
const localeStorageOptions = {
|
|
32
43
|
getCookie: (name) => document.cookie.split(";").find((c) => c.trim().startsWith(`${name}=`))?.split("=")[1],
|
|
33
44
|
getLocaleStorage: (name) => localStorage.getItem(name),
|
|
@@ -81,7 +92,7 @@ const setLocaleInStorageClient = (locale, options) => {
|
|
|
81
92
|
try {
|
|
82
93
|
if (options?.setCookieStore) options.setCookieStore(name, locale, {
|
|
83
94
|
...attributes,
|
|
84
|
-
expires: attributes
|
|
95
|
+
expires: getCookieStoreExpires(attributes)
|
|
85
96
|
});
|
|
86
97
|
} catch {
|
|
87
98
|
try {
|
|
@@ -153,7 +164,7 @@ const setLocaleInStorageServer = (locale, options) => {
|
|
|
153
164
|
try {
|
|
154
165
|
if (options?.setCookieStore) options.setCookieStore(name, locale, {
|
|
155
166
|
...attributes,
|
|
156
|
-
expires: attributes
|
|
167
|
+
expires: getCookieStoreExpires(attributes)
|
|
157
168
|
});
|
|
158
169
|
} catch {
|
|
159
170
|
try {
|
|
@@ -235,7 +246,7 @@ const setLocaleInStorage = (locale, options) => {
|
|
|
235
246
|
try {
|
|
236
247
|
if (options?.setCookieStore) options.setCookieStore(name, locale, {
|
|
237
248
|
...attributes,
|
|
238
|
-
expires: attributes
|
|
249
|
+
expires: getCookieStoreExpires(attributes)
|
|
239
250
|
});
|
|
240
251
|
} catch {
|
|
241
252
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localeStorage.cjs","names":["internationalization","routing","getCookie"],"sources":["../../../src/utils/localeStorage.ts"],"sourcesContent":["import { internationalization, routing } from '@intlayer/config/built';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { CookiesAttributes } from '@intlayer/types/config';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCookie } from './getCookie';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\n/**\n * True when cookie storage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_COOKIES =\n process.env['INTLAYER_ROUTING_STORAGE_COOKIES'] === 'false';\n\n/**\n * True when localStorage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_LOCAL_STORAGE =\n process.env['INTLAYER_ROUTING_STORAGE_LOCALSTORAGE'] === 'false';\n\n/**\n * True when sessionStorage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_SESSION_STORAGE =\n process.env['INTLAYER_ROUTING_STORAGE_SESSIONSTORAGE'] === 'false';\n\n/**\n * True when header storage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_HEADERS =\n process.env['INTLAYER_ROUTING_STORAGE_HEADERS'] === 'false';\n\n// ============================================================================\n// Shared types\n// ============================================================================\n\nexport type CookieBuildAttributes = {\n domain?: string;\n path?: string;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: 'strict' | 'lax' | 'none';\n /** Expiry as milliseconds since epoch (Date.getTime()) or number of days */\n expires?: number | undefined;\n};\n\n// ============================================================================\n// Shared helpers\n// ============================================================================\n\nconst buildCookieString = (\n name: string,\n value: string,\n attributes: Omit<CookiesAttributes, 'name' | 'type'>\n): string => {\n const encodedValue = encodeURIComponent(value);\n const parts: string[] = [`${name}=${encodedValue}`];\n\n if (attributes.path) parts.push(`Path=${attributes.path}`);\n if (attributes.domain) parts.push(`Domain=${attributes.domain}`);\n if (attributes.expires instanceof Date)\n parts.push(`Expires=${attributes.expires.toUTCString()}`);\n if (attributes.secure) parts.push('Secure');\n if (attributes.sameSite) parts.push(`SameSite=${attributes.sameSite}`);\n return parts.join('; ');\n};\n\n// ============================================================================\n// Client-specific types and functions\n// (cookies via browser APIs, localStorage, sessionStorage — no headers)\n// ============================================================================\n\nexport type LocaleStorageClientOptions = {\n overwrite?: boolean;\n isCookieEnabled?: boolean;\n setCookieStore?: (\n name: string,\n value: string,\n cookie: CookieBuildAttributes\n ) => void;\n setCookieString?: (name: string, cookie: string) => void;\n getCookie?: (name: string) => string | undefined | null;\n setSessionStorage?: (name: string, value: string) => void;\n getSessionStorage?: (name: string) => string | undefined | null;\n setLocaleStorage?: (name: string, value: string) => void;\n getLocaleStorage?: (name: string) => string | undefined | null;\n};\n\n// cookieStore is part of the experimental Cookie Store API\ndeclare const cookieStore: any;\n\nexport const localeStorageOptions: LocaleStorageClientOptions = {\n getCookie: (name: string) =>\n document.cookie\n .split(';')\n .find((c) => c.trim().startsWith(`${name}=`))\n ?.split('=')[1],\n getLocaleStorage: (name: string) => localStorage.getItem(name),\n getSessionStorage: (name: string) => sessionStorage.getItem(name),\n isCookieEnabled: true,\n setCookieStore: (name, value, attributes) =>\n cookieStore.set({\n name,\n value,\n path: attributes.path,\n domain: attributes.domain,\n expires: attributes.expires,\n sameSite: attributes.sameSite,\n }),\n setCookieString: (_name, cookie) => {\n // biome-ignore lint/suspicious/noDocumentCookie: set cookie fallback\n document.cookie = cookie;\n },\n setSessionStorage: (name, value) => sessionStorage.setItem(name, value),\n setLocaleStorage: (name, value) => localStorage.setItem(name, value),\n};\n\n/**\n * Retrieves the locale from browser storage mechanisms\n * (cookies, localStorage, sessionStorage).\n * Does not read from headers — use `getLocaleFromStorageServer` for that.\n */\nexport const getLocaleFromStorageClient = (\n options: LocaleStorageClientOptions = localeStorageOptions\n): Locale | undefined => {\n const { locales } = internationalization;\n\n if (options?.isCookieEnabled === false) return undefined;\n\n const isValidLocale = (value: string | null | undefined): value is Locale =>\n !!value && locales.includes(value as Locale);\n\n if (!TREE_SHAKE_STORAGE_COOKIES) {\n for (let i = 0; i < (routing.storage.cookies ?? []).length; i++) {\n try {\n const value = options?.getCookie?.(routing.storage.cookies![i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_LOCAL_STORAGE) {\n for (let i = 0; i < (routing.storage.localStorage ?? []).length; i++) {\n try {\n const value = options?.getLocaleStorage?.(\n routing.storage.localStorage![i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_SESSION_STORAGE && routing.storage.sessionStorage) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n try {\n const value = options?.getSessionStorage?.(\n routing.storage.sessionStorage[i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n};\n\n/**\n * Stores the locale in browser storage mechanisms\n * (cookies, localStorage, sessionStorage).\n * Does not write to headers — use `setLocaleInStorageServer` for that.\n */\nexport const setLocaleInStorageClient = (\n locale: LocalesValues,\n options?: LocaleStorageClientOptions\n): void => {\n if (options?.isCookieEnabled === false) return;\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const { name, attributes } = routing.storage.cookies[i];\n try {\n if (options?.setCookieStore) {\n options.setCookieStore(name, locale, {\n ...attributes,\n expires:\n attributes.expires instanceof Date\n ? attributes.expires.getTime()\n : attributes.expires,\n });\n }\n } catch {\n try {\n if (options?.setCookieString) {\n options.setCookieString(\n name,\n buildCookieString(name, locale, attributes)\n );\n }\n } catch {}\n }\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_LOCAL_STORAGE &&\n routing.storage.localStorage &&\n options?.setLocaleStorage\n ) {\n for (let i = 0; i < routing.storage.localStorage.length; i++) {\n const { name } = routing.storage.localStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getLocaleStorage) {\n if (options.getLocaleStorage(name)) continue;\n }\n options.setLocaleStorage(name, locale);\n } catch {}\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_SESSION_STORAGE &&\n routing.storage.sessionStorage &&\n options?.setSessionStorage\n ) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n const { name } = routing.storage.sessionStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getSessionStorage) {\n if (options.getSessionStorage(name)) continue;\n }\n options.setSessionStorage(name, locale);\n } catch {}\n }\n }\n};\n\n/**\n * Client-side locale storage utility.\n * Handles cookies (browser), localStorage and sessionStorage.\n * Does not access headers.\n *\n * @example\n * ```ts\n * const storage = LocaleStorageClient(localeStorageOptions);\n * const locale = storage.getLocale();\n * storage.setLocale('fr');\n * ```\n */\nexport const LocaleStorageClient = (options: LocaleStorageClientOptions) => ({\n getLocale: () => getLocaleFromStorageClient(options),\n setLocale: (locale: LocalesValues) =>\n setLocaleInStorageClient(locale, options),\n});\n\n// ============================================================================\n// Server-specific types and functions\n// (cookies via injected getter/setter, headers — no localStorage/sessionStorage)\n// ============================================================================\n\nexport type LocaleStorageServerOptions = {\n overwrite?: boolean;\n isCookieEnabled?: boolean;\n setCookieStore?: (\n name: string,\n value: string,\n cookie: CookieBuildAttributes\n ) => void;\n setCookieString?: (name: string, cookie: string) => void;\n getCookie?: (name: string) => string | undefined | null;\n getHeader?: (name: string) => string | undefined | null;\n setHeader?: (name: string, value: string) => void;\n};\n\n/**\n * Retrieves the locale from server-side storage mechanisms (cookies, headers).\n * Does not access localStorage or sessionStorage.\n * No browser cookie fallback — the caller must provide `getCookie`.\n */\nexport const getLocaleFromStorageServer = (\n options: LocaleStorageServerOptions\n): Locale | undefined => {\n const { locales } = internationalization;\n\n if (options?.isCookieEnabled === false) return undefined;\n\n const isValidLocale = (value: string | null | undefined): value is Locale =>\n !!value && locales.includes(value as Locale);\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n try {\n const value = options?.getCookie?.(routing.storage.cookies[i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_HEADERS && routing.storage.headers) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n const value = options?.getHeader?.(routing.storage.headers[i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n};\n\n/**\n * Stores the locale in server-side storage mechanisms (cookies, headers).\n * Does not write to localStorage or sessionStorage.\n */\nexport const setLocaleInStorageServer = (\n locale: LocalesValues,\n options?: LocaleStorageServerOptions\n): void => {\n if (options?.isCookieEnabled === false) return;\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const { name, attributes } = routing.storage.cookies[i];\n\n try {\n if (options?.setCookieStore) {\n options.setCookieStore(name, locale, {\n ...attributes,\n expires:\n attributes.expires instanceof Date\n ? attributes.expires.getTime()\n : attributes.expires,\n });\n }\n } catch {\n try {\n if (options?.setCookieString) {\n options.setCookieString(\n name,\n buildCookieString(name, locale, attributes)\n );\n }\n } catch {}\n }\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_HEADERS &&\n routing.storage.headers &&\n options?.setHeader\n ) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n options.setHeader(routing.storage.headers[i].name, locale);\n } catch {}\n }\n }\n};\n\n/**\n * Server-side locale storage utility.\n * Handles cookies (via injected getter/setter) and headers.\n * Does not access localStorage or sessionStorage.\n *\n * @example\n * ```ts\n * const storage = LocaleStorageServer({\n * getCookie: (name) => req.cookies[name],\n * setCookieStore: (name, value, attrs) => res.cookie(name, value, attrs),\n * getHeader: (name) => req.headers[name],\n * setHeader: (name, value) => res.setHeader(name, value),\n * });\n * const locale = storage.getLocale();\n * storage.setLocale('fr');\n * ```\n */\nexport const LocaleStorageServer = (options: LocaleStorageServerOptions) => ({\n getLocale: () => getLocaleFromStorageServer(options),\n setLocale: (locale: LocalesValues) =>\n setLocaleInStorageServer(locale, options),\n});\n\n// ============================================================================\n// Deprecated: combined LocaleStorage\n// Use LocaleStorageClient or LocaleStorageServer instead\n// ============================================================================\n\n/**\n * @deprecated Use {@link LocaleStorageClientOptions} or {@link LocaleStorageServerOptions} instead.\n */\nexport type LocaleStorageOptions = LocaleStorageClientOptions &\n LocaleStorageServerOptions;\n\n/**\n * Retrieves the locale from all storage mechanisms\n * (cookies, localStorage, sessionStorage, headers).\n *\n * @deprecated Use {@link getLocaleFromStorageClient} (browser) or\n * {@link getLocaleFromStorageServer} (server) instead.\n */\nexport const getLocaleFromStorage = (\n options: Pick<\n LocaleStorageOptions,\n | 'getCookie'\n | 'getSessionStorage'\n | 'getLocaleStorage'\n | 'getHeader'\n | 'isCookieEnabled'\n >\n): Locale | undefined => {\n const { locales } = internationalization;\n\n if (options?.isCookieEnabled === false) return undefined;\n\n const isValidLocale = (value: string | null | undefined): value is Locale =>\n !!value && locales.includes(value as Locale);\n\n const readCookie = (name: string): string | undefined => {\n try {\n const fromOption = options?.getCookie?.(name);\n if (fromOption !== null && fromOption !== undefined) return fromOption;\n } catch {}\n // Browser fallback kept for backward compatibility\n return getCookie(name);\n };\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const value = readCookie(routing.storage.cookies[i].name);\n if (isValidLocale(value)) return value;\n }\n }\n\n if (!TREE_SHAKE_STORAGE_LOCAL_STORAGE && routing.storage.localStorage) {\n for (let i = 0; i < routing.storage.localStorage.length; i++) {\n try {\n const value = options?.getLocaleStorage?.(\n routing.storage.localStorage[i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_SESSION_STORAGE && routing.storage.sessionStorage) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n try {\n const value = options?.getSessionStorage?.(\n routing.storage.sessionStorage[i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_HEADERS && routing.storage.headers) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n const value = options?.getHeader?.(routing.storage.headers[i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n};\n\n/**\n * Stores the locale in all configured storage mechanisms\n * (cookies, localStorage, sessionStorage, headers).\n *\n * @deprecated Use {@link setLocaleInStorageClient} (browser) or\n * {@link setLocaleInStorageServer} (server) instead.\n */\nexport const setLocaleInStorage = (\n locale: LocalesValues,\n options?: LocaleStorageOptions\n): void => {\n if (options?.isCookieEnabled === false) return;\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const { name, attributes } = routing.storage.cookies[i];\n try {\n if (options?.setCookieStore) {\n options.setCookieStore(name, locale, {\n ...attributes,\n expires:\n attributes.expires instanceof Date\n ? attributes.expires.getTime()\n : attributes.expires,\n });\n }\n } catch {\n try {\n if (options?.setCookieString) {\n options.setCookieString(\n name,\n buildCookieString(name, locale, attributes)\n );\n }\n } catch {}\n }\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_LOCAL_STORAGE &&\n routing.storage.localStorage &&\n options?.setLocaleStorage\n ) {\n for (let i = 0; i < routing.storage.localStorage.length; i++) {\n const { name } = routing.storage.localStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getLocaleStorage) {\n if (options.getLocaleStorage(name)) continue;\n }\n options.setLocaleStorage(name, locale);\n } catch {}\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_SESSION_STORAGE &&\n routing.storage.sessionStorage &&\n options?.setSessionStorage\n ) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n const { name } = routing.storage.sessionStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getSessionStorage) {\n if (options.getSessionStorage(name)) continue;\n }\n options.setSessionStorage(name, locale);\n } catch {}\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_HEADERS &&\n routing.storage.headers &&\n options?.setHeader\n ) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n options.setHeader(routing.storage.headers[i].name, locale);\n } catch {}\n }\n }\n};\n\n/**\n * Utility object to get and set the locale in storage based on configuration.\n *\n * @deprecated Use {@link LocaleStorageClient} (browser) or\n * {@link LocaleStorageServer} (server) instead.\n */\nexport const LocaleStorage = (options: LocaleStorageOptions) => ({\n getLocale: () => getLocaleFromStorage(options),\n setLocale: (locale: LocalesValues) => setLocaleInStorage(locale, options),\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"localeStorage.cjs","names":["internationalization","routing","getCookie"],"sources":["../../../src/utils/localeStorage.ts"],"sourcesContent":["import { internationalization, routing } from '@intlayer/config/built';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { CookiesAttributes } from '@intlayer/types/config';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getCookie } from './getCookie';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\n/**\n * True when cookie storage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_COOKIES =\n process.env['INTLAYER_ROUTING_STORAGE_COOKIES'] === 'false';\n\n/**\n * True when localStorage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_LOCAL_STORAGE =\n process.env['INTLAYER_ROUTING_STORAGE_LOCALSTORAGE'] === 'false';\n\n/**\n * True when sessionStorage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_SESSION_STORAGE =\n process.env['INTLAYER_ROUTING_STORAGE_SESSIONSTORAGE'] === 'false';\n\n/**\n * True when header storage is explicitly disabled at build time.\n */\nconst TREE_SHAKE_STORAGE_HEADERS =\n process.env['INTLAYER_ROUTING_STORAGE_HEADERS'] === 'false';\n\n// ============================================================================\n// Shared types\n// ============================================================================\n\nexport type CookieBuildAttributes = {\n domain?: string;\n path?: string;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: 'strict' | 'lax' | 'none';\n /** Expiry as milliseconds since epoch (Date.getTime()) or number of days */\n expires?: number | undefined;\n /** Lifetime in seconds from creation. Takes precedence over `expires` */\n maxAge?: number;\n};\n\n// ============================================================================\n// Shared helpers\n// ============================================================================\n\nconst buildCookieString = (\n name: string,\n value: string,\n attributes: Omit<CookiesAttributes, 'name' | 'type'>\n): string => {\n const encodedValue = encodeURIComponent(value);\n const parts: string[] = [`${name}=${encodedValue}`];\n\n if (attributes.path) parts.push(`Path=${attributes.path}`);\n if (attributes.domain) parts.push(`Domain=${attributes.domain}`);\n if (typeof attributes.maxAge === 'number')\n parts.push(`Max-Age=${Math.floor(attributes.maxAge)}`);\n else if (attributes.expires instanceof Date)\n parts.push(`Expires=${attributes.expires.toUTCString()}`);\n if (attributes.secure) parts.push('Secure');\n if (attributes.sameSite) parts.push(`SameSite=${attributes.sameSite}`);\n return parts.join('; ');\n};\n\n/**\n * Resolves the cookie expiry for the Cookie Store API (epoch milliseconds).\n * `maxAge` takes precedence and is converted to an absolute timestamp, as the\n * native `maxAge` option of `cookieStore.set()` is not yet supported in all\n * browsers that support `cookieStore` itself.\n */\nconst getCookieStoreExpires = (\n attributes: Omit<CookiesAttributes, 'name' | 'type'>\n): number | undefined => {\n if (typeof attributes.maxAge === 'number')\n return Date.now() + attributes.maxAge * 1000;\n return attributes.expires instanceof Date\n ? attributes.expires.getTime()\n : attributes.expires;\n};\n\n// ============================================================================\n// Client-specific types and functions\n// (cookies via browser APIs, localStorage, sessionStorage — no headers)\n// ============================================================================\n\nexport type LocaleStorageClientOptions = {\n overwrite?: boolean;\n isCookieEnabled?: boolean;\n setCookieStore?: (\n name: string,\n value: string,\n cookie: CookieBuildAttributes\n ) => void;\n setCookieString?: (name: string, cookie: string) => void;\n getCookie?: (name: string) => string | undefined | null;\n setSessionStorage?: (name: string, value: string) => void;\n getSessionStorage?: (name: string) => string | undefined | null;\n setLocaleStorage?: (name: string, value: string) => void;\n getLocaleStorage?: (name: string) => string | undefined | null;\n};\n\n// cookieStore is part of the experimental Cookie Store API\ndeclare const cookieStore: any;\n\nexport const localeStorageOptions: LocaleStorageClientOptions = {\n getCookie: (name: string) =>\n document.cookie\n .split(';')\n .find((c) => c.trim().startsWith(`${name}=`))\n ?.split('=')[1],\n getLocaleStorage: (name: string) => localStorage.getItem(name),\n getSessionStorage: (name: string) => sessionStorage.getItem(name),\n isCookieEnabled: true,\n setCookieStore: (name, value, attributes) =>\n cookieStore.set({\n name,\n value,\n path: attributes.path,\n domain: attributes.domain,\n expires: attributes.expires,\n sameSite: attributes.sameSite,\n }),\n setCookieString: (_name, cookie) => {\n // biome-ignore lint/suspicious/noDocumentCookie: set cookie fallback\n document.cookie = cookie;\n },\n setSessionStorage: (name, value) => sessionStorage.setItem(name, value),\n setLocaleStorage: (name, value) => localStorage.setItem(name, value),\n};\n\n/**\n * Retrieves the locale from browser storage mechanisms\n * (cookies, localStorage, sessionStorage).\n * Does not read from headers — use `getLocaleFromStorageServer` for that.\n */\nexport const getLocaleFromStorageClient = (\n options: LocaleStorageClientOptions = localeStorageOptions\n): Locale | undefined => {\n const { locales } = internationalization;\n\n if (options?.isCookieEnabled === false) return undefined;\n\n const isValidLocale = (value: string | null | undefined): value is Locale =>\n !!value && locales.includes(value as Locale);\n\n if (!TREE_SHAKE_STORAGE_COOKIES) {\n for (let i = 0; i < (routing.storage.cookies ?? []).length; i++) {\n try {\n const value = options?.getCookie?.(routing.storage.cookies![i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_LOCAL_STORAGE) {\n for (let i = 0; i < (routing.storage.localStorage ?? []).length; i++) {\n try {\n const value = options?.getLocaleStorage?.(\n routing.storage.localStorage![i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_SESSION_STORAGE && routing.storage.sessionStorage) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n try {\n const value = options?.getSessionStorage?.(\n routing.storage.sessionStorage[i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n};\n\n/**\n * Stores the locale in browser storage mechanisms\n * (cookies, localStorage, sessionStorage).\n * Does not write to headers — use `setLocaleInStorageServer` for that.\n */\nexport const setLocaleInStorageClient = (\n locale: LocalesValues,\n options?: LocaleStorageClientOptions\n): void => {\n if (options?.isCookieEnabled === false) return;\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const { name, attributes } = routing.storage.cookies[i];\n try {\n if (options?.setCookieStore) {\n options.setCookieStore(name, locale, {\n ...attributes,\n expires: getCookieStoreExpires(attributes),\n });\n }\n } catch {\n try {\n if (options?.setCookieString) {\n options.setCookieString(\n name,\n buildCookieString(name, locale, attributes)\n );\n }\n } catch {}\n }\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_LOCAL_STORAGE &&\n routing.storage.localStorage &&\n options?.setLocaleStorage\n ) {\n for (let i = 0; i < routing.storage.localStorage.length; i++) {\n const { name } = routing.storage.localStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getLocaleStorage) {\n if (options.getLocaleStorage(name)) continue;\n }\n options.setLocaleStorage(name, locale);\n } catch {}\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_SESSION_STORAGE &&\n routing.storage.sessionStorage &&\n options?.setSessionStorage\n ) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n const { name } = routing.storage.sessionStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getSessionStorage) {\n if (options.getSessionStorage(name)) continue;\n }\n options.setSessionStorage(name, locale);\n } catch {}\n }\n }\n};\n\n/**\n * Client-side locale storage utility.\n * Handles cookies (browser), localStorage and sessionStorage.\n * Does not access headers.\n *\n * @example\n * ```ts\n * const storage = LocaleStorageClient(localeStorageOptions);\n * const locale = storage.getLocale();\n * storage.setLocale('fr');\n * ```\n */\nexport const LocaleStorageClient = (options: LocaleStorageClientOptions) => ({\n getLocale: () => getLocaleFromStorageClient(options),\n setLocale: (locale: LocalesValues) =>\n setLocaleInStorageClient(locale, options),\n});\n\n// ============================================================================\n// Server-specific types and functions\n// (cookies via injected getter/setter, headers — no localStorage/sessionStorage)\n// ============================================================================\n\nexport type LocaleStorageServerOptions = {\n overwrite?: boolean;\n isCookieEnabled?: boolean;\n setCookieStore?: (\n name: string,\n value: string,\n cookie: CookieBuildAttributes\n ) => void;\n setCookieString?: (name: string, cookie: string) => void;\n getCookie?: (name: string) => string | undefined | null;\n getHeader?: (name: string) => string | undefined | null;\n setHeader?: (name: string, value: string) => void;\n};\n\n/**\n * Retrieves the locale from server-side storage mechanisms (cookies, headers).\n * Does not access localStorage or sessionStorage.\n * No browser cookie fallback — the caller must provide `getCookie`.\n */\nexport const getLocaleFromStorageServer = (\n options: LocaleStorageServerOptions\n): Locale | undefined => {\n const { locales } = internationalization;\n\n if (options?.isCookieEnabled === false) return undefined;\n\n const isValidLocale = (value: string | null | undefined): value is Locale =>\n !!value && locales.includes(value as Locale);\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n try {\n const value = options?.getCookie?.(routing.storage.cookies[i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_HEADERS && routing.storage.headers) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n const value = options?.getHeader?.(routing.storage.headers[i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n};\n\n/**\n * Stores the locale in server-side storage mechanisms (cookies, headers).\n * Does not write to localStorage or sessionStorage.\n */\nexport const setLocaleInStorageServer = (\n locale: LocalesValues,\n options?: LocaleStorageServerOptions\n): void => {\n if (options?.isCookieEnabled === false) return;\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const { name, attributes } = routing.storage.cookies[i];\n\n try {\n if (options?.setCookieStore) {\n options.setCookieStore(name, locale, {\n ...attributes,\n expires: getCookieStoreExpires(attributes),\n });\n }\n } catch {\n try {\n if (options?.setCookieString) {\n options.setCookieString(\n name,\n buildCookieString(name, locale, attributes)\n );\n }\n } catch {}\n }\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_HEADERS &&\n routing.storage.headers &&\n options?.setHeader\n ) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n options.setHeader(routing.storage.headers[i].name, locale);\n } catch {}\n }\n }\n};\n\n/**\n * Server-side locale storage utility.\n * Handles cookies (via injected getter/setter) and headers.\n * Does not access localStorage or sessionStorage.\n *\n * @example\n * ```ts\n * const storage = LocaleStorageServer({\n * getCookie: (name) => req.cookies[name],\n * setCookieStore: (name, value, attrs) => res.cookie(name, value, attrs),\n * getHeader: (name) => req.headers[name],\n * setHeader: (name, value) => res.setHeader(name, value),\n * });\n * const locale = storage.getLocale();\n * storage.setLocale('fr');\n * ```\n */\nexport const LocaleStorageServer = (options: LocaleStorageServerOptions) => ({\n getLocale: () => getLocaleFromStorageServer(options),\n setLocale: (locale: LocalesValues) =>\n setLocaleInStorageServer(locale, options),\n});\n\n// ============================================================================\n// Deprecated: combined LocaleStorage\n// Use LocaleStorageClient or LocaleStorageServer instead\n// ============================================================================\n\n/**\n * @deprecated Use {@link LocaleStorageClientOptions} or {@link LocaleStorageServerOptions} instead.\n */\nexport type LocaleStorageOptions = LocaleStorageClientOptions &\n LocaleStorageServerOptions;\n\n/**\n * Retrieves the locale from all storage mechanisms\n * (cookies, localStorage, sessionStorage, headers).\n *\n * @deprecated Use {@link getLocaleFromStorageClient} (browser) or\n * {@link getLocaleFromStorageServer} (server) instead.\n */\nexport const getLocaleFromStorage = (\n options: Pick<\n LocaleStorageOptions,\n | 'getCookie'\n | 'getSessionStorage'\n | 'getLocaleStorage'\n | 'getHeader'\n | 'isCookieEnabled'\n >\n): Locale | undefined => {\n const { locales } = internationalization;\n\n if (options?.isCookieEnabled === false) return undefined;\n\n const isValidLocale = (value: string | null | undefined): value is Locale =>\n !!value && locales.includes(value as Locale);\n\n const readCookie = (name: string): string | undefined => {\n try {\n const fromOption = options?.getCookie?.(name);\n if (fromOption !== null && fromOption !== undefined) return fromOption;\n } catch {}\n // Browser fallback kept for backward compatibility\n return getCookie(name);\n };\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const value = readCookie(routing.storage.cookies[i].name);\n if (isValidLocale(value)) return value;\n }\n }\n\n if (!TREE_SHAKE_STORAGE_LOCAL_STORAGE && routing.storage.localStorage) {\n for (let i = 0; i < routing.storage.localStorage.length; i++) {\n try {\n const value = options?.getLocaleStorage?.(\n routing.storage.localStorage[i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_SESSION_STORAGE && routing.storage.sessionStorage) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n try {\n const value = options?.getSessionStorage?.(\n routing.storage.sessionStorage[i].name\n );\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n\n if (!TREE_SHAKE_STORAGE_HEADERS && routing.storage.headers) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n const value = options?.getHeader?.(routing.storage.headers[i].name);\n if (isValidLocale(value)) return value;\n } catch {}\n }\n }\n};\n\n/**\n * Stores the locale in all configured storage mechanisms\n * (cookies, localStorage, sessionStorage, headers).\n *\n * @deprecated Use {@link setLocaleInStorageClient} (browser) or\n * {@link setLocaleInStorageServer} (server) instead.\n */\nexport const setLocaleInStorage = (\n locale: LocalesValues,\n options?: LocaleStorageOptions\n): void => {\n if (options?.isCookieEnabled === false) return;\n\n if (!TREE_SHAKE_STORAGE_COOKIES && routing.storage.cookies) {\n for (let i = 0; i < routing.storage.cookies.length; i++) {\n const { name, attributes } = routing.storage.cookies[i];\n try {\n if (options?.setCookieStore) {\n options.setCookieStore(name, locale, {\n ...attributes,\n expires: getCookieStoreExpires(attributes),\n });\n }\n } catch {\n try {\n if (options?.setCookieString) {\n options.setCookieString(\n name,\n buildCookieString(name, locale, attributes)\n );\n }\n } catch {}\n }\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_LOCAL_STORAGE &&\n routing.storage.localStorage &&\n options?.setLocaleStorage\n ) {\n for (let i = 0; i < routing.storage.localStorage.length; i++) {\n const { name } = routing.storage.localStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getLocaleStorage) {\n if (options.getLocaleStorage(name)) continue;\n }\n options.setLocaleStorage(name, locale);\n } catch {}\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_SESSION_STORAGE &&\n routing.storage.sessionStorage &&\n options?.setSessionStorage\n ) {\n for (let i = 0; i < routing.storage.sessionStorage.length; i++) {\n const { name } = routing.storage.sessionStorage[i];\n try {\n if (!(options?.overwrite ?? true) && options?.getSessionStorage) {\n if (options.getSessionStorage(name)) continue;\n }\n options.setSessionStorage(name, locale);\n } catch {}\n }\n }\n\n if (\n !TREE_SHAKE_STORAGE_HEADERS &&\n routing.storage.headers &&\n options?.setHeader\n ) {\n for (let i = 0; i < routing.storage.headers.length; i++) {\n try {\n options.setHeader(routing.storage.headers[i].name, locale);\n } catch {}\n }\n }\n};\n\n/**\n * Utility object to get and set the locale in storage based on configuration.\n *\n * @deprecated Use {@link LocaleStorageClient} (browser) or\n * {@link LocaleStorageServer} (server) instead.\n */\nexport const LocaleStorage = (options: LocaleStorageOptions) => ({\n getLocale: () => getLocaleFromStorage(options),\n setLocale: (locale: LocalesValues) => setLocaleInStorage(locale, options),\n});\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getLocaleLang.mjs","names":[],"sources":["../../../src/localization/getLocaleLang.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"getLocaleLang.mjs","names":[],"sources":["../../../src/localization/getLocaleLang.ts"],"sourcesContent":["import type {\n GetLocaleLang,\n LocalesValues,\n} from '@intlayer/types/module_augmentation';\n\n/**\n * Returns the language code of the given locale for locales including the country code.\n *\n * Example:\n *\n * getLocaleLang('en-US') // 'en'\n * getLocaleLang('en') // 'en'\n * getLocaleLang('fr-CA') // 'fr'\n * getLocaleLang('fr') // 'fr'\n *\n * @param locale The locale to get the language code for.\n * @returns The language code of the given locale.\n */\nexport const getLocaleLang = <const L extends LocalesValues>(\n locale?: L\n): GetLocaleLang<L & string> =>\n (locale?.split('-')[0] ?? '') as GetLocaleLang<L & string>;\n"],"mappings":";;;;;;;;;;;;;;AAkBA,MAAa,iBACX,WAEC,QAAQ,MAAM,GAAG,EAAE,MAAM"}
|
|
@@ -52,6 +52,11 @@ const extractHostname = (domain) => {
|
|
|
52
52
|
* Auto-detected from the input URL or `window.location` when omitted.
|
|
53
53
|
* @returns The localized URL for the current locale.
|
|
54
54
|
*/
|
|
55
|
+
/**
|
|
56
|
+
* The return type is narrowed to a template-literal type when both `url` and
|
|
57
|
+
* `currentLocale` are string literals and the routing mode / defaultLocale are
|
|
58
|
+
* not overridden via `options`.
|
|
59
|
+
*/
|
|
55
60
|
const getLocalizedUrl = (url, currentLocale = internationalization?.defaultLocale, options = {}) => {
|
|
56
61
|
const { defaultLocale, mode, locales, rewrite, domains, currentDomain } = resolveRoutingConfig(options);
|
|
57
62
|
const urlWithoutLocale = getPathWithoutLocale(url, locales);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getLocalizedUrl.mjs","names":[],"sources":["../../../src/localization/getLocalizedUrl.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type {
|
|
1
|
+
{"version":3,"file":"getLocalizedUrl.mjs","names":[],"sources":["../../../src/localization/getLocalizedUrl.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type {\n LocalesValues,\n LocalizedUrl,\n ResolvedDefaultLocale,\n} from '@intlayer/types/module_augmentation';\nimport { checkIsURLAbsolute } from '../utils/checkIsURLAbsolute';\nimport { getPathWithoutLocale } from './getPathWithoutLocale';\nimport {\n getPrefix,\n type RoutingOptions,\n resolveRoutingConfig,\n} from './getPrefix';\nimport {\n getCanonicalPath,\n getLocalizedPath,\n getRewriteRules,\n} from './rewriteUtils';\n\nexport type { RoutingOptions };\n\n/** Strips the protocol and returns the bare hostname of a domain string. */\nconst extractHostname = (domain: string): string => {\n try {\n return /^https?:\\/\\//.test(domain) ? new URL(domain).hostname : domain;\n } catch {\n return domain;\n }\n};\n\n/**\n * Generate URL by prefixing the given URL with the referenced locale or adding search parameters\n * based on the routing mode. Handles both absolute and relative URLs appropriately.\n *\n * This function gets the locales, default locale, and routing mode from the configuration if not provided.\n *\n * Example:\n *\n * ```ts\n * // prefix-no-default mode\n * getLocalizedUrl('/about', 'fr', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'prefix-no-default' });\n * // Returns '/fr/about' for the French locale\n * // Returns '/about' for the English locale (default)\n *\n * // prefix-all mode\n * getLocalizedUrl('/about', 'en', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'prefix-all' });\n * // Returns '/en/about' for the English locale\n * // Returns '/fr/about' for the French locale\n *\n * // search-params mode\n * getLocalizedUrl('/about', 'fr', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'search-params' });\n * // Returns '/about?locale=fr' for the French locale\n *\n * // no-prefix mode\n * getLocalizedUrl('/about', 'fr', { locales: ['en', 'fr'], defaultLocale: 'en', mode: 'no-prefix' });\n * // Returns '/about' for any locale\n * ```\n *\n * @param url - The original URL string to be processed.\n * @param currentLocale - The current locale.\n * @param options - Configuration options\n * @param options.locales - Optional array of supported locales. Defaults to configured locales.\n * @param options.defaultLocale - The default locale. Defaults to configured default locale.\n * @param options.mode - URL routing mode for locale handling. Defaults to configured mode.\n * @param options.currentDomain - Hostname of the page being rendered. Used to decide\n * whether to emit a relative URL (same domain) or an absolute URL (cross-domain).\n * Auto-detected from the input URL or `window.location` when omitted.\n * @returns The localized URL for the current locale.\n */\n/**\n * The return type is narrowed to a template-literal type when both `url` and\n * `currentLocale` are string literals and the routing mode / defaultLocale are\n * not overridden via `options`.\n */\nexport const getLocalizedUrl = <\n const T extends string,\n const L extends LocalesValues = ResolvedDefaultLocale,\n>(\n url: T,\n currentLocale: L = internationalization?.defaultLocale as L,\n options: RoutingOptions = {}\n): LocalizedUrl<T, L> => {\n const { defaultLocale, mode, locales, rewrite, domains, currentDomain } =\n resolveRoutingConfig(options);\n\n const urlWithoutLocale = getPathWithoutLocale(url, locales);\n const rewriteRules = getRewriteRules(rewrite, 'url');\n\n if (\n !(\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'no-prefix'\n ) &&\n mode === 'no-prefix'\n ) {\n return getLocalizedPath(\n getCanonicalPath(urlWithoutLocale, undefined, rewriteRules),\n currentLocale as Locale,\n rewriteRules\n ).path as LocalizedUrl<T, L>;\n }\n\n const isAbsoluteUrl = checkIsURLAbsolute(urlWithoutLocale);\n const parsedUrl = isAbsoluteUrl\n ? new URL(urlWithoutLocale)\n : new URL(urlWithoutLocale, 'http://example.com');\n\n const translatedPathname = getLocalizedPath(\n getCanonicalPath(parsedUrl.pathname, undefined, rewriteRules),\n currentLocale as Locale,\n rewriteRules\n ).path;\n\n // ── Domain routing ────────────────────────────────────────────────────────\n // Resolve the \"current\" hostname so we can choose between a relative URL\n // (same domain) and an absolute URL (cross-domain).\n //\n // Detection priority:\n // 1. Explicit `currentDomain` option passed by the caller.\n // 2. Hostname extracted from an absolute input URL.\n // 3. `window.location.hostname` in browser environments.\n // When none of these is available we fall back to always generating an\n // absolute URL (the previous behaviour, safe for SSR/static generation).\n const detectedCurrentHostname =\n process.env['INTLAYER_ROUTING_DOMAINS'] !== 'false'\n ? extractHostname(\n currentDomain ??\n (isAbsoluteUrl ? parsedUrl.hostname : undefined) ??\n (typeof window !== 'undefined'\n ? window?.location?.hostname\n : undefined) ??\n ''\n ) || null\n : null;\n\n const localeDomain =\n process.env['INTLAYER_ROUTING_DOMAINS'] !== 'false'\n ? domains?.[currentLocale as LocalesValues]\n : undefined;\n\n const localeDomainHostname = localeDomain\n ? extractHostname(localeDomain)\n : null;\n\n // Only prepend the locale's domain when it differs from the current hostname.\n const isCrossDomain =\n localeDomainHostname !== null &&\n detectedCurrentHostname !== null &&\n localeDomainHostname !== detectedCurrentHostname;\n\n const normalizedDomain =\n isCrossDomain && localeDomain\n ? /^https?:\\/\\//.test(localeDomain)\n ? localeDomain\n : `https://${localeDomain}`\n : null;\n\n const baseUrl = normalizedDomain\n ? normalizedDomain\n : isAbsoluteUrl\n ? `${parsedUrl.protocol}//${parsedUrl.host}`\n : '';\n // ─────────────────────────────────────────────────────────────────────────\n\n if (\n !(\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'search-params'\n ) &&\n mode === 'search-params'\n ) {\n const searchParams = new URLSearchParams(parsedUrl.search);\n\n searchParams.set('locale', currentLocale.toString());\n\n const queryParams = searchParams.toString();\n const path = queryParams\n ? `${translatedPathname}?${queryParams}`\n : translatedPathname;\n\n return `${baseUrl}${path}${parsedUrl.hash}` as LocalizedUrl<T, L>;\n }\n\n const { prefix } = getPrefix(currentLocale, {\n defaultLocale,\n mode,\n locales,\n domains,\n });\n\n let localizedPath = `/${prefix}${translatedPathname}`.replace(/\\/+/g, '/');\n\n if (localizedPath.length > 1 && localizedPath.endsWith('/')) {\n localizedPath = localizedPath.slice(0, -1);\n }\n\n return `${baseUrl}${localizedPath}${parsedUrl.search}${parsedUrl.hash}` as LocalizedUrl<\n T,\n L\n >;\n};\n"],"mappings":";;;;;;;;AA4BA,MAAM,mBAAmB,WAA2B;CAClD,IAAI;EACF,OAAO,eAAe,KAAK,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,WAAW;CAClE,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAa,mBAIX,KACA,gBAAmB,sBAAsB,eACzC,UAA0B,CAAC,MACJ;CACvB,MAAM,EAAE,eAAe,MAAM,SAAS,SAAS,SAAS,kBACtD,qBAAqB,OAAO;CAE9B,MAAM,mBAAmB,qBAAqB,KAAK,OAAO;CAC1D,MAAM,eAAe,gBAAgB,SAAS,KAAK;CAEnD,IACE,EACE,QAAQ,IAAI,4BACZ,QAAQ,IAAI,6BAA6B,gBAE3C,SAAS,aAET,OAAO,iBACL,iBAAiB,kBAAkB,QAAW,YAAY,GAC1D,eACA,YACF,EAAE;CAGJ,MAAM,gBAAgB,mBAAmB,gBAAgB;CACzD,MAAM,YAAY,gBACd,IAAI,IAAI,gBAAgB,IACxB,IAAI,IAAI,kBAAkB,oBAAoB;CAElD,MAAM,qBAAqB,iBACzB,iBAAiB,UAAU,UAAU,QAAW,YAAY,GAC5D,eACA,YACF,EAAE;CAYF,MAAM,0BACJ,QAAQ,IAAI,gCAAgC,UACxC,gBACE,kBACG,gBAAgB,UAAU,WAAW,YACrC,OAAO,WAAW,cACf,QAAQ,UAAU,WAClB,WACJ,EACJ,KAAK,OACL;CAEN,MAAM,eACJ,QAAQ,IAAI,gCAAgC,UACxC,UAAU,iBACV;CAEN,MAAM,uBAAuB,eACzB,gBAAgB,YAAY,IAC5B;CAQJ,MAAM,mBAJJ,yBAAyB,QACzB,4BAA4B,QAC5B,yBAAyB,2BAGR,eACb,eAAe,KAAK,YAAY,IAC9B,eACA,WAAW,iBACb;CAEN,MAAM,UAAU,mBACZ,mBACA,gBACE,GAAG,UAAU,SAAS,IAAI,UAAU,SACpC;CAGN,IACE,EACE,QAAQ,IAAI,4BACZ,QAAQ,IAAI,6BAA6B,oBAE3C,SAAS,iBACT;EACA,MAAM,eAAe,IAAI,gBAAgB,UAAU,MAAM;EAEzD,aAAa,IAAI,UAAU,cAAc,SAAS,CAAC;EAEnD,MAAM,cAAc,aAAa,SAAS;EAK1C,OAAO,GAAG,UAJG,cACT,GAAG,mBAAmB,GAAG,gBACzB,qBAEuB,UAAU;CACvC;CAEA,MAAM,EAAE,WAAW,UAAU,eAAe;EAC1C;EACA;EACA;EACA;CACF,CAAC;CAED,IAAI,gBAAgB,IAAI,SAAS,qBAAqB,QAAQ,QAAQ,GAAG;CAEzE,IAAI,cAAc,SAAS,KAAK,cAAc,SAAS,GAAG,GACxD,gBAAgB,cAAc,MAAM,GAAG,EAAE;CAG3C,OAAO,GAAG,UAAU,gBAAgB,UAAU,SAAS,UAAU;AAInE"}
|
|
@@ -35,6 +35,11 @@ const TREE_SHAKE_SEARCH_PARAMS = process.env["INTLAYER_ROUTING_MODE"] && process
|
|
|
35
35
|
* @param locales - Optional array of supported locales. Defaults to `localesDefault`.
|
|
36
36
|
* @returns The URL string or pathname without the locale segment or locale search parameter.
|
|
37
37
|
*/
|
|
38
|
+
/**
|
|
39
|
+
* The return type is narrowed to a template-literal type when both `inputUrl`
|
|
40
|
+
* and `locales` are string literals known at compile time. Absolute URLs fall
|
|
41
|
+
* back to `string`.
|
|
42
|
+
*/
|
|
38
43
|
const getPathWithoutLocale = (inputUrl, locales = internationalization?.locales) => {
|
|
39
44
|
const isAbsoluteUrl = checkIsURLAbsolute(inputUrl);
|
|
40
45
|
let fixedInputUrl = inputUrl;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getPathWithoutLocale.mjs","names":[],"sources":["../../../src/localization/getPathWithoutLocale.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\n/**\n * True when the build-time routing mode is known and is not a prefix-based\n * mode (neither 'prefix-all' nor 'prefix-no-default').\n */\nconst TREE_SHAKE_PREFIX_MODES =\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'prefix-all' &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'prefix-no-default';\n\n/**\n * True when the build-time routing mode is known and is NOT 'search-params'.\n */\nconst TREE_SHAKE_SEARCH_PARAMS =\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'search-params';\n\nimport type {
|
|
1
|
+
{"version":3,"file":"getPathWithoutLocale.mjs","names":[],"sources":["../../../src/localization/getPathWithoutLocale.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\n/**\n * True when the build-time routing mode is known and is not a prefix-based\n * mode (neither 'prefix-all' nor 'prefix-no-default').\n */\nconst TREE_SHAKE_PREFIX_MODES =\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'prefix-all' &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'prefix-no-default';\n\n/**\n * True when the build-time routing mode is known and is NOT 'search-params'.\n */\nconst TREE_SHAKE_SEARCH_PARAMS =\n process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'search-params';\n\nimport type {\n DeclaredLocales,\n LocalesValues,\n PathWithoutLocale,\n} from '@intlayer/types/module_augmentation';\nimport { checkIsURLAbsolute } from '../utils/checkIsURLAbsolute';\n\n/**\n * Removes the locale segment from the given URL or pathname if present.\n * Also removes locale from search parameters if present.\n *\n * This function get the locales from the configuration if not provided.\n *\n * Example:\n *\n * ```ts\n * getPathWithoutLocale('/en/dashboard') // Returns '/dashboard'\n * getPathWithoutLocale('/fr/dashboard') // Returns '/dashboard'\n * getPathWithoutLocale('/dashboard') // Returns '/dashboard'\n * getPathWithoutLocale('dashboard') // Returns 'dashboard'\n * getPathWithoutLocale('/dashboard?locale=fr') // Returns '/dashboard'\n * getPathWithoutLocale('https://example.com/en/dashboard') // Returns 'https://example.com/dashboard'\n * getPathWithoutLocale('https://example.com/fr/dashboard') // Returns 'https://example.com/dashboard'\n * getPathWithoutLocale('https://example.com/dashboard') // Returns 'https://example.com/dashboard'\n * getPathWithoutLocale('https://example.com/dashboard?locale=fr') // Returns 'https://example.com/dashboard'\n * ```\n *\n * @param inputUrl - The complete URL string or pathname to process.\n * @param locales - Optional array of supported locales. Defaults to `localesDefault`.\n * @returns The URL string or pathname without the locale segment or locale search parameter.\n */\n/**\n * The return type is narrowed to a template-literal type when both `inputUrl`\n * and `locales` are string literals known at compile time. Absolute URLs fall\n * back to `string`.\n */\nexport const getPathWithoutLocale = <\n const T extends string,\n const L extends LocalesValues = DeclaredLocales,\n>(\n inputUrl: T,\n locales: L[] = internationalization?.locales as L[]\n): PathWithoutLocale<T, L> => {\n // Determine if the original URL is absolute (includes protocol)\n const isAbsoluteUrl = checkIsURLAbsolute(inputUrl);\n\n let fixedInputUrl = inputUrl;\n\n if (inputUrl?.endsWith('/')) {\n fixedInputUrl = inputUrl.slice(0, -1);\n }\n\n // Initialize a URL object if the URL is absolute\n // For relative URLs, use a dummy base to leverage the URL API\n const url = isAbsoluteUrl\n ? new URL(fixedInputUrl)\n : new URL(fixedInputUrl, 'http://example.com');\n\n const pathname = url.pathname;\n\n // Ensure the pathname starts with '/'\n if (!pathname.startsWith('/')) {\n // If not, return the URL as is\n url.pathname = `/${pathname}`;\n }\n\n // Only strip locale path prefix in prefix-based routing modes\n if (!TREE_SHAKE_PREFIX_MODES) {\n // Split the pathname to extract the first segment\n const pathSegments = pathname.split('/');\n const firstSegment = pathSegments[1]; // The segment after the first '/'\n\n // Check if the first segment is a supported locale\n if (locales?.includes(firstSegment as L)) {\n // Remove the locale segment from the pathname\n pathSegments.splice(1, 1); // Remove the first segment\n\n // Reconstruct the pathname\n const newPathname = pathSegments.join('/') ?? '/';\n url.pathname = newPathname;\n }\n }\n\n // Only strip locale from search parameters in search-params routing mode\n if (!TREE_SHAKE_SEARCH_PARAMS) {\n const searchParams = new URLSearchParams(url.search);\n if (searchParams.has('locale')) {\n searchParams.delete('locale');\n url.search = searchParams.toString();\n }\n }\n\n if (isAbsoluteUrl) {\n return url.toString() as PathWithoutLocale<T, L>;\n }\n\n return url.toString().replace('http://example.com', '') as PathWithoutLocale<\n T,\n L\n >;\n};\n"],"mappings":";;;;;;;;AAUA,MAAM,0BACJ,QAAQ,IAAI,4BACZ,QAAQ,IAAI,6BAA6B,gBACzC,QAAQ,IAAI,6BAA6B;;;;AAK3C,MAAM,2BACJ,QAAQ,IAAI,4BACZ,QAAQ,IAAI,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsC3C,MAAa,wBAIX,UACA,UAAe,sBAAsB,YACT;CAE5B,MAAM,gBAAgB,mBAAmB,QAAQ;CAEjD,IAAI,gBAAgB;CAEpB,IAAI,UAAU,SAAS,GAAG,GACxB,gBAAgB,SAAS,MAAM,GAAG,EAAE;CAKtC,MAAM,MAAM,gBACR,IAAI,IAAI,aAAa,IACrB,IAAI,IAAI,eAAe,oBAAoB;CAE/C,MAAM,WAAW,IAAI;CAGrB,IAAI,CAAC,SAAS,WAAW,GAAG,GAE1B,IAAI,WAAW,IAAI;CAIrB,IAAI,CAAC,yBAAyB;EAE5B,MAAM,eAAe,SAAS,MAAM,GAAG;EACvC,MAAM,eAAe,aAAa;EAGlC,IAAI,SAAS,SAAS,YAAiB,GAAG;GAExC,aAAa,OAAO,GAAG,CAAC;GAIxB,IAAI,WADgB,aAAa,KAAK,GAAG,KAAK;EAEhD;CACF;CAGA,IAAI,CAAC,0BAA0B;EAC7B,MAAM,eAAe,IAAI,gBAAgB,IAAI,MAAM;EACnD,IAAI,aAAa,IAAI,QAAQ,GAAG;GAC9B,aAAa,OAAO,QAAQ;GAC5B,IAAI,SAAS,aAAa,SAAS;EACrC;CACF;CAEA,IAAI,eACF,OAAO,IAAI,SAAS;CAGtB,OAAO,IAAI,SAAS,EAAE,QAAQ,sBAAsB,EAAE;AAIxD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getPrefix.mjs","names":[],"sources":["../../../src/localization/getPrefix.ts"],"sourcesContent":["import { internationalization, routing } from '@intlayer/config/built';\nimport {\n DEFAULT_LOCALE,\n LOCALES,\n ROUTING_MODE,\n} from '@intlayer/config/defaultValues';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { RoutingConfig } from '@intlayer/types/config';\nimport type {
|
|
1
|
+
{"version":3,"file":"getPrefix.mjs","names":[],"sources":["../../../src/localization/getPrefix.ts"],"sourcesContent":["import { internationalization, routing } from '@intlayer/config/built';\nimport {\n DEFAULT_LOCALE,\n LOCALES,\n ROUTING_MODE,\n} from '@intlayer/config/defaultValues';\n\n// ── Tree-shake constants ──────────────────────────────────────────────────────\n// When these env vars are injected at build time, bundlers eliminate the\n// branches guarded by these constants.\n\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type { RoutingConfig } from '@intlayer/types/config';\nimport type {\n DeclaredLocales,\n LocalesValues,\n ResolvedDefaultLocale,\n ResolvedRoutingMode,\n} from '@intlayer/types/module_augmentation';\n\n/**\n * Shared routing options used across all URL localization functions.\n */\nexport type RoutingOptions = {\n locales?: LocalesValues[];\n defaultLocale?: LocalesValues;\n mode?: RoutingConfig['mode'];\n rewrite?: RoutingConfig['rewrite'];\n domains?: RoutingConfig['domains'];\n /**\n * The hostname of the page currently being rendered (e.g. `'intlayer.org'`).\n * When provided, `getLocalizedUrl` returns a relative URL for locales whose\n * configured domain matches `currentDomain`, and an absolute URL only when\n * the target locale lives on a different domain.\n *\n * When omitted the function tries to infer it from:\n * 1. The domain of an absolute input URL.\n * 2. `window.location.hostname` in browser environments.\n * Falls back to always generating absolute URLs when neither is available.\n */\n currentDomain?: string;\n};\n\n/**\n * Resolves routing configuration by merging provided options with configuration defaults.\n * Single source of truth for default routing config resolution across all localization functions.\n */\nexport const resolveRoutingConfig = (options: RoutingOptions = {}) => ({\n defaultLocale: internationalization?.defaultLocale ?? DEFAULT_LOCALE,\n mode: routing?.mode ?? ROUTING_MODE,\n locales: internationalization?.locales ?? LOCALES,\n rewrite: routing?.rewrite,\n domains: routing?.domains,\n ...options,\n});\n\nexport type GetPrefixOptions = {\n defaultLocale?: LocalesValues;\n mode?: RoutingConfig['mode'];\n};\n\nexport type GetPrefixResult = {\n /**\n * The locale path segment appended to `/`, with a trailing slash (e.g. `'fr/'`).\n * Empty string when no prefix is needed.\n */\n prefix: string;\n /**\n * The bare locale identifier (e.g. `'fr'`), or `undefined` when no prefix is applied.\n */\n localePrefix: Locale | undefined;\n};\n\n/**\n * Narrowed return type for {@link getPrefix} that carries the locale literal through.\n *\n * Distributes over union locales — calling `getPrefix('fr')` in `prefix-no-default`\n * mode with `defaultLocale = 'en'` resolves to `{ prefix: 'fr/'; localePrefix: 'fr' }`.\n *\n * Note: domain-based routing and \"locale not in locales\" edge cases may return an\n * empty result at runtime regardless of what this type reports.\n */\nexport type GetPrefixResultNarrowed<\n L extends LocalesValues | undefined,\n Mode extends string = ResolvedRoutingMode,\n Default extends LocalesValues = ResolvedDefaultLocale,\n> = L extends string\n ? [string] extends [L] // L is wide (string / LocalesValues) → distribute over declared locales\n ? GetPrefixResultNarrowed<DeclaredLocales, Mode, Default>\n : [string] extends [Mode]\n ? GetPrefixResult // mode is wide → fall back to generic result\n : Mode extends 'prefix-all'\n ? { prefix: `${L}/`; localePrefix: L }\n : Mode extends 'prefix-no-default'\n ? L extends Default\n ? { prefix: ''; localePrefix: undefined }\n : { prefix: `${L}/`; localePrefix: L }\n : { prefix: ''; localePrefix: undefined } // no-prefix / search-params\n : { prefix: ''; localePrefix: undefined }; // locale is undefined\n\n/**\n * Determines the URL prefix for a given locale based on the routing mode configuration.\n *\n * Example:\n *\n * ```ts\n * // prefix-no-default mode with default locale\n * getPrefix('en', { defaultLocale: 'en', mode: 'prefix-no-default' })\n * // Returns { prefix: '', localePrefix: undefined }\n *\n * // prefix-no-default mode with non-default locale\n * getPrefix('fr', { defaultLocale: 'en', mode: 'prefix-no-default' })\n * // Returns { prefix: '/fr', localePrefix: 'fr' }\n *\n * // prefix-all mode\n * getPrefix('en', { defaultLocale: 'en', mode: 'prefix-all' })\n * // Returns { prefix: '/en', localePrefix: locale }\n *\n * // search-params mode\n * getPrefix('en', { defaultLocale: 'en', mode: 'search-params' })\n * // Returns { prefix: '', localePrefix: undefined }\n *\n * // no-prefix mode\n * getPrefix('en', { defaultLocale: 'en', mode: 'no-prefix' })\n * // Returns { prefix: '', localePrefix: undefined }\n * ```\n *\n * @param locale - The locale to check for prefix. If not provided, uses configured default locale.\n * @param options - Configuration options\n * @param options.defaultLocale - The default locale. Defaults to configured default locale.\n * @param options.mode - URL routing mode for locale handling. Defaults to configured mode.\n * @returns An object containing pathPrefix, prefix, and localePrefix for the given locale.\n */\nexport const getPrefix = <const L extends LocalesValues | undefined>(\n locale: L,\n options: RoutingOptions = {}\n): GetPrefixResultNarrowed<L> => {\n const { defaultLocale, mode, locales, domains } =\n resolveRoutingConfig(options);\n\n if (\n (process.env['INTLAYER_ROUTING_MODE'] &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'prefix-all' &&\n process.env['INTLAYER_ROUTING_MODE'] !== 'prefix-no-default') ||\n !locale ||\n !locales.includes(locale)\n ) {\n return {\n prefix: '',\n localePrefix: undefined,\n } as GetPrefixResultNarrowed<L>;\n }\n\n // If this locale is the only one assigned to its domain, no URL prefix is needed\n // (the domain itself identifies the locale). Shared domains use normal prefix logic.\n if (process.env['INTLAYER_ROUTING_DOMAINS'] !== 'false' && domains) {\n const localeDomain = domains[locale as LocalesValues];\n\n if (localeDomain) {\n const localesOnSameDomain = Object.values(domains).filter(\n (domain) => domain === localeDomain\n ).length;\n\n if (localesOnSameDomain === 1) {\n return {\n prefix: '',\n localePrefix: undefined,\n } as GetPrefixResultNarrowed<L>;\n }\n }\n }\n\n // Handle prefix-based modes (prefix-all or prefix-no-default)\n const shouldPrefix =\n mode === 'prefix-all' ||\n (mode === 'prefix-no-default' && defaultLocale !== locale);\n\n if (shouldPrefix) {\n return {\n prefix: `${locale}/`,\n localePrefix: locale as Locale,\n } as GetPrefixResultNarrowed<L>;\n }\n\n return {\n prefix: '',\n localePrefix: undefined,\n } as GetPrefixResultNarrowed<L>;\n};\n"],"mappings":";;;;;;;;AA+CA,MAAa,wBAAwB,UAA0B,CAAC,OAAO;CACrE,eAAe,sBAAsB,iBAAiB;CACtD,MAAM,SAAS,QAAQ;CACvB,SAAS,sBAAsB,WAAW;CAC1C,SAAS,SAAS;CAClB,SAAS,SAAS;CAClB,GAAG;AACL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+EA,MAAa,aACX,QACA,UAA0B,CAAC,MACI;CAC/B,MAAM,EAAE,eAAe,MAAM,SAAS,YACpC,qBAAqB,OAAO;CAE9B,IACG,QAAQ,IAAI,4BACX,QAAQ,IAAI,6BAA6B,gBACzC,QAAQ,IAAI,6BAA6B,uBAC3C,CAAC,UACD,CAAC,QAAQ,SAAS,MAAM,GAExB,OAAO;EACL,QAAQ;EACR,cAAc;CAChB;CAKF,IAAI,QAAQ,IAAI,gCAAgC,WAAW,SAAS;EAClE,MAAM,eAAe,QAAQ;EAE7B,IAAI,cAKF;OAJ4B,OAAO,OAAO,OAAO,EAAE,QAChD,WAAW,WAAW,YACzB,EAAE,WAE0B,GAC1B,OAAO;IACL,QAAQ;IACR,cAAc;GAChB;EACF;CAEJ;CAOA,IAHE,SAAS,gBACR,SAAS,uBAAuB,kBAAkB,QAGnD,OAAO;EACL,QAAQ,GAAG,OAAO;EAClB,cAAc;CAChB;CAGF,OAAO;EACL,QAAQ;EACR,cAAc;CAChB;AACF"}
|
|
@@ -22,11 +22,22 @@ const buildCookieString = (name, value, attributes) => {
|
|
|
22
22
|
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
23
23
|
if (attributes.path) parts.push(`Path=${attributes.path}`);
|
|
24
24
|
if (attributes.domain) parts.push(`Domain=${attributes.domain}`);
|
|
25
|
-
if (attributes.
|
|
25
|
+
if (typeof attributes.maxAge === "number") parts.push(`Max-Age=${Math.floor(attributes.maxAge)}`);
|
|
26
|
+
else if (attributes.expires instanceof Date) parts.push(`Expires=${attributes.expires.toUTCString()}`);
|
|
26
27
|
if (attributes.secure) parts.push("Secure");
|
|
27
28
|
if (attributes.sameSite) parts.push(`SameSite=${attributes.sameSite}`);
|
|
28
29
|
return parts.join("; ");
|
|
29
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Resolves the cookie expiry for the Cookie Store API (epoch milliseconds).
|
|
33
|
+
* `maxAge` takes precedence and is converted to an absolute timestamp, as the
|
|
34
|
+
* native `maxAge` option of `cookieStore.set()` is not yet supported in all
|
|
35
|
+
* browsers that support `cookieStore` itself.
|
|
36
|
+
*/
|
|
37
|
+
const getCookieStoreExpires = (attributes) => {
|
|
38
|
+
if (typeof attributes.maxAge === "number") return Date.now() + attributes.maxAge * 1e3;
|
|
39
|
+
return attributes.expires instanceof Date ? attributes.expires.getTime() : attributes.expires;
|
|
40
|
+
};
|
|
30
41
|
const localeStorageOptions = {
|
|
31
42
|
getCookie: (name) => document.cookie.split(";").find((c) => c.trim().startsWith(`${name}=`))?.split("=")[1],
|
|
32
43
|
getLocaleStorage: (name) => localStorage.getItem(name),
|
|
@@ -80,7 +91,7 @@ const setLocaleInStorageClient = (locale, options) => {
|
|
|
80
91
|
try {
|
|
81
92
|
if (options?.setCookieStore) options.setCookieStore(name, locale, {
|
|
82
93
|
...attributes,
|
|
83
|
-
expires: attributes
|
|
94
|
+
expires: getCookieStoreExpires(attributes)
|
|
84
95
|
});
|
|
85
96
|
} catch {
|
|
86
97
|
try {
|
|
@@ -152,7 +163,7 @@ const setLocaleInStorageServer = (locale, options) => {
|
|
|
152
163
|
try {
|
|
153
164
|
if (options?.setCookieStore) options.setCookieStore(name, locale, {
|
|
154
165
|
...attributes,
|
|
155
|
-
expires: attributes
|
|
166
|
+
expires: getCookieStoreExpires(attributes)
|
|
156
167
|
});
|
|
157
168
|
} catch {
|
|
158
169
|
try {
|
|
@@ -234,7 +245,7 @@ const setLocaleInStorage = (locale, options) => {
|
|
|
234
245
|
try {
|
|
235
246
|
if (options?.setCookieStore) options.setCookieStore(name, locale, {
|
|
236
247
|
...attributes,
|
|
237
|
-
expires: attributes
|
|
248
|
+
expires: getCookieStoreExpires(attributes)
|
|
238
249
|
});
|
|
239
250
|
} catch {
|
|
240
251
|
try {
|