@intlayer/core 9.3.1 → 9.3.3
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/dictionaryManipulator/index.cjs +5 -1
- package/dist/cjs/dictionaryManipulator/plainDynamicLoader.cjs +71 -0
- package/dist/cjs/dictionaryManipulator/plainDynamicLoader.cjs.map +1 -0
- package/dist/cjs/dictionaryManipulator/preloadedDynamicLoader.cjs +29 -0
- package/dist/cjs/dictionaryManipulator/preloadedDynamicLoader.cjs.map +1 -0
- package/dist/cjs/dictionaryManipulator/qualifiedDynamicLoader.cjs +4 -10
- package/dist/cjs/dictionaryManipulator/qualifiedDynamicLoader.cjs.map +1 -1
- package/dist/cjs/index.cjs +13 -2
- package/dist/cjs/localization/comparePaths.cjs +1 -1
- package/dist/cjs/localization/comparePaths.cjs.map +1 -1
- package/dist/cjs/localization/domainUtils.cjs.map +1 -1
- package/dist/cjs/localization/getHTMLTextDir.cjs +11 -10
- package/dist/cjs/localization/getHTMLTextDir.cjs.map +1 -1
- package/dist/cjs/localization/getLocale.cjs.map +1 -1
- package/dist/cjs/localization/getLocaleFromPath.cjs +3 -3
- package/dist/cjs/localization/getLocaleFromPath.cjs.map +1 -1
- package/dist/cjs/localization/getLocalizedPath.cjs +74 -0
- package/dist/cjs/localization/getLocalizedPath.cjs.map +1 -0
- package/dist/cjs/localization/getLocalizedPath.test-d.cjs +83 -0
- package/dist/cjs/localization/getLocalizedPath.test-d.cjs.map +1 -0
- package/dist/cjs/localization/getLocalizedUrl.cjs +9 -25
- package/dist/cjs/localization/getLocalizedUrl.cjs.map +1 -1
- package/dist/cjs/localization/getLocalizedUrl.test-d.cjs +42 -0
- package/dist/cjs/localization/getLocalizedUrl.test-d.cjs.map +1 -0
- package/dist/cjs/localization/getPathWithoutLocale.cjs +2 -2
- package/dist/cjs/localization/getPathWithoutLocale.cjs.map +1 -1
- package/dist/cjs/localization/getPrefix.cjs +15 -1
- package/dist/cjs/localization/getPrefix.cjs.map +1 -1
- package/dist/cjs/localization/index.cjs +7 -1
- package/dist/cjs/localization/localeDetector.cjs +5 -3
- package/dist/cjs/localization/localeDetector.cjs.map +1 -1
- package/dist/cjs/localization/localeMapper.cjs.map +1 -1
- package/dist/cjs/localization/localeResolver.cjs.map +1 -1
- package/dist/cjs/localization/resolveBrowserLocale.cjs +63 -0
- package/dist/cjs/localization/resolveBrowserLocale.cjs.map +1 -0
- package/dist/cjs/localization/rewriteUtils.cjs +26 -14
- package/dist/cjs/localization/rewriteUtils.cjs.map +1 -1
- package/dist/cjs/localization/rewriteUtils.test-d.cjs +48 -0
- package/dist/cjs/localization/rewriteUtils.test-d.cjs.map +1 -0
- package/dist/cjs/markdown/compiler.cjs +2 -1
- package/dist/cjs/markdown/compiler.cjs.map +1 -1
- package/dist/cjs/markdown/constants.cjs +3 -0
- package/dist/cjs/markdown/constants.cjs.map +1 -1
- package/dist/cjs/markdown/index.cjs +1 -0
- package/dist/cjs/markdown/utils.cjs +23 -1
- package/dist/cjs/markdown/utils.cjs.map +1 -1
- package/dist/esm/dictionaryManipulator/index.mjs +2 -1
- package/dist/esm/dictionaryManipulator/plainDynamicLoader.mjs +70 -0
- package/dist/esm/dictionaryManipulator/plainDynamicLoader.mjs.map +1 -0
- package/dist/esm/dictionaryManipulator/preloadedDynamicLoader.mjs +27 -0
- package/dist/esm/dictionaryManipulator/preloadedDynamicLoader.mjs.map +1 -0
- package/dist/esm/dictionaryManipulator/qualifiedDynamicLoader.mjs +2 -8
- package/dist/esm/dictionaryManipulator/qualifiedDynamicLoader.mjs.map +1 -1
- package/dist/esm/index.mjs +7 -4
- package/dist/esm/localization/comparePaths.mjs +1 -1
- package/dist/esm/localization/comparePaths.mjs.map +1 -1
- package/dist/esm/localization/domainUtils.mjs.map +1 -1
- package/dist/esm/localization/getHTMLTextDir.mjs +11 -10
- package/dist/esm/localization/getHTMLTextDir.mjs.map +1 -1
- package/dist/esm/localization/getLocale.mjs.map +1 -1
- package/dist/esm/localization/getLocaleFromPath.mjs +4 -4
- package/dist/esm/localization/getLocaleFromPath.mjs.map +1 -1
- package/dist/esm/localization/getLocalizedPath.mjs +73 -0
- package/dist/esm/localization/getLocalizedPath.mjs.map +1 -0
- package/dist/esm/localization/getLocalizedPath.test-d.mjs +83 -0
- package/dist/esm/localization/getLocalizedPath.test-d.mjs.map +1 -0
- package/dist/esm/localization/getLocalizedUrl.mjs +10 -26
- package/dist/esm/localization/getLocalizedUrl.mjs.map +1 -1
- package/dist/esm/localization/getLocalizedUrl.test-d.mjs +42 -0
- package/dist/esm/localization/getLocalizedUrl.test-d.mjs.map +1 -0
- package/dist/esm/localization/getPathWithoutLocale.mjs +2 -2
- package/dist/esm/localization/getPathWithoutLocale.mjs.map +1 -1
- package/dist/esm/localization/getPrefix.mjs +15 -2
- package/dist/esm/localization/getPrefix.mjs.map +1 -1
- package/dist/esm/localization/index.mjs +5 -3
- package/dist/esm/localization/localeDetector.mjs +5 -3
- package/dist/esm/localization/localeDetector.mjs.map +1 -1
- package/dist/esm/localization/localeMapper.mjs.map +1 -1
- package/dist/esm/localization/localeResolver.mjs.map +1 -1
- package/dist/esm/localization/resolveBrowserLocale.mjs +61 -0
- package/dist/esm/localization/resolveBrowserLocale.mjs.map +1 -0
- package/dist/esm/localization/rewriteUtils.mjs +26 -14
- package/dist/esm/localization/rewriteUtils.mjs.map +1 -1
- package/dist/esm/localization/rewriteUtils.test-d.mjs +48 -0
- package/dist/esm/localization/rewriteUtils.test-d.mjs.map +1 -0
- package/dist/esm/markdown/compiler.mjs +2 -1
- package/dist/esm/markdown/compiler.mjs.map +1 -1
- package/dist/esm/markdown/constants.mjs +3 -1
- package/dist/esm/markdown/constants.mjs.map +1 -1
- package/dist/esm/markdown/index.mjs +2 -2
- package/dist/esm/markdown/utils.mjs +24 -2
- package/dist/esm/markdown/utils.mjs.map +1 -1
- package/dist/types/@intlayer/core/dist/types/formatters/compact.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/currency.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/date.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/index.d.ts +9 -0
- package/dist/types/@intlayer/core/dist/types/formatters/list.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/number.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/percentage.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/relativeTime.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/formatters/units.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getCondition.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getContent/deepTransform.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getContent/getContent.d.ts +3 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getContent/index.d.ts +3 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getContent/plugins.d.ts +7 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getDictionary.d.ts +4 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getEnumeration.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getGender.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getInsertion.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getIntlayer.d.ts +4 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getNesting.d.ts +6 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getPlural.d.ts +3 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getSelect.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/getTranslation.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/index.d.ts +15 -0
- package/dist/types/@intlayer/core/dist/types/interpreter/interpolableNode.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/comparePaths.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/domainUtils.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/generateSitemap.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/getBrowserLocale.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getHTMLTextDir.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getLocale.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getLocaleFromPath.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/getLocaleLang.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getLocaleName.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getLocalizedPath.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/getLocalizedUrl.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/getMultilingualUrls.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/getPathWithoutLocale.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/getPrefix.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/index.d.ts +20 -0
- package/dist/types/@intlayer/core/dist/types/localization/localeDetector.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/localeMapper.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/localeResolver.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/localization/resolveBrowserLocale.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/rewriteUtils.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/localization/validatePrefix.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/markdown/index.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/condition/condition.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/condition/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/enumeration/enumeration.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/enumeration/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/file/file.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/file/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/gender/gender.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/gender/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/html/html.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/html/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/index.d.ts +22 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/insertion/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/insertion/insertion.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/markdown/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/markdown/markdown.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/nesting/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/nesting/nesting.d.ts +4 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/plural/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/plural/plural.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/select/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/select/select.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/translation/index.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/transpiler/translation/translation.d.ts +2 -0
- package/dist/types/@intlayer/core/dist/types/utils/index.d.ts +3 -0
- package/dist/types/@intlayer/core/dist/types/utils/intl.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/utils/isSameKeyPath.d.ts +1 -0
- package/dist/types/@intlayer/core/dist/types/utils/localeStorage.d.ts +2 -0
- package/dist/types/dictionaryManipulator/index.d.ts +2 -1
- package/dist/types/dictionaryManipulator/plainDynamicLoader.d.ts +50 -0
- package/dist/types/dictionaryManipulator/plainDynamicLoader.d.ts.map +1 -0
- package/dist/types/dictionaryManipulator/preloadedDynamicLoader.d.ts +38 -0
- package/dist/types/dictionaryManipulator/preloadedDynamicLoader.d.ts.map +1 -0
- package/dist/types/dictionaryManipulator/qualifiedDynamicLoader.d.ts +1 -7
- package/dist/types/dictionaryManipulator/qualifiedDynamicLoader.d.ts.map +1 -1
- package/dist/types/index.d.ts +8 -5
- package/dist/types/interpreter/getPlural.d.ts +1 -1
- package/dist/types/intlayer/dist/types/index.d.ts +14 -0
- package/dist/types/localization/domainUtils.d.ts +2 -3
- package/dist/types/localization/domainUtils.d.ts.map +1 -1
- package/dist/types/localization/getHTMLTextDir.d.ts.map +1 -1
- package/dist/types/localization/getLocale.d.ts +2 -2
- package/dist/types/localization/getLocale.d.ts.map +1 -1
- package/dist/types/localization/getLocaleFromPath.d.ts +2 -2
- package/dist/types/localization/getLocaleFromPath.d.ts.map +1 -1
- package/dist/types/localization/getLocalizedPath.d.ts +47 -0
- package/dist/types/localization/getLocalizedPath.d.ts.map +1 -0
- package/dist/types/localization/getLocalizedPath.test-d.d.ts +1 -0
- package/dist/types/localization/getLocalizedUrl.d.ts +5 -1
- package/dist/types/localization/getLocalizedUrl.d.ts.map +1 -1
- package/dist/types/localization/getLocalizedUrl.test-d.d.ts +1 -0
- package/dist/types/localization/getPrefix.d.ts +20 -8
- package/dist/types/localization/getPrefix.d.ts.map +1 -1
- package/dist/types/localization/index.d.ts +5 -3
- package/dist/types/localization/localeDetector.d.ts +2 -1
- package/dist/types/localization/localeDetector.d.ts.map +1 -1
- package/dist/types/localization/localeMapper.d.ts +4 -5
- package/dist/types/localization/localeMapper.d.ts.map +1 -1
- package/dist/types/localization/localeResolver.d.ts +2 -3
- package/dist/types/localization/localeResolver.d.ts.map +1 -1
- package/dist/types/localization/resolveBrowserLocale.d.ts +35 -0
- package/dist/types/localization/resolveBrowserLocale.d.ts.map +1 -0
- package/dist/types/localization/rewriteUtils.d.ts +51 -11
- package/dist/types/localization/rewriteUtils.d.ts.map +1 -1
- package/dist/types/localization/rewriteUtils.test-d.d.ts +1 -0
- package/dist/types/markdown/compiler.d.ts +12 -1
- package/dist/types/markdown/compiler.d.ts.map +1 -1
- package/dist/types/markdown/constants.d.ts +3 -1
- package/dist/types/markdown/constants.d.ts.map +1 -1
- package/dist/types/markdown/index.d.ts +3 -3
- package/dist/types/markdown/utils.d.ts +6 -0
- package/dist/types/markdown/utils.d.ts.map +1 -1
- package/package.json +7 -7
|
@@ -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';\
|
|
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';\nimport type { RewriteRules, RoutingConfig } from '@intlayer/types/config';\nimport type {\n DeclaredLocales,\n LocalesValues,\n ResolvedDefaultLocale,\n ResolvedRoutingMode,\n} from '@intlayer/types/module_augmentation';\nimport { isLocaleExclusiveOnDomain } from './domainUtils';\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'] | RewriteRules;\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 = (\n options: RoutingOptions = {}\n): Omit<RoutingOptions, 'defaultLocale' | 'mode' | 'locales'> & {\n defaultLocale: DeclaredLocales;\n mode: RoutingConfig['mode'];\n locales: DeclaredLocales[];\n} => ({\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\n/**\n * Narrows an arbitrary string to a declared locale by membership test.\n *\n * `locales` is typed as `DeclaredLocales[]`, so a plain `includes` rejects the\n * `string` values that actually need testing (a URL segment, a query param, a\n * stored value). This keeps the check honest and hands back a narrowed type\n * instead of forcing a cast at every call site.\n *\n * @param value - The candidate locale, from a URL segment / storage / header.\n * @param locales - The locales declared by the configuration.\n * @returns Whether `value` is one of the declared locales.\n */\nexport const isDeclaredLocale = (\n value: LocalesValues | undefined | null,\n locales?: readonly LocalesValues[]\n): value is DeclaredLocales =>\n !!value && (locales ?? internationalization.locales).includes(value);\n\nexport type GetPrefixOptions = {\n defaultLocale?: ResolvedDefaultLocale;\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: DeclaredLocales | 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 !isDeclaredLocale(locale, locales)\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 (\n process.env.INTLAYER_ROUTING_DOMAINS !== 'false' &&\n isLocaleExclusiveOnDomain(locale as LocalesValues, domains)\n ) {\n return {\n prefix: '',\n localePrefix: undefined,\n } as GetPrefixResultNarrowed<L>;\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 DeclaredLocales,\n } as GetPrefixResultNarrowed<L>;\n }\n\n return {\n prefix: '',\n localePrefix: undefined,\n } as GetPrefixResultNarrowed<L>;\n};\n"],"mappings":";;;;;;;;;AA0CA,MAAa,wBACX,UAA0B,CAAC,OAKvB;CACJ,eAAe,sBAAsB,iBAAiB;CACtD,MAAM,SAAS,QAAQ;CACvB,SAAS,sBAAsB,WAAW;CAC1C,SAAS,SAAS;CAClB,SAAS,SAAS;CAClB,GAAG;AACL;;;;;;;;;;;;;AAcA,MAAa,oBACX,OACA,YAEA,CAAC,CAAC,UAAU,WAAW,qBAAqB,QAAO,CAAE,SAAS,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+ErE,MAAa,aACX,QACA,UAA0B,CAAC,MACI;CAC/B,MAAM,EAAE,eAAe,MAAM,SAAS,YACpC,qBAAqB,OAAO;CAE9B,IACG,QAAQ,IAAI,yBACX,QAAQ,IAAI,0BAA0B,gBACtC,QAAQ,IAAI,0BAA0B,uBACxC,CAAC,UACD,CAAC,iBAAiB,QAAQ,OAAO,GAEjC,OAAO;EACL,QAAQ;EACR,cAAc;CAChB;CAKF,IACE,QAAQ,IAAI,6BAA6B,WACzC,0BAA0B,QAAyB,OAAO,GAE1D,OAAO;EACL,QAAQ;EACR,cAAc;CAChB;CAQF,IAHE,SAAS,gBACR,SAAS,uBAAuB,kBAAkB,QAGnD,OAAO;EACL,QAAQ,GAAG,OAAO;EAClB,cAAc;CAChB;CAGF,OAAO;EACL,QAAQ;EACR,cAAc;CAChB;AACF"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getPathWithoutLocale } from "./getPathWithoutLocale.mjs";
|
|
2
2
|
import { comparePaths, normalizePath } from "./comparePaths.mjs";
|
|
3
3
|
import { getDomainHostname, getDomainOrigin, getLocaleFromDomain, isLocaleExclusiveOnDomain } from "./domainUtils.mjs";
|
|
4
|
-
import { getPrefix } from "./getPrefix.mjs";
|
|
5
|
-
import { getCanonicalPath, getInternalPath,
|
|
4
|
+
import { getPrefix, isDeclaredLocale } from "./getPrefix.mjs";
|
|
5
|
+
import { getCanonicalPath, getInternalPath, getRewritePath, getRewriteRules, resolveLocalizedPath } from "./rewriteUtils.mjs";
|
|
6
|
+
import { getLocalizedPath } from "./getLocalizedPath.mjs";
|
|
6
7
|
import { getLocalizedUrl } from "./getLocalizedUrl.mjs";
|
|
7
8
|
import { getMultilingualUrls } from "./getMultilingualUrls.mjs";
|
|
8
9
|
import { generateSitemap, generateSitemapUrl } from "./generateSitemap.mjs";
|
|
@@ -16,6 +17,7 @@ import { getLocaleLang } from "./getLocaleLang.mjs";
|
|
|
16
17
|
import { getLocaleName } from "./getLocaleName.mjs";
|
|
17
18
|
import { localeFlatMap, localeMap, localeRecord } from "./localeMapper.mjs";
|
|
18
19
|
import { formatProxyEnabledMessage, isProxyStorageLocaleEnabled, resolveProxyMode } from "./proxyMode.mjs";
|
|
20
|
+
import { getPreloadLocale, resolveBrowserLocale } from "./resolveBrowserLocale.mjs";
|
|
19
21
|
import { validatePrefix } from "./validatePrefix.mjs";
|
|
20
22
|
|
|
21
|
-
export { comparePaths, formatProxyEnabledMessage, generateSitemap, generateSitemapUrl, getBrowserLocale, getCanonicalPath, getDomainHostname, getDomainOrigin, getHTMLTextDir, getInternalPath, getLocale, getLocaleFromDomain, getLocaleFromPath, getLocaleLang, getLocaleName, getLocalizedPath, getLocalizedUrl, getMultilingualUrls, getPathWithoutLocale, getPrefix, getRewritePath, getRewriteRules, isLocaleExclusiveOnDomain, isProxyStorageLocaleEnabled, localeDetector, localeFlatMap, localeMap, localeRecord, localeResolver, normalizePath, resolveProxyMode, validatePrefix };
|
|
23
|
+
export { comparePaths, formatProxyEnabledMessage, generateSitemap, generateSitemapUrl, getBrowserLocale, getCanonicalPath, getDomainHostname, getDomainOrigin, getHTMLTextDir, getInternalPath, getLocale, getLocaleFromDomain, getLocaleFromPath, getLocaleLang, getLocaleName, getLocalizedPath, getLocalizedUrl, getMultilingualUrls, getPathWithoutLocale, getPrefix, getPreloadLocale, getRewritePath, getRewriteRules, isDeclaredLocale, isLocaleExclusiveOnDomain, isProxyStorageLocaleEnabled, localeDetector, localeFlatMap, localeMap, localeRecord, localeResolver, normalizePath, resolveBrowserLocale, resolveLocalizedPath, resolveProxyMode, validatePrefix };
|
|
@@ -40,9 +40,11 @@ const parseAcceptLanguageHeader = (headerValue) => {
|
|
|
40
40
|
const rawTags = headerValue.split(",");
|
|
41
41
|
const preferences = [];
|
|
42
42
|
for (let index = 0; index < rawTags.length; index++) {
|
|
43
|
-
const tag = rawTags[index]
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const tag = rawTags[index]?.trim();
|
|
44
|
+
if (tag) {
|
|
45
|
+
const parsedLanguage = parseLanguageTag(tag, index);
|
|
46
|
+
if (parsedLanguage) preferences.push(parsedLanguage);
|
|
47
|
+
}
|
|
46
48
|
}
|
|
47
49
|
return preferences;
|
|
48
50
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localeDetector.mjs","names":[],"sources":["../../../src/localization/localeDetector.ts"],"sourcesContent":["import type { Locale } from '@intlayer/types/allLocales';\nimport { localeResolver } from './localeResolver';\n\n/**\n * Constants\n */\nconst LANGUAGE_FORMAT_REGULAR_EXPRESSION =\n /^\\s*([^\\s\\-;]+)(?:-([^\\s;]+))?\\s*(?:;(.*))?$/;\nconst DEFAULT_QUALITY_SCORE = 1;\n\n/**\n * Enumeration for specificity weights.\n * Higher values indicate a more precise match.\n */\nenum SpecificityWeight {\n None = 0,\n Broad = 1, // Matches prefix (e.g., 'en' matches 'en-US')\n Prefix = 2, // Matches prefix in reverse (e.g., 'en-US' matches 'en')\n Exact = 4, // Matches exact string (e.g., 'en-US' matches 'en-US')\n}\n\n/**\n * Represents a parsed language tag from the header.\n */\ntype LanguagePreference = {\n languageCode: string;\n regionCode?: string;\n fullLocale: string;\n qualityScore: number;\n originalIndex: number;\n};\n\n/**\n * Represents the result of matching a requested language against an available language.\n */\ntype MatchResult = {\n providedIndex: number;\n headerIndex: number;\n qualityScore: number;\n specificityScore: number;\n};\n\n/**\n * Parses a single language tag string from the Accept-Language header.\n * Example input: \"en-US;q=0.8\"\n */\nconst parseLanguageTag = (\n tagString: string,\n index: number\n): LanguagePreference | null => {\n const match = LANGUAGE_FORMAT_REGULAR_EXPRESSION.exec(tagString);\n if (!match) {\n return null;\n }\n\n const languageCode = match[1];\n const regionCode = match[2];\n const parameters = match[3];\n\n // Construct the full locale string (e.g., \"en-US\" or \"en\")\n const fullLocale = regionCode\n ? `${languageCode}-${regionCode}`\n : languageCode;\n\n let qualityScore = DEFAULT_QUALITY_SCORE;\n\n // Parse parameters to find the quality score (\"q\")\n if (parameters) {\n const parameterList = parameters.split(';');\n for (const parameter of parameterList) {\n const [key, value] = parameter.split('=');\n if (key === 'q') {\n qualityScore = parseFloat(value);\n }\n }\n }\n\n return {\n languageCode,\n regionCode,\n qualityScore,\n originalIndex: index,\n fullLocale,\n };\n};\n\n/**\n * Parses the entire Accept-Language header string into a list of preferences.\n */\nconst parseAcceptLanguageHeader = (\n headerValue: string\n): LanguagePreference[] => {\n const rawTags = headerValue.split(',');\n const preferences: LanguagePreference[] = [];\n\n for (let index = 0; index < rawTags.length; index++) {\n const tag = rawTags[index]
|
|
1
|
+
{"version":3,"file":"localeDetector.mjs","names":[],"sources":["../../../src/localization/localeDetector.ts"],"sourcesContent":["import type { Locale } from '@intlayer/types/allLocales';\nimport type { DeclaredLocales } from '@intlayer/types/module_augmentation';\nimport { localeResolver } from './localeResolver';\n\n/**\n * Constants\n */\nconst LANGUAGE_FORMAT_REGULAR_EXPRESSION =\n /^\\s*([^\\s\\-;]+)(?:-([^\\s;]+))?\\s*(?:;(.*))?$/;\nconst DEFAULT_QUALITY_SCORE = 1;\n\n/**\n * Enumeration for specificity weights.\n * Higher values indicate a more precise match.\n */\nenum SpecificityWeight {\n None = 0,\n Broad = 1, // Matches prefix (e.g., 'en' matches 'en-US')\n Prefix = 2, // Matches prefix in reverse (e.g., 'en-US' matches 'en')\n Exact = 4, // Matches exact string (e.g., 'en-US' matches 'en-US')\n}\n\n/**\n * Represents a parsed language tag from the header.\n */\ntype LanguagePreference = {\n languageCode: string;\n regionCode?: string;\n fullLocale: string;\n qualityScore: number;\n originalIndex: number;\n};\n\n/**\n * Represents the result of matching a requested language against an available language.\n */\ntype MatchResult = {\n providedIndex: number;\n headerIndex: number;\n qualityScore: number;\n specificityScore: number;\n};\n\n/**\n * Parses a single language tag string from the Accept-Language header.\n * Example input: \"en-US;q=0.8\"\n */\nconst parseLanguageTag = (\n tagString: string,\n index: number\n): LanguagePreference | null => {\n const match = LANGUAGE_FORMAT_REGULAR_EXPRESSION.exec(tagString);\n if (!match) {\n return null;\n }\n\n const languageCode = match[1] as string;\n const regionCode = match[2];\n const parameters = match[3];\n\n // Construct the full locale string (e.g., \"en-US\" or \"en\")\n const fullLocale = regionCode\n ? `${languageCode}-${regionCode}`\n : languageCode;\n\n let qualityScore = DEFAULT_QUALITY_SCORE;\n\n // Parse parameters to find the quality score (\"q\")\n if (parameters) {\n const parameterList = parameters.split(';');\n\n for (const parameter of parameterList) {\n const [key, value] = parameter.split('=');\n\n if (key === 'q') {\n qualityScore = parseFloat(value as string);\n }\n }\n }\n\n return {\n languageCode,\n regionCode,\n qualityScore,\n originalIndex: index,\n fullLocale,\n };\n};\n\n/**\n * Parses the entire Accept-Language header string into a list of preferences.\n */\nconst parseAcceptLanguageHeader = (\n headerValue: string\n): LanguagePreference[] => {\n const rawTags = headerValue.split(',');\n const preferences: LanguagePreference[] = [];\n\n for (let index = 0; index < rawTags.length; index++) {\n const tag = rawTags[index]?.trim();\n\n if (tag) {\n const parsedLanguage = parseLanguageTag(tag, index);\n\n if (parsedLanguage) {\n preferences.push(parsedLanguage);\n }\n }\n }\n\n return preferences;\n};\n\n/**\n * Calculates the specificity of a match between a provided language and a requested preference.\n */\nconst calculateMatchSpecificity = (\n providedLanguage: string,\n preference: LanguagePreference,\n providedIndex: number\n): MatchResult | null => {\n const parsedProvided = parseLanguageTag(providedLanguage, providedIndex);\n if (!parsedProvided) {\n return null;\n }\n\n let specificityScore = SpecificityWeight.None;\n\n const preferenceFullLower = preference.fullLocale.toLowerCase();\n const preferencePrefixLower = preference.languageCode.toLowerCase();\n const providedFullLower = parsedProvided.fullLocale.toLowerCase();\n const providedPrefixLower = parsedProvided.languageCode.toLowerCase();\n\n if (preferenceFullLower === providedFullLower) {\n specificityScore |= SpecificityWeight.Exact;\n } else if (preferencePrefixLower === providedFullLower) {\n specificityScore |= SpecificityWeight.Prefix;\n } else if (preferenceFullLower === providedPrefixLower) {\n specificityScore |= SpecificityWeight.Broad;\n } else if (preference.fullLocale !== '*') {\n return null;\n }\n\n return {\n providedIndex,\n headerIndex: preference.originalIndex,\n qualityScore: preference.qualityScore,\n specificityScore,\n };\n};\n\n/**\n * Determines the best match for a specific available language against the list of user accepted languages.\n */\nconst getBestMatchForLanguage = (\n providedLanguage: string,\n acceptedPreferences: LanguagePreference[],\n providedIndex: number\n): MatchResult => {\n // Initialize with a non-match priority\n let bestMatch: MatchResult = {\n headerIndex: -1,\n qualityScore: 0,\n specificityScore: 0,\n providedIndex,\n };\n\n for (const preference of acceptedPreferences) {\n const matchSpec = calculateMatchSpecificity(\n providedLanguage,\n preference,\n providedIndex\n );\n\n if (matchSpec) {\n // Compare current best match with new match\n const scoreDifference =\n bestMatch.specificityScore - matchSpec.specificityScore ||\n bestMatch.qualityScore - matchSpec.qualityScore ||\n bestMatch.headerIndex - matchSpec.headerIndex;\n\n // If the new match is better (difference < 0), update priority\n if (scoreDifference < 0) {\n bestMatch = matchSpec;\n }\n }\n }\n\n return bestMatch;\n};\n\n/**\n * Comparator function to sort language matches.\n * Sorting order:\n * 1. Quality Score (Descending)\n * 2. Specificity Score (Descending)\n * 3. Order in Header (Ascending - lower index is better)\n * 4. Order in Provided List (Ascending)\n */\nconst compareMatchResults = (a: MatchResult, b: MatchResult): number => {\n return (\n b.qualityScore - a.qualityScore ||\n b.specificityScore - a.specificityScore ||\n a.headerIndex - b.headerIndex ||\n a.providedIndex - b.providedIndex ||\n 0\n );\n};\n\n/**\n * Derives the list of preferred languages based on the Accept-Language header\n * and an optional list of available languages.\n */\nexport const getPreferredLanguages = (\n acceptHeader: string | undefined,\n availableLanguages?: string[]\n): string[] => {\n // RFC 2616 sec 14.4: no header implies '*'\n const headerValue = acceptHeader === undefined ? '*' : acceptHeader || '';\n const acceptedPreferences = parseAcceptLanguageHeader(headerValue);\n\n // If no specific languages are provided to filter against, return the header languages sorted by quality\n if (!availableLanguages) {\n return acceptedPreferences\n .filter((preference) => preference.qualityScore > 0)\n .sort((a, b) => b.qualityScore - a.qualityScore) // Simple sort by quality\n .map((preference) => preference.fullLocale);\n }\n\n // Map available languages to their match priority against the header\n const matchResults = availableLanguages.map((language, index) =>\n getBestMatchForLanguage(language, acceptedPreferences, index)\n );\n\n return matchResults\n .filter((result) => result.qualityScore > 0)\n .sort(compareMatchResults)\n .map((result) => availableLanguages[result.providedIndex]) as string[];\n};\n\n/**\n * Detects the locale from the request headers.\n *\n * Headers are provided by the browser/client and can be used to determine the user's preferred language.\n * This function intersects the user's `Accept-Language` header with the application's available locales.\n */\nexport const localeDetector = (\n headers: Record<string, string | undefined>,\n availableLocales?: Locale[],\n defaultLocale?: Locale\n): DeclaredLocales => {\n const acceptLanguageHeader = headers['accept-language'];\n\n const preferredLocaleStrings = getPreferredLanguages(\n acceptLanguageHeader,\n availableLocales as string[]\n );\n\n return localeResolver(\n preferredLocaleStrings as Locale[],\n availableLocales,\n defaultLocale\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localeMapper.mjs","names":[],"sources":["../../../src/localization/localeMapper.ts"],"sourcesContent":["import { internationalization, routing } from '@intlayer/config/built';\nimport { DEFAULT_LOCALE } from '@intlayer/config/defaultValues';\nimport type {
|
|
1
|
+
{"version":3,"file":"localeMapper.mjs","names":[],"sources":["../../../src/localization/localeMapper.ts"],"sourcesContent":["import { internationalization, routing } from '@intlayer/config/built';\nimport { DEFAULT_LOCALE } from '@intlayer/config/defaultValues';\nimport type {\n DeclaredLocales,\n LocalesValues,\n} from '@intlayer/types/module_augmentation';\nimport { getPrefix } from './getPrefix';\n\nexport type LocaleData = {\n locale: DeclaredLocales;\n defaultLocale: DeclaredLocales;\n isDefault: boolean;\n locales: DeclaredLocales[];\n urlPrefix: string;\n};\n\n/**\n * Map the locale data to an array of objects\n *\n * @example\n * ```ts\n * const routes = localeMap((localizedData) =>\n * ({\n * path: localizedData.urlPrefix,\n * name: localizedData.locale,\n * isDefault: localizedData.isDefault,\n * locales: localizedData.locales,\n * defaultLocale: localizedData.defaultLocale,\n * }),\n * );\n *\n * // Result\n * [\n * { path: '/', name: 'en', isDefault: true, locales: ['en'], defaultLocale: 'en', urlPrefix: '' },\n * { path: '/fr', name: 'fr', isDefault: false, locales: ['fr'], defaultLocale: 'en', urlPrefix: '/fr' },\n * { path: '/es', name: 'es', isDefault: false, locales: ['es'], defaultLocale: 'en', urlPrefix: '/es' },\n * ]\n * ```\n *\n * @param mapper - The mapper function that returns an object\n * @returns An array of objects\n */\nexport const localeMap = <T>(\n mapper: (locale: LocaleData) => T,\n locales: LocalesValues[] = internationalization.locales ?? [],\n defaultLocale: LocalesValues = internationalization.defaultLocale ??\n DEFAULT_LOCALE,\n mode:\n | 'prefix-no-default'\n | 'prefix-all'\n | 'no-prefix'\n | 'search-params' = routing?.mode ?? 'prefix-no-default'\n): T[] =>\n (locales ?? []).map((locale) =>\n mapper({\n locale,\n defaultLocale,\n locales,\n isDefault: locale === defaultLocale,\n urlPrefix: getPrefix(locale, { defaultLocale, mode, locales })\n .localePrefix\n ? `/${locale}`\n : '',\n } as LocaleData)\n );\n\n/**\n * Flatten the locale map into a single array of objects\n *\n * @example\n * ```ts\n * const routes = localeMap((localizedData) =>\n * [{\n * path: localizedData.urlPrefix,\n * name: localizedData.locale,\n * isDefault: localizedData.isDefault,\n * locales: localizedData.locales,\n * defaultLocale: localizedData.defaultLocale,\n * }],\n * );\n *\n * // Result\n * [\n * path: '/', name: 'en', isDefault: true, locales: ['en'], defaultLocale: 'en', urlPrefix: '' ,\n * path: '/fr', name: 'fr', isDefault: false, locales: ['fr'], defaultLocale: 'en', urlPrefix: '/fr' ,\n * path: '/es', name: 'es', isDefault: false, locales: ['es'], defaultLocale: 'en', urlPrefix: '/es' ,\n * ]\n * ```\n *\n * @param mapper - The mapper function that returns an array of objects\n * @returns An array of objects\n */\nexport const localeFlatMap = <T>(\n mapper: (locale: LocaleData) => T[],\n locales: LocalesValues[] = internationalization.locales ?? [],\n defaultLocale: LocalesValues = internationalization.defaultLocale ??\n DEFAULT_LOCALE,\n mode:\n | 'prefix-no-default'\n | 'prefix-all'\n | 'no-prefix'\n | 'search-params' = routing?.mode ?? 'prefix-no-default'\n): T[] =>\n locales.flatMap((locale) =>\n mapper({\n locale,\n defaultLocale,\n locales,\n isDefault: locale === defaultLocale,\n urlPrefix: getPrefix(locale, { defaultLocale, mode, locales })\n .localePrefix\n ? `/${locale}`\n : '',\n } as LocaleData)\n );\n\n/**\n * Creates a record object mapping locales to values transformed by the mapper function\n *\n * @example\n * ```ts\n * const translations = localeRecord(({ locale }) =>\n * require(`./translations/${locale}.json`)\n * );\n *\n * // Result\n *\n * en: ... , // Content of translations/en.json\n * fr: ... , // Content of translations/fr.json\n * es: ...\n *\n * ```\n *\n * @param mapper - Function that takes locale data and returns a value for that locale\n * @param locales - Array of locale codes to map over (defaults to configured locales)\n * @param defaultLocale - The default locale (defaults to configured default)\n * @param mode - URL routing mode for locale handling (defaults to configured value)\n * @returns Record mapping locale codes to mapped values\n */\nexport const localeRecord = <T>(\n mapper: (locale: LocaleData) => T,\n locales: LocalesValues[] = internationalization.locales ?? [],\n defaultLocale: LocalesValues = internationalization.defaultLocale ??\n DEFAULT_LOCALE,\n mode:\n | 'prefix-no-default'\n | 'prefix-all'\n | 'no-prefix'\n | 'search-params' = routing?.mode ?? 'prefix-no-default'\n): Record<LocalesValues, T> =>\n (locales ?? []).reduce(\n (acc, locale) => {\n acc[locale] = mapper({\n locale,\n defaultLocale,\n locales,\n isDefault: locale === defaultLocale,\n urlPrefix: getPrefix(locale, { defaultLocale, mode, locales })\n .localePrefix\n ? `/${locale}`\n : '',\n } as LocaleData);\n return acc;\n },\n {} as Record<LocalesValues, T>\n );\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAa,aACX,QACA,UAA2B,qBAAqB,WAAW,CAAC,GAC5D,gBAA+B,qBAAqB,iBAClD,gBACF,OAIsB,SAAS,QAAQ,yBAEtC,WAAW,CAAC,EAAC,CAAE,KAAK,WACnB,OAAO;CACL;CACA;CACA;CACA,WAAW,WAAW;CACtB,WAAW,UAAU,QAAQ;EAAE;EAAe;EAAM;CAAQ,CAAC,CAAC,CAC3D,eACC,IAAI,WACJ;AACN,CAAe,CACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BF,MAAa,iBACX,QACA,UAA2B,qBAAqB,WAAW,CAAC,GAC5D,gBAA+B,qBAAqB,iBAClD,gBACF,OAIsB,SAAS,QAAQ,wBAEvC,QAAQ,SAAS,WACf,OAAO;CACL;CACA;CACA;CACA,WAAW,WAAW;CACtB,WAAW,UAAU,QAAQ;EAAE;EAAe;EAAM;CAAQ,CAAC,CAAC,CAC3D,eACC,IAAI,WACJ;AACN,CAAe,CACjB;;;;;;;;;;;;;;;;;;;;;;;;AAyBF,MAAa,gBACX,QACA,UAA2B,qBAAqB,WAAW,CAAC,GAC5D,gBAA+B,qBAAqB,iBAClD,gBACF,OAIsB,SAAS,QAAQ,yBAEtC,WAAW,CAAC,EAAC,CAAE,QACb,KAAK,WAAW;CACf,IAAI,UAAU,OAAO;EACnB;EACA;EACA;EACA,WAAW,WAAW;EACtB,WAAW,UAAU,QAAQ;GAAE;GAAe;GAAM;EAAQ,CAAC,CAAC,CAC3D,eACC,IAAI,WACJ;CACN,CAAe;CACf,OAAO;AACT,GACA,CAAC,CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localeResolver.mjs","names":[],"sources":["../../../src/localization/localeResolver.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type {
|
|
1
|
+
{"version":3,"file":"localeResolver.mjs","names":[],"sources":["../../../src/localization/localeResolver.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type {\n DeclaredLocales,\n LocalesValues,\n} from '@intlayer/types/module_augmentation';\n\n/**\n * Resolves the most specific locale from a user-provided list,\n * or falls back to the default locale if no match is found.\n */\nexport const localeResolver = (\n selectedLocale: LocalesValues | LocalesValues[],\n locales: LocalesValues[] = internationalization?.locales,\n defaultLocale: LocalesValues = internationalization?.defaultLocale\n): DeclaredLocales => {\n // Ensure we can handle both a single locale or an array of locales uniformly\n const requestedLocales = [selectedLocale].flat();\n\n // Simple helper to normalize locale strings (e.g. \"en-US\" => \"en-us\")\n const normalize = (locale: string): string => locale.trim().toLowerCase();\n\n try {\n // Check each requested locale in order\n for (const requested of requestedLocales) {\n const normalizedRequested = normalize(requested);\n\n // Attempt exact match\n const exactMatch = locales.find(\n (locale) => normalize(locale) === normalizedRequested\n );\n if (exactMatch) {\n return exactMatch as DeclaredLocales;\n }\n\n // Attempt partial match on language subtag\n // e.g. if requested is \"en-US\" and not found,\n // see if \"en\" is available among locales\n const [requestedLang] = normalizedRequested.split('-');\n const partialMatch = locales.find(\n (locale) => normalize(locale).split('-')[0] === requestedLang\n );\n if (partialMatch) {\n return partialMatch as DeclaredLocales;\n }\n }\n } catch {\n // If anything unexpected happened, fall back to default\n }\n\n // If no match was found, return the default\n return defaultLocale as DeclaredLocales;\n};\n"],"mappings":";;;;;;;AAUA,MAAa,kBACX,gBACA,UAA2B,sBAAsB,SACjD,gBAA+B,sBAAsB,kBACjC;CAEpB,MAAM,mBAAmB,CAAC,cAAc,CAAC,CAAC,KAAK;CAG/C,MAAM,aAAa,WAA2B,OAAO,KAAK,CAAC,CAAC,YAAY;CAExE,IAAI;EAEF,KAAK,MAAM,aAAa,kBAAkB;GACxC,MAAM,sBAAsB,UAAU,SAAS;GAG/C,MAAM,aAAa,QAAQ,MACxB,WAAW,UAAU,MAAM,MAAM,mBACpC;GACA,IAAI,YACF,OAAO;GAMT,MAAM,CAAC,iBAAiB,oBAAoB,MAAM,GAAG;GACrD,MAAM,eAAe,QAAQ,MAC1B,WAAW,UAAU,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,aAClD;GACA,IAAI,cACF,OAAO;EAEX;CACF,QAAQ,CAER;CAGA,OAAO;AACT"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getLocaleFromDomain } from "./domainUtils.mjs";
|
|
2
|
+
import { resolveRoutingConfig } from "./getPrefix.mjs";
|
|
3
|
+
import { getLocaleFromStorageClient } from "../utils/localeStorage.mjs";
|
|
4
|
+
import { getLocaleFromPath } from "./getLocaleFromPath.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/localization/resolveBrowserLocale.ts
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the locale of the current document outside of React.
|
|
9
|
+
*
|
|
10
|
+
* Generated dynamic entry points need the locale while they are still
|
|
11
|
+
* evaluating, long before a provider exists, so this reproduces the precedence
|
|
12
|
+
* a provider applies — URL first (which is what a router hands the provider as
|
|
13
|
+
* `locale`), then browser storage, then the configured default. Agreeing with
|
|
14
|
+
* the provider is what makes the preloaded read hit; disagreeing is safe but
|
|
15
|
+
* costs a load, since the consumer compares locales before using a preloaded
|
|
16
|
+
* dictionary.
|
|
17
|
+
*
|
|
18
|
+
* @param options - Routing overrides, defaulting to the built configuration.
|
|
19
|
+
* @returns The resolved locale, falling back to the default locale off-browser.
|
|
20
|
+
*/
|
|
21
|
+
const resolveBrowserLocale = (options) => {
|
|
22
|
+
const { defaultLocale, mode, domains } = resolveRoutingConfig(options);
|
|
23
|
+
if (typeof window === "undefined") return defaultLocale;
|
|
24
|
+
if (domains) {
|
|
25
|
+
const localeFromDomain = getLocaleFromDomain(window.location.hostname, domains);
|
|
26
|
+
if (localeFromDomain) return localeFromDomain;
|
|
27
|
+
}
|
|
28
|
+
if (mode === "prefix-all" || mode === "prefix-no-default" || mode === "search-params" && new URLSearchParams(window.location.search).has("locale")) {
|
|
29
|
+
const localeFromPath = getLocaleFromPath(window.location.pathname + window.location.search, options);
|
|
30
|
+
if (localeFromPath) return localeFromPath;
|
|
31
|
+
}
|
|
32
|
+
return getLocaleFromStorageClient() ?? defaultLocale;
|
|
33
|
+
};
|
|
34
|
+
/** URL (path + search) the memoized locale below was resolved against. */
|
|
35
|
+
let memoizedUrl;
|
|
36
|
+
let memoizedLocale;
|
|
37
|
+
/**
|
|
38
|
+
* {@link resolveBrowserLocale}, memoized for the current URL.
|
|
39
|
+
*
|
|
40
|
+
* Every generated dynamic entry point calls this as it evaluates, and an
|
|
41
|
+
* application has hundreds of them — repeating the cookie and storage reads
|
|
42
|
+
* that many times on startup is pure cost, since they cannot change while a
|
|
43
|
+
* page is being assembled.
|
|
44
|
+
*
|
|
45
|
+
* Keying the cache on the URL rather than caching forever keeps a client-side
|
|
46
|
+
* locale change honest: navigating to another locale's URL loads further
|
|
47
|
+
* chunks with that locale, instead of preloading the old one and silently
|
|
48
|
+
* falling back to an asynchronous read.
|
|
49
|
+
*/
|
|
50
|
+
const getPreloadLocale = () => {
|
|
51
|
+
const currentUrl = typeof window === "undefined" ? "" : window.location.pathname + window.location.search;
|
|
52
|
+
if (memoizedLocale === void 0 || memoizedUrl !== currentUrl) {
|
|
53
|
+
memoizedUrl = currentUrl;
|
|
54
|
+
memoizedLocale = resolveBrowserLocale();
|
|
55
|
+
}
|
|
56
|
+
return memoizedLocale;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
export { getPreloadLocale, resolveBrowserLocale };
|
|
61
|
+
//# sourceMappingURL=resolveBrowserLocale.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveBrowserLocale.mjs","names":[],"sources":["../../../src/localization/resolveBrowserLocale.ts"],"sourcesContent":["import type { LocalesValues } from '@intlayer/types/module_augmentation';\nimport { getLocaleFromStorageClient } from '../utils/localeStorage';\nimport { getLocaleFromDomain } from './domainUtils';\nimport { getLocaleFromPath } from './getLocaleFromPath';\nimport { type RoutingOptions, resolveRoutingConfig } from './getPrefix';\n\n/**\n * Resolves the locale of the current document outside of React.\n *\n * Generated dynamic entry points need the locale while they are still\n * evaluating, long before a provider exists, so this reproduces the precedence\n * a provider applies — URL first (which is what a router hands the provider as\n * `locale`), then browser storage, then the configured default. Agreeing with\n * the provider is what makes the preloaded read hit; disagreeing is safe but\n * costs a load, since the consumer compares locales before using a preloaded\n * dictionary.\n *\n * @param options - Routing overrides, defaulting to the built configuration.\n * @returns The resolved locale, falling back to the default locale off-browser.\n */\nexport const resolveBrowserLocale = (\n options?: RoutingOptions\n): LocalesValues => {\n const { defaultLocale, mode, domains } = resolveRoutingConfig(options);\n\n if (typeof window === 'undefined') return defaultLocale;\n\n // A domain mapped to exactly one locale identifies it on its own, and no URL\n // prefix is emitted for it — so it has to be consulted before the path.\n if (domains) {\n const localeFromDomain = getLocaleFromDomain(\n window.location.hostname,\n domains\n );\n if (localeFromDomain) return localeFromDomain;\n }\n\n // Only a URL that actually carries a locale may take precedence over a\n // stored preference: in `no-prefix`, and in `search-params` without the\n // parameter, `getLocaleFromPath` answers the default locale — which is not\n // what a provider would receive from its router, and would shadow storage.\n const urlCarriesLocale =\n mode === 'prefix-all' ||\n mode === 'prefix-no-default' ||\n (mode === 'search-params' &&\n new URLSearchParams(window.location.search).has('locale'));\n\n if (urlCarriesLocale) {\n const localeFromPath = getLocaleFromPath(\n window.location.pathname + window.location.search,\n options\n );\n if (localeFromPath) return localeFromPath;\n }\n\n return getLocaleFromStorageClient() ?? defaultLocale;\n};\n\n/** URL (path + search) the memoized locale below was resolved against. */\nlet memoizedUrl: string | undefined;\nlet memoizedLocale: LocalesValues | undefined;\n\n/**\n * {@link resolveBrowserLocale}, memoized for the current URL.\n *\n * Every generated dynamic entry point calls this as it evaluates, and an\n * application has hundreds of them — repeating the cookie and storage reads\n * that many times on startup is pure cost, since they cannot change while a\n * page is being assembled.\n *\n * Keying the cache on the URL rather than caching forever keeps a client-side\n * locale change honest: navigating to another locale's URL loads further\n * chunks with that locale, instead of preloading the old one and silently\n * falling back to an asynchronous read.\n */\nexport const getPreloadLocale = (): LocalesValues => {\n const currentUrl =\n typeof window === 'undefined'\n ? ''\n : window.location.pathname + window.location.search;\n\n if (memoizedLocale === undefined || memoizedUrl !== currentUrl) {\n memoizedUrl = currentUrl;\n memoizedLocale = resolveBrowserLocale();\n }\n\n return memoizedLocale;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,wBACX,YACkB;CAClB,MAAM,EAAE,eAAe,MAAM,YAAY,qBAAqB,OAAO;CAErE,IAAI,OAAO,WAAW,aAAa,OAAO;CAI1C,IAAI,SAAS;EACX,MAAM,mBAAmB,oBACvB,OAAO,SAAS,UAChB,OACF;EACA,IAAI,kBAAkB,OAAO;CAC/B;CAYA,IALE,SAAS,gBACT,SAAS,uBACR,SAAS,mBACR,IAAI,gBAAgB,OAAO,SAAS,MAAM,CAAC,CAAC,IAAI,QAAQ,GAEtC;EACpB,MAAM,iBAAiB,kBACrB,OAAO,SAAS,WAAW,OAAO,SAAS,QAC3C,OACF;EACA,IAAI,gBAAgB,OAAO;CAC7B;CAEA,OAAO,2BAA2B,KAAK;AACzC;;AAGA,IAAI;AACJ,IAAI;;;;;;;;;;;;;;AAeJ,MAAa,yBAAwC;CACnD,MAAM,aACJ,OAAO,WAAW,cACd,KACA,OAAO,SAAS,WAAW,OAAO,SAAS;CAEjD,IAAI,mBAAmB,UAAa,gBAAgB,YAAY;EAC9D,cAAc;EACd,iBAAiB,qBAAqB;CACxC;CAEA,OAAO;AACT"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import { internationalization } from "@intlayer/config/built";
|
|
2
|
+
|
|
1
3
|
//#region src/localization/rewriteUtils.ts
|
|
2
4
|
/**
|
|
3
|
-
* True when rewrite rules are explicitly disabled at build time
|
|
4
|
-
* (INTLAYER_ROUTING_REWRITE_RULES === 'false').
|
|
5
|
-
*/
|
|
6
|
-
const TREE_SHAKE_REWRITE = process.env.INTLAYER_ROUTING_REWRITE_RULES === "false";
|
|
7
|
-
/**
|
|
8
5
|
* Normalizes legacy Record format or extracts specialized rules from RewriteObject.
|
|
6
|
+
*
|
|
7
|
+
* Idempotent: already-normalized rules are handed back untouched, so a caller
|
|
8
|
+
* holding resolved rules can pass them on without re-normalizing.
|
|
9
9
|
*/
|
|
10
10
|
const getRewriteRules = (rewrite, context = "url") => {
|
|
11
|
-
if (!rewrite ||
|
|
11
|
+
if (!rewrite || process.env.INTLAYER_ROUTING_REWRITE_RULES && process.env.INTLAYER_ROUTING_REWRITE_RULES === "false") return void 0;
|
|
12
|
+
if (Array.isArray(rewrite.rules)) return rewrite;
|
|
12
13
|
if ("url" in rewrite) return rewrite[context];
|
|
13
14
|
return { rules: Object.entries(rewrite).map(([canonical, localized]) => ({
|
|
14
15
|
canonical: canonical.startsWith("/") ? canonical.replace(/\[([^\]]+)\]/g, ":$1") : `/${canonical.replace(/\[([^\]]+)\]/g, ":$1")}`,
|
|
@@ -49,7 +50,7 @@ const extractParams = (url, pattern) => {
|
|
|
49
50
|
* If locale is provided, only check for that locale. Otherwise, check for all locales.
|
|
50
51
|
*/
|
|
51
52
|
const getCanonicalPath = (localizedPath, locale, rewriteRules) => {
|
|
52
|
-
if (!rewriteRules ||
|
|
53
|
+
if (!rewriteRules || process.env.INTLAYER_ROUTING_REWRITE_RULES && process.env.INTLAYER_ROUTING_REWRITE_RULES === "false") return localizedPath;
|
|
53
54
|
for (const rule of rewriteRules.rules) {
|
|
54
55
|
const { canonical, localized } = rule;
|
|
55
56
|
const localesToCheck = locale ? [locale] : Object.keys(localized);
|
|
@@ -63,10 +64,21 @@ const getCanonicalPath = (localizedPath, locale, rewriteRules) => {
|
|
|
63
64
|
return localizedPath;
|
|
64
65
|
};
|
|
65
66
|
/**
|
|
66
|
-
* Given a canonical path (e.g., "/products/123"), finds the localized URL pattern
|
|
67
|
+
* Given a canonical path (e.g., "/products/123"), finds the localized URL pattern
|
|
68
|
+
* (e.g., "/produits/123") and reports whether a rule actually matched.
|
|
69
|
+
*
|
|
70
|
+
* Takes already-normalized rules, so callers that resolve the rules once (proxies,
|
|
71
|
+
* static emitters) can reuse them. {@link getLocalizedPath} is the user-facing
|
|
72
|
+
* counterpart: it accepts the raw `routing.rewrite` configuration, defaults to the
|
|
73
|
+
* project configuration, and returns the path alone.
|
|
74
|
+
*
|
|
75
|
+
* @param canonicalPath - The internal application path (e.g. `/product/:id`, `/about`).
|
|
76
|
+
* @param locale - The target locale. Defaults to the configured default locale.
|
|
77
|
+
* @param rewriteRules - Normalized rules, as returned by {@link getRewriteRules}.
|
|
78
|
+
* @returns The localized path, discriminated on whether a rewrite rule matched.
|
|
67
79
|
*/
|
|
68
|
-
const
|
|
69
|
-
if (!rewriteRules ||
|
|
80
|
+
const resolveLocalizedPath = (canonicalPath, locale, rewriteRules) => {
|
|
81
|
+
if (!rewriteRules || process.env.INTLAYER_ROUTING_REWRITE_RULES && process.env.INTLAYER_ROUTING_REWRITE_RULES === "false") return {
|
|
70
82
|
path: canonicalPath,
|
|
71
83
|
isRewritten: false
|
|
72
84
|
};
|
|
@@ -74,7 +86,7 @@ const getLocalizedPath = (canonicalPath, locale, rewriteRules) => {
|
|
|
74
86
|
const { canonical, localized } = rule;
|
|
75
87
|
const params = extractParams(canonicalPath, canonical);
|
|
76
88
|
if (params) {
|
|
77
|
-
const targetPattern = localized[locale];
|
|
89
|
+
const targetPattern = localized[locale ?? internationalization.defaultLocale];
|
|
78
90
|
if (targetPattern) return {
|
|
79
91
|
path: fillPath(targetPattern, params),
|
|
80
92
|
isRewritten: true
|
|
@@ -99,14 +111,14 @@ const getInternalPath = (canonicalPath, locale) => {
|
|
|
99
111
|
* Given a current pathname and locale, returns the pretty localized path if a rewrite rule exists and the path is not already localized.
|
|
100
112
|
*/
|
|
101
113
|
const getRewritePath = (pathname, locale, rewrite) => {
|
|
102
|
-
if (
|
|
114
|
+
if (process.env.INTLAYER_ROUTING_REWRITE_RULES && process.env.INTLAYER_ROUTING_REWRITE_RULES === "false") return void 0;
|
|
103
115
|
const rules = getRewriteRules(rewrite, "url");
|
|
104
116
|
if (!rules) return void 0;
|
|
105
117
|
const canonicalPath = getCanonicalPath(pathname, void 0, rules);
|
|
106
|
-
const { path: localizedPath, isRewritten } =
|
|
118
|
+
const { path: localizedPath, isRewritten } = resolveLocalizedPath(canonicalPath, locale, rules);
|
|
107
119
|
if (isRewritten && localizedPath !== pathname) return localizedPath;
|
|
108
120
|
};
|
|
109
121
|
|
|
110
122
|
//#endregion
|
|
111
|
-
export { getCanonicalPath, getInternalPath,
|
|
123
|
+
export { getCanonicalPath, getInternalPath, getRewritePath, getRewriteRules, resolveLocalizedPath };
|
|
112
124
|
//# sourceMappingURL=rewriteUtils.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rewriteUtils.mjs","names":[],"sources":["../../../src/localization/rewriteUtils.ts"],"sourcesContent":["// ── 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 rewrite rules are explicitly disabled at build time\n * (INTLAYER_ROUTING_REWRITE_RULES === 'false').\n */\nconst TREE_SHAKE_REWRITE =\n process.env.INTLAYER_ROUTING_REWRITE_RULES === 'false';\n\nimport type { Locale } from '@intlayer/types/allLocales';\nimport type {\n RewriteObject,\n RewriteRules,\n RoutingConfig,\n} from '@intlayer/types/config';\nimport type { LocalesValues } from '@intlayer/types/module_augmentation';\n\nexport type LocalizedPathResult = {\n path: string;\n isRewritten: boolean;\n};\n\n/**\n * Normalizes legacy Record format or extracts specialized rules from RewriteObject.\n */\nexport const getRewriteRules = (\n rewrite: RoutingConfig['rewrite'],\n context: keyof RewriteObject = 'url'\n): RewriteRules | undefined => {\n if (!rewrite || TREE_SHAKE_REWRITE) return undefined;\n\n if ('url' in rewrite) {\n return (rewrite as RewriteObject)[context];\n }\n\n // Normalize legacy format\n return {\n rules: Object.entries(rewrite).map(([canonical, localized]) => ({\n // Normalize canonical path\n canonical: canonical.startsWith('/')\n ? canonical.replace(/\\[([^\\]]+)\\]/g, ':$1')\n : `/${canonical.replace(/\\[([^\\]]+)\\]/g, ':$1')}`,\n\n // Normalize localized path\n localized: Object.fromEntries(\n Object.entries(localized).map(([locale, pattern]) => {\n const normalizedPattern = pattern?.startsWith('/')\n ? pattern.replace(/\\[([^\\]]+)\\]/g, ':$1')\n : `/${(pattern || '').replace(/\\[([^\\]]+)\\]/g, ':$1')}`;\n return [locale, normalizedPattern];\n })\n ),\n })),\n };\n};\n\n/**\n * Converts normalized pattern to Regex.\n * Internal syntax supports:\n * - :param -> ([^/]+) (one segment)\n * - :param* -> (.*) (zero or more segments)\n * - :param+ -> (.+) (one or more segments)\n * - :param? -> ([^/]*) (zero or one segment)\n */\nconst patternToRegex = (pattern: string) => {\n const regexString = pattern\n .replace(/\\//g, '\\\\/') // Escape slashes\n .replace(/\\\\\\/:(?:[^/\\\\*+?]+)\\*/g, '(?:\\\\/(.*))?') // /:param*\n .replace(/\\\\\\/:(?:[^/\\\\*+?]+)\\?/g, '(?:\\\\/([^\\\\/]+))?') // /:param?\n .replace(/:([^/\\\\*+?]+)\\*/g, '(.*)') // :param* (if no leading slash)\n .replace(/:([^/\\\\*+?]+)\\?/g, '([^\\\\/]*)') // :param? (if no leading slash)\n .replace(/:([^/\\\\*+?]+)\\+/g, '(.+)') // :param+\n .replace(/:([^/\\\\*+?]+)/g, '([^\\\\/]+)'); // :param\n\n return new RegExp(`^${regexString}$`);\n};\n\n/**\n * Replaces route parameters in a path with provided values.\n */\nconst fillPath = (pattern: string, params: string[]) => {\n let index = 0;\n return (\n pattern\n .replace(/:([^/\\\\*+?]+)[*+?]?/g, () => params[index++] ?? '')\n .replace(/\\/+/g, '/')\n .replace(/\\/$/, '') || '/'\n );\n};\n\n/**\n * Extract values from a URL based on a pattern.\n */\nconst extractParams = (url: string, pattern: string): string[] | null => {\n const regex = patternToRegex(pattern);\n const match = url.match(regex);\n return match ? match.slice(1) : null;\n};\n\n/**\n * Given a localized URL (e.g., \"/produits/123\"), finds the canonical internal path (e.g., \"/products/123\").\n * If locale is provided, only check for that locale. Otherwise, check for all locales.\n */\nexport const getCanonicalPath = (\n localizedPath: string,\n locale?: Locale,\n rewriteRules?: RewriteRules\n): string => {\n if (!rewriteRules || TREE_SHAKE_REWRITE) return localizedPath;\n\n for (const rule of rewriteRules.rules) {\n const { canonical, localized } = rule;\n const localesToCheck = locale ? [locale] : Object.keys(localized);\n\n for (const loc of localesToCheck) {\n const localizedPattern = localized[loc as keyof typeof localized];\n\n if (!localizedPattern) continue;\n\n const params = extractParams(localizedPath, localizedPattern);\n\n if (params) {\n return fillPath(canonical, params);\n }\n }\n }\n\n return localizedPath;\n};\n\n/**\n * Given a canonical path (e.g., \"/products/123\"), finds the localized URL pattern (e.g., \"/produits/123\").\n */\nexport const getLocalizedPath = (\n canonicalPath: string,\n locale: LocalesValues,\n rewriteRules?: RewriteRules\n): LocalizedPathResult => {\n if (!rewriteRules || TREE_SHAKE_REWRITE)\n return { path: canonicalPath, isRewritten: false };\n\n for (const rule of rewriteRules.rules) {\n const { canonical, localized } = rule;\n\n // Check if the input path matches a configured canonical pattern\n const params = extractParams(canonicalPath, canonical);\n\n if (params) {\n const targetPattern = localized[locale as keyof typeof localized];\n\n if (targetPattern) {\n return {\n path: fillPath(targetPattern, params),\n isRewritten: true,\n };\n }\n }\n }\n\n return { path: canonicalPath, isRewritten: false };\n};\n\n/**\n * Returns the internal path for a given canonical path and locale.\n * Ensures the locale prefix is present exactly once.\n */\nexport const getInternalPath = (\n canonicalPath: string,\n locale: Locale\n): string => {\n const pathWithLeadingSlash = canonicalPath.startsWith('/')\n ? canonicalPath\n : `/${canonicalPath}`;\n\n if (\n pathWithLeadingSlash.startsWith(`/${locale}/`) ||\n pathWithLeadingSlash === `/${locale}`\n ) {\n return pathWithLeadingSlash;\n }\n\n return `/${locale}${pathWithLeadingSlash === '/' ? '' : pathWithLeadingSlash}`;\n};\n\n/**\n * Given a current pathname and locale, returns the pretty localized path if a rewrite rule exists and the path is not already localized.\n */\nexport const getRewritePath = (\n pathname: string,\n locale: Locale,\n rewrite?: RoutingConfig['rewrite']\n): string | undefined => {\n if (TREE_SHAKE_REWRITE) return undefined;\n const rules = getRewriteRules(rewrite, 'url');\n if (!rules) return undefined;\n\n // Identify canonical path (relative to root, no locale prefix expected in 'url' context)\n const canonicalPath = getCanonicalPath(pathname, undefined, rules);\n\n // Get the localized path for the current locale\n const { path: localizedPath, isRewritten } = getLocalizedPath(\n canonicalPath,\n locale,\n rules\n );\n\n if (isRewritten && localizedPath !== pathname) {\n return localizedPath;\n }\n\n return undefined;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rewriteUtils.mjs","names":["legacyRules"],"sources":["../../../src/localization/rewriteUtils.ts"],"sourcesContent":["import { internationalization } from '@intlayer/config/built';\nimport type {\n RewriteObject,\n RewriteRules,\n RoutingConfig,\n} from '@intlayer/types/config';\nimport type {\n LocalesValues,\n ResolvedDefaultLocale,\n} from '@intlayer/types/module_augmentation';\n\n/**\n * Outcome of resolving a canonical path against the rewrite rules.\n *\n * Discriminated on `isRewritten`: when no rule matched, `path` is the very\n * input that was passed in, so a literal input keeps its literal type. Only a\n * rewritten path is computed at runtime and therefore widens to `string`.\n */\nexport type LocalizedPathResult<CanonicalPath extends string = string> =\n | { path: CanonicalPath; isRewritten: false }\n | { path: string; isRewritten: true };\n\n/** Adds the leading slash a path may be missing. */\ntype WithLeadingSlash<Path extends string> = Path extends `/${string}`\n ? Path\n : `/${Path}`;\n\n/**\n * Computes the locale-prefixed internal path for a path and a locale, mirroring\n * `getInternalPath`'s runtime behaviour: an already-prefixed path is returned\n * untouched, the root collapses to `/${Locale}` (never a trailing slash), and\n * anything else gets the prefix prepended.\n *\n * Falls back to `string` for a non-literal path, where the prefixed and\n * already-prefixed cases cannot be told apart.\n *\n * @example\n * type A = InternalPath<'/about', 'fr'>; // '/fr/about'\n * type B = InternalPath<'/', 'fr'>; // '/fr'\n * type C = InternalPath<'/fr/about', 'fr'>; // '/fr/about' (already prefixed)\n */\nexport type InternalPath<\n Path extends string,\n Locale extends string,\n> = string extends Path\n ? string\n : WithLeadingSlash<Path> extends infer Prefixed extends string\n ? Prefixed extends `/${Locale}` | `/${Locale}/${string}`\n ? Prefixed\n : Prefixed extends '/'\n ? `/${Locale}`\n : `/${Locale}${Prefixed}`\n : never;\n\n/**\n * Normalizes legacy Record format or extracts specialized rules from RewriteObject.\n *\n * Idempotent: already-normalized rules are handed back untouched, so a caller\n * holding resolved rules can pass them on without re-normalizing.\n */\nexport const getRewriteRules = <\n const Context extends keyof RewriteObject = 'url',\n>(\n rewrite: RoutingConfig['rewrite'] | RewriteRules,\n context: Context = 'url' as Context\n): RewriteRules | undefined => {\n if (\n !rewrite ||\n (process.env.INTLAYER_ROUTING_REWRITE_RULES &&\n process.env.INTLAYER_ROUTING_REWRITE_RULES === 'false')\n )\n return undefined;\n\n if (Array.isArray((rewrite as RewriteRules).rules)) {\n return rewrite as RewriteRules;\n }\n\n if ('url' in rewrite) {\n return (rewrite as RewriteObject)[context];\n }\n\n // Normalize legacy format\n const legacyRules = rewrite as Record<string, Record<string, string>>;\n\n return {\n rules: Object.entries(legacyRules).map(([canonical, localized]) => ({\n // Normalize canonical path\n canonical: canonical.startsWith('/')\n ? canonical.replace(/\\[([^\\]]+)\\]/g, ':$1')\n : `/${canonical.replace(/\\[([^\\]]+)\\]/g, ':$1')}`,\n\n // Normalize localized path\n localized: Object.fromEntries(\n Object.entries(localized).map(([locale, pattern]) => {\n const normalizedPattern = pattern?.startsWith('/')\n ? pattern.replace(/\\[([^\\]]+)\\]/g, ':$1')\n : `/${(pattern || '').replace(/\\[([^\\]]+)\\]/g, ':$1')}`;\n return [locale, normalizedPattern];\n })\n ),\n })),\n };\n};\n\n/**\n * Converts normalized pattern to Regex.\n * Internal syntax supports:\n * - :param -> ([^/]+) (one segment)\n * - :param* -> (.*) (zero or more segments)\n * - :param+ -> (.+) (one or more segments)\n * - :param? -> ([^/]*) (zero or one segment)\n */\nconst patternToRegex = (pattern: string) => {\n const regexString = pattern\n .replace(/\\//g, '\\\\/') // Escape slashes\n .replace(/\\\\\\/:(?:[^/\\\\*+?]+)\\*/g, '(?:\\\\/(.*))?') // /:param*\n .replace(/\\\\\\/:(?:[^/\\\\*+?]+)\\?/g, '(?:\\\\/([^\\\\/]+))?') // /:param?\n .replace(/:([^/\\\\*+?]+)\\*/g, '(.*)') // :param* (if no leading slash)\n .replace(/:([^/\\\\*+?]+)\\?/g, '([^\\\\/]*)') // :param? (if no leading slash)\n .replace(/:([^/\\\\*+?]+)\\+/g, '(.+)') // :param+\n .replace(/:([^/\\\\*+?]+)/g, '([^\\\\/]+)'); // :param\n\n return new RegExp(`^${regexString}$`);\n};\n\n/**\n * Replaces route parameters in a path with provided values.\n */\nconst fillPath = (pattern: string, params: string[]) => {\n let index = 0;\n return (\n pattern\n .replace(/:([^/\\\\*+?]+)[*+?]?/g, () => params[index++] ?? '')\n .replace(/\\/+/g, '/')\n .replace(/\\/$/, '') || '/'\n );\n};\n\n/**\n * Extract values from a URL based on a pattern.\n */\nconst extractParams = (url: string, pattern: string): string[] | null => {\n const regex = patternToRegex(pattern);\n const match = url.match(regex);\n return match ? match.slice(1) : null;\n};\n\n/**\n * Given a localized URL (e.g., \"/produits/123\"), finds the canonical internal path (e.g., \"/products/123\").\n * If locale is provided, only check for that locale. Otherwise, check for all locales.\n */\nexport const getCanonicalPath = <\n const Path extends string,\n const Locale extends LocalesValues = LocalesValues,\n>(\n localizedPath: Path,\n locale?: Locale,\n rewriteRules?: RewriteRules\n): string => {\n if (\n !rewriteRules ||\n (process.env.INTLAYER_ROUTING_REWRITE_RULES &&\n process.env.INTLAYER_ROUTING_REWRITE_RULES === 'false')\n )\n return localizedPath;\n\n for (const rule of rewriteRules.rules) {\n const { canonical, localized } = rule;\n const localesToCheck = locale ? [locale] : Object.keys(localized);\n\n for (const loc of localesToCheck) {\n const localizedPattern = localized[loc as keyof typeof localized];\n\n if (!localizedPattern) continue;\n\n const params = extractParams(localizedPath, localizedPattern);\n\n if (params) {\n return fillPath(canonical, params);\n }\n }\n }\n\n return localizedPath;\n};\n\n/**\n * Given a canonical path (e.g., \"/products/123\"), finds the localized URL pattern\n * (e.g., \"/produits/123\") and reports whether a rule actually matched.\n *\n * Takes already-normalized rules, so callers that resolve the rules once (proxies,\n * static emitters) can reuse them. {@link getLocalizedPath} is the user-facing\n * counterpart: it accepts the raw `routing.rewrite` configuration, defaults to the\n * project configuration, and returns the path alone.\n *\n * @param canonicalPath - The internal application path (e.g. `/product/:id`, `/about`).\n * @param locale - The target locale. Defaults to the configured default locale.\n * @param rewriteRules - Normalized rules, as returned by {@link getRewriteRules}.\n * @returns The localized path, discriminated on whether a rewrite rule matched.\n */\nexport const resolveLocalizedPath = <\n const CanonicalPath extends string,\n const Locale extends LocalesValues = ResolvedDefaultLocale,\n>(\n canonicalPath: CanonicalPath,\n locale?: Locale,\n rewriteRules?: RewriteRules\n): LocalizedPathResult<CanonicalPath> => {\n if (\n !rewriteRules ||\n (process.env.INTLAYER_ROUTING_REWRITE_RULES &&\n process.env.INTLAYER_ROUTING_REWRITE_RULES === 'false')\n )\n return { path: canonicalPath, isRewritten: false };\n\n for (const rule of rewriteRules.rules) {\n const { canonical, localized } = rule;\n\n // Check if the input path matches a configured canonical pattern\n const params = extractParams(canonicalPath, canonical);\n\n if (params) {\n const targetLocale = locale ?? internationalization.defaultLocale;\n const targetPattern = localized[targetLocale as keyof typeof localized];\n\n if (targetPattern) {\n return {\n path: fillPath(targetPattern, params),\n isRewritten: true,\n };\n }\n }\n }\n\n return { path: canonicalPath, isRewritten: false };\n};\n\n/**\n * Returns the internal path for a given canonical path and locale.\n * Ensures the locale prefix is present exactly once.\n */\nexport const getInternalPath = <\n const Path extends string,\n const Locale extends LocalesValues,\n>(\n canonicalPath: Path,\n locale: Locale\n): InternalPath<Path, Locale> => {\n const pathWithLeadingSlash = canonicalPath.startsWith('/')\n ? canonicalPath\n : `/${canonicalPath}`;\n\n if (\n pathWithLeadingSlash.startsWith(`/${locale}/`) ||\n pathWithLeadingSlash === `/${locale}`\n ) {\n return pathWithLeadingSlash as InternalPath<Path, Locale>;\n }\n\n return `/${locale}${\n pathWithLeadingSlash === '/' ? '' : pathWithLeadingSlash\n }` as InternalPath<Path, Locale>;\n};\n\n/**\n * Given a current pathname and locale, returns the pretty localized path if a rewrite rule exists and the path is not already localized.\n */\nexport const getRewritePath = <\n const Path extends string,\n const Locale extends LocalesValues = ResolvedDefaultLocale,\n>(\n pathname: Path,\n locale: Locale,\n rewrite?: RoutingConfig['rewrite']\n): string | undefined => {\n if (\n process.env.INTLAYER_ROUTING_REWRITE_RULES &&\n process.env.INTLAYER_ROUTING_REWRITE_RULES === 'false'\n )\n return undefined;\n const rules = getRewriteRules(rewrite, 'url');\n if (!rules) return undefined;\n\n // Identify canonical path (relative to root, no locale prefix expected in 'url' context)\n const canonicalPath = getCanonicalPath(pathname, undefined, rules);\n\n // Get the localized path for the current locale\n const { path: localizedPath, isRewritten } = resolveLocalizedPath(\n canonicalPath,\n locale,\n rules\n );\n\n if (isRewritten && localizedPath !== pathname) {\n return localizedPath;\n }\n\n return undefined;\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getCanonicalPath, getInternalPath, resolveLocalizedPath } from "./rewriteUtils.mjs";
|
|
2
|
+
import { describe, expectTypeOf, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
//#region src/localization/rewriteUtils.test-d.ts
|
|
5
|
+
/**
|
|
6
|
+
* `getInternalPath` is pure string surgery — no rewrite rules involved — so its
|
|
7
|
+
* result is fully derivable from the arguments whenever both are literals.
|
|
8
|
+
*/
|
|
9
|
+
describe("getInternalPath", () => {
|
|
10
|
+
it("should prefix a literal path with the locale", () => {
|
|
11
|
+
expectTypeOf(getInternalPath("/about", "fr")).toEqualTypeOf();
|
|
12
|
+
});
|
|
13
|
+
it("should collapse the root to a bare prefix, never a trailing slash", () => {
|
|
14
|
+
expectTypeOf(getInternalPath("/", "fr")).toEqualTypeOf();
|
|
15
|
+
});
|
|
16
|
+
it("should leave an already-prefixed path untouched", () => {
|
|
17
|
+
expectTypeOf(getInternalPath("/fr/about", "fr")).toEqualTypeOf();
|
|
18
|
+
expectTypeOf(getInternalPath("/fr", "fr")).toEqualTypeOf();
|
|
19
|
+
});
|
|
20
|
+
it("should add the leading slash a path is missing", () => {
|
|
21
|
+
expectTypeOf(getInternalPath("about", "fr")).toEqualTypeOf();
|
|
22
|
+
});
|
|
23
|
+
it("should widen to string when the path is not a literal", () => {
|
|
24
|
+
expectTypeOf(getInternalPath("/about", "fr")).toEqualTypeOf();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* `resolveLocalizedPath` only computes a new path when a rule matched, so the
|
|
29
|
+
* `isRewritten: false` branch still carries the exact input it was given.
|
|
30
|
+
*/
|
|
31
|
+
describe("resolveLocalizedPath", () => {
|
|
32
|
+
it("should keep the input literal on the non-rewritten branch", () => {
|
|
33
|
+
const result = resolveLocalizedPath("/about", "fr");
|
|
34
|
+
if (!result.isRewritten) expectTypeOf(result.path).toEqualTypeOf();
|
|
35
|
+
else expectTypeOf(result.path).toEqualTypeOf();
|
|
36
|
+
});
|
|
37
|
+
it("should discriminate on isRewritten", () => {
|
|
38
|
+
expectTypeOf(resolveLocalizedPath("/about")).toEqualTypeOf();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe("getCanonicalPath", () => {
|
|
42
|
+
it("should accept an omitted locale (checks every locale)", () => {
|
|
43
|
+
expectTypeOf(getCanonicalPath("/a-propos")).toEqualTypeOf();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
//# sourceMappingURL=rewriteUtils.test-d.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rewriteUtils.test-d.mjs","names":[],"sources":["../../../src/localization/rewriteUtils.test-d.ts"],"sourcesContent":["import { describe, expectTypeOf, it } from 'vitest';\nimport {\n getCanonicalPath,\n getInternalPath,\n resolveLocalizedPath,\n} from './rewriteUtils';\n\n/**\n * `getInternalPath` is pure string surgery — no rewrite rules involved — so its\n * result is fully derivable from the arguments whenever both are literals.\n */\ndescribe('getInternalPath', () => {\n it('should prefix a literal path with the locale', () => {\n expectTypeOf(getInternalPath('/about', 'fr')).toEqualTypeOf<'/fr/about'>();\n });\n\n it('should collapse the root to a bare prefix, never a trailing slash', () => {\n expectTypeOf(getInternalPath('/', 'fr')).toEqualTypeOf<'/fr'>();\n });\n\n it('should leave an already-prefixed path untouched', () => {\n expectTypeOf(\n getInternalPath('/fr/about', 'fr')\n ).toEqualTypeOf<'/fr/about'>();\n expectTypeOf(getInternalPath('/fr', 'fr')).toEqualTypeOf<'/fr'>();\n });\n\n it('should add the leading slash a path is missing', () => {\n expectTypeOf(getInternalPath('about', 'fr')).toEqualTypeOf<'/fr/about'>();\n });\n\n it('should widen to string when the path is not a literal', () => {\n const dynamicPath = '/about' as string;\n\n expectTypeOf(getInternalPath(dynamicPath, 'fr')).toEqualTypeOf<string>();\n });\n});\n\n/**\n * `resolveLocalizedPath` only computes a new path when a rule matched, so the\n * `isRewritten: false` branch still carries the exact input it was given.\n */\ndescribe('resolveLocalizedPath', () => {\n it('should keep the input literal on the non-rewritten branch', () => {\n const result = resolveLocalizedPath('/about', 'fr');\n\n if (!result.isRewritten) {\n expectTypeOf(result.path).toEqualTypeOf<'/about'>();\n } else {\n expectTypeOf(result.path).toEqualTypeOf<string>();\n }\n });\n\n it('should discriminate on isRewritten', () => {\n expectTypeOf(resolveLocalizedPath('/about')).toEqualTypeOf<\n | { path: '/about'; isRewritten: false }\n | { path: string; isRewritten: true }\n >();\n });\n});\n\ndescribe('getCanonicalPath', () => {\n it('should accept an omitted locale (checks every locale)', () => {\n expectTypeOf(getCanonicalPath('/a-propos')).toEqualTypeOf<string>();\n });\n});\n"],"mappings":";;;;;;;;AAWA,SAAS,yBAAyB;CAChC,GAAG,sDAAsD;EACvD,aAAa,gBAAgB,UAAU,IAAI,CAAC,CAAC,CAAC,cAA2B;CAC3E,CAAC;CAED,GAAG,2EAA2E;EAC5E,aAAa,gBAAgB,KAAK,IAAI,CAAC,CAAC,CAAC,cAAqB;CAChE,CAAC;CAED,GAAG,yDAAyD;EAC1D,aACE,gBAAgB,aAAa,IAAI,CACnC,CAAC,CAAC,cAA2B;EAC7B,aAAa,gBAAgB,OAAO,IAAI,CAAC,CAAC,CAAC,cAAqB;CAClE,CAAC;CAED,GAAG,wDAAwD;EACzD,aAAa,gBAAgB,SAAS,IAAI,CAAC,CAAC,CAAC,cAA2B;CAC1E,CAAC;CAED,GAAG,+DAA+D;EAGhE,aAAa,gBAAgB,UAAa,IAAI,CAAC,CAAC,CAAC,cAAsB;CACzE,CAAC;AACH,CAAC;;;;;AAMD,SAAS,8BAA8B;CACrC,GAAG,mEAAmE;EACpE,MAAM,SAAS,qBAAqB,UAAU,IAAI;EAElD,IAAI,CAAC,OAAO,aACV,aAAa,OAAO,IAAI,CAAC,CAAC,cAAwB;OAElD,aAAa,OAAO,IAAI,CAAC,CAAC,cAAsB;CAEpD,CAAC;CAED,GAAG,4CAA4C;EAC7C,aAAa,qBAAqB,QAAQ,CAAC,CAAC,CAAC,cAG3C;CACJ,CAAC;AACH,CAAC;AAED,SAAS,0BAA0B;CACjC,GAAG,+DAA+D;EAChE,aAAa,iBAAiB,WAAW,CAAC,CAAC,CAAC,cAAsB;CACpE,CAAC;AACH,CAAC"}
|
|
@@ -741,6 +741,7 @@ const parseMarkdown = (markdown = "", ctx, options = {}) => {
|
|
|
741
741
|
return {
|
|
742
742
|
ast,
|
|
743
743
|
footnotes,
|
|
744
|
+
references: refs,
|
|
744
745
|
inline
|
|
745
746
|
};
|
|
746
747
|
};
|
|
@@ -751,7 +752,7 @@ const renderMarkdownAst = (parsed, ctx, options = {}) => {
|
|
|
751
752
|
};
|
|
752
753
|
const createElement = createElementFactory(ctx, options);
|
|
753
754
|
const footnotes = parsed.footnotes || [];
|
|
754
|
-
const refs = {};
|
|
755
|
+
const refs = parsed.references ?? {};
|
|
755
756
|
const attrStringToMap = () => null;
|
|
756
757
|
const containsBlockSyntax = () => false;
|
|
757
758
|
const baseRules = createRules(createElement, ctx, options, footnotes, refs, attrStringToMap, containsBlockSyntax, []);
|