@inlang/paraglide-js 2.27.0 → 2.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/bundler-plugins/per-locale-build/index.js +14 -7
- package/dist/bundler-plugins/per-locale-build/index.js.map +1 -1
- package/dist/bundler-plugins/per-locale-build/vite-environments.test.js +235 -2
- package/dist/bundler-plugins/per-locale-build/vite-environments.test.js.map +1 -1
- package/dist/bundler-plugins/unplugin.d.ts +1 -1
- package/dist/bundler-plugins/unplugin.js +79 -10
- package/dist/bundler-plugins/unplugin.js.map +1 -1
- package/dist/bundler-plugins/unplugin.test.js +334 -1
- package/dist/bundler-plugins/unplugin.test.js.map +1 -1
- package/dist/bundler-plugins/vite.js +2 -1
- package/dist/bundler-plugins/vite.js.map +1 -1
- package/dist/bundler-plugins/vite.test.js +1 -1
- package/dist/bundler-plugins/vite.test.js.map +1 -1
- package/dist/cli/commands/compile/command.js +13 -0
- package/dist/cli/commands/compile/command.js.map +1 -1
- package/dist/cli/commands/compile/command.test.js +87 -3
- package/dist/cli/commands/compile/command.test.js.map +1 -1
- package/dist/cli/commands/init/command.d.ts +5 -0
- package/dist/cli/commands/init/command.js +213 -59
- package/dist/cli/commands/init/command.js.map +1 -1
- package/dist/cli/commands/init/command.test.d.ts +1 -0
- package/dist/cli/commands/init/command.test.js +123 -0
- package/dist/cli/commands/init/command.test.js.map +1 -0
- package/dist/cli/init-options.d.ts +11 -0
- package/dist/cli/init-options.js +2 -0
- package/dist/cli/init-options.js.map +1 -0
- package/dist/cli/steps/add-inlang-check.js +2 -2
- package/dist/cli/steps/add-inlang-check.js.map +1 -1
- package/dist/cli/steps/add-message-lint.d.ts +34 -0
- package/dist/cli/steps/add-message-lint.js +105 -0
- package/dist/cli/steps/add-message-lint.js.map +1 -0
- package/dist/cli/steps/add-message-lint.test.d.ts +1 -0
- package/dist/cli/steps/add-message-lint.test.js +74 -0
- package/dist/cli/steps/add-message-lint.test.js.map +1 -0
- package/dist/cli/steps/add-vite-plugin.d.ts +18 -0
- package/dist/cli/steps/add-vite-plugin.js +34 -6
- package/dist/cli/steps/add-vite-plugin.js.map +1 -1
- package/dist/cli/steps/add-vite-plugin.test.js +25 -9
- package/dist/cli/steps/add-vite-plugin.test.js.map +1 -1
- package/dist/cli/steps/check-for-uncomitted-changes.d.ts +3 -0
- package/dist/cli/steps/check-for-uncomitted-changes.js +15 -3
- package/dist/cli/steps/check-for-uncomitted-changes.js.map +1 -1
- package/dist/cli/steps/detect-bundler.d.ts +1 -0
- package/dist/cli/steps/detect-bundler.js +7 -1
- package/dist/cli/steps/detect-bundler.js.map +1 -1
- package/dist/cli/steps/initialize-inlang-project.d.ts +4 -3
- package/dist/cli/steps/initialize-inlang-project.js +60 -157
- package/dist/cli/steps/initialize-inlang-project.js.map +1 -1
- package/dist/cli/steps/maybe-add-machine-translation.d.ts +4 -0
- package/dist/cli/steps/maybe-add-machine-translation.js +11 -6
- package/dist/cli/steps/maybe-add-machine-translation.js.map +1 -1
- package/dist/cli/steps/maybe-add-sherlock.d.ts +2 -0
- package/dist/cli/steps/maybe-add-sherlock.js +12 -6
- package/dist/cli/steps/maybe-add-sherlock.js.map +1 -1
- package/dist/cli/steps/maybe-add-sherlock.test.d.ts +1 -0
- package/dist/cli/steps/maybe-add-sherlock.test.js +44 -0
- package/dist/cli/steps/maybe-add-sherlock.test.js.map +1 -0
- package/dist/cli/steps/prompt-for-outdir.d.ts +2 -0
- package/dist/cli/steps/prompt-for-outdir.js +4 -0
- package/dist/cli/steps/prompt-for-outdir.js.map +1 -1
- package/dist/cli/steps/update-package-json.js +8 -13
- package/dist/cli/steps/update-package-json.js.map +1 -1
- package/dist/cli/steps/update-ts-config.d.ts +6 -0
- package/dist/cli/steps/update-ts-config.js +27 -4
- package/dist/cli/steps/update-ts-config.js.map +1 -1
- package/dist/cli/utils.d.ts +2 -0
- package/dist/cli/utils.js +6 -0
- package/dist/cli/utils.js.map +1 -1
- package/dist/cli/utils.test.d.ts +1 -0
- package/dist/cli/utils.test.js +16 -0
- package/dist/cli/utils.test.js.map +1 -0
- package/dist/compiler/compile-annotation.js +6 -1
- package/dist/compiler/compile-annotation.js.map +1 -1
- package/dist/compiler/compile-bundle.d.ts +4 -0
- package/dist/compiler/compile-bundle.js +67 -2
- package/dist/compiler/compile-bundle.js.map +1 -1
- package/dist/compiler/compile-bundle.test.js +214 -1
- package/dist/compiler/compile-bundle.test.js.map +1 -1
- package/dist/compiler/compile-lifecycle.test.d.ts +1 -0
- package/dist/compiler/compile-lifecycle.test.js +69 -0
- package/dist/compiler/compile-lifecycle.test.js.map +1 -0
- package/dist/compiler/compile-local-variable.test.js +14 -14
- package/dist/compiler/compile-local-variable.test.js.map +1 -1
- package/dist/compiler/compile-message.exact-number.test.js +1 -1
- package/dist/compiler/compile-message.exact-number.test.js.map +1 -1
- package/dist/compiler/compile-message.locals.test.js +1 -1
- package/dist/compiler/compile-message.locals.test.js.map +1 -1
- package/dist/compiler/compile-message.test.js +2 -2
- package/dist/compiler/compile-message.test.js.map +1 -1
- package/dist/compiler/compile-pattern.test.js +5 -5
- package/dist/compiler/compile-pattern.test.js.map +1 -1
- package/dist/compiler/compile-project.js +1 -0
- package/dist/compiler/compile-project.js.map +1 -1
- package/dist/compiler/compile-project.test.js +94 -1
- package/dist/compiler/compile-project.test.js.map +1 -1
- package/dist/compiler/compile.js +36 -17
- package/dist/compiler/compile.js.map +1 -1
- package/dist/compiler/compile.test.js +70 -0
- package/dist/compiler/compile.test.js.map +1 -1
- package/dist/compiler/compiler-options.d.ts +41 -0
- package/dist/compiler/compiler-options.js.map +1 -1
- package/dist/compiler/output-structure/message-modules.js +1 -1
- package/dist/compiler/output-structure/message-modules.js.map +1 -1
- package/dist/compiler/registry.js +71 -13
- package/dist/compiler/registry.js.map +1 -1
- package/dist/compiler/registry.test.d.ts +1 -0
- package/dist/compiler/registry.test.js +291 -0
- package/dist/compiler/registry.test.js.map +1 -0
- package/dist/compiler/runtime/create-runtime.d.ts +2 -0
- package/dist/compiler/runtime/create-runtime.js +9 -0
- package/dist/compiler/runtime/create-runtime.js.map +1 -1
- package/dist/compiler/runtime/extract-locale-from-cookie.js +8 -1
- package/dist/compiler/runtime/extract-locale-from-cookie.js.map +1 -1
- package/dist/compiler/runtime/extract-locale-from-cookie.test.js +26 -0
- package/dist/compiler/runtime/extract-locale-from-cookie.test.js.map +1 -1
- package/dist/compiler/runtime/extract-locale-from-header.js +47 -5
- package/dist/compiler/runtime/extract-locale-from-header.js.map +1 -1
- package/dist/compiler/runtime/extract-locale-from-header.test.js +58 -0
- package/dist/compiler/runtime/extract-locale-from-header.test.js.map +1 -1
- package/dist/compiler/runtime/get-locale.js +7 -1
- package/dist/compiler/runtime/get-locale.js.map +1 -1
- package/dist/compiler/runtime/get-locale.test.js +95 -0
- package/dist/compiler/runtime/get-locale.test.js.map +1 -1
- package/dist/compiler/runtime/localize-href.d.ts +8 -0
- package/dist/compiler/runtime/localize-href.js +60 -0
- package/dist/compiler/runtime/localize-href.js.map +1 -1
- package/dist/compiler/runtime/localize-href.test.js +227 -1
- package/dist/compiler/runtime/localize-href.test.js.map +1 -1
- package/dist/compiler/runtime/set-locale.js +24 -6
- package/dist/compiler/runtime/set-locale.js.map +1 -1
- package/dist/compiler/runtime/set-locale.test.js +182 -0
- package/dist/compiler/runtime/set-locale.test.js.map +1 -1
- package/dist/compiler/runtime/type.d.ts +2 -0
- package/dist/compiler/runtime/type.js.map +1 -1
- package/dist/compiler/runtime/type.test.js +34 -0
- package/dist/compiler/runtime/type.test.js.map +1 -1
- package/dist/compiler/runtime/variables.d.ts +2 -0
- package/dist/compiler/runtime/variables.js +7 -0
- package/dist/compiler/runtime/variables.js.map +1 -1
- package/dist/compiler/server/middleware.d.ts +8 -2
- package/dist/compiler/server/middleware.js +175 -7
- package/dist/compiler/server/middleware.js.map +1 -1
- package/dist/compiler/server/middleware.test.js +750 -3
- package/dist/compiler/server/middleware.test.js.map +1 -1
- package/dist/eslint/index.d.ts +4 -0
- package/dist/eslint/index.js +8 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/eslint/no-dynamic-message-access.d.ts +3 -0
- package/dist/eslint/no-dynamic-message-access.js +150 -0
- package/dist/eslint/no-dynamic-message-access.js.map +1 -0
- package/dist/eslint/no-dynamic-message-access.test.d.ts +1 -0
- package/dist/eslint/no-dynamic-message-access.test.js +126 -0
- package/dist/eslint/no-dynamic-message-access.test.js.map +1 -0
- package/dist/eslint/oxlint.test.d.ts +1 -0
- package/dist/eslint/oxlint.test.js +64 -0
- package/dist/eslint/oxlint.test.js.map +1 -0
- package/dist/services/config/config-schema.d.ts +2 -0
- package/dist/services/config/config-schema.js +2 -0
- package/dist/services/config/config-schema.js.map +1 -1
- package/dist/services/config/load-paraglide-config.test.js +12 -0
- package/dist/services/config/load-paraglide-config.test.js.map +1 -1
- package/dist/services/env-variables/index.js +1 -1
- package/dist/services/env-variables/index.js.map +1 -1
- package/dist/services/file-handling/write-output.bench.js +51 -11
- package/dist/services/file-handling/write-output.bench.js.map +1 -1
- package/dist/services/file-handling/write-output.js +60 -20
- package/dist/services/file-handling/write-output.js.map +1 -1
- package/dist/services/file-handling/write-output.test.js +175 -0
- package/dist/services/file-handling/write-output.test.js.map +1 -1
- package/package.json +14 -4
|
@@ -31,6 +31,10 @@ import { deLocalizeUrl, localizeUrl } from "./localize-url.js";
|
|
|
31
31
|
* // Cross-origin links remain absolute
|
|
32
32
|
* localizeHref("https://other-site.com/about")
|
|
33
33
|
* // => "https://other-site.com/de/about"
|
|
34
|
+
*
|
|
35
|
+
* // Protocol-relative links stay protocol-relative
|
|
36
|
+
* localizeHref("//other-site.com/about")
|
|
37
|
+
* // => "//other-site.com/de/about"
|
|
34
38
|
* ```
|
|
35
39
|
*
|
|
36
40
|
* For server-side URL localization (e.g., in middleware), use `localizeUrl()`
|
|
@@ -46,6 +50,10 @@ export function localizeHref(href, options) {
|
|
|
46
50
|
const locale = options?.locale ?? currentLocale;
|
|
47
51
|
const url = new URL(href, getUrlOrigin());
|
|
48
52
|
const localized = localizeUrl(url, { locale });
|
|
53
|
+
const protocolRelativeHref = serializeProtocolRelativeHref(href, url, localized);
|
|
54
|
+
if (protocolRelativeHref !== undefined) {
|
|
55
|
+
return protocolRelativeHref;
|
|
56
|
+
}
|
|
49
57
|
// if the origin is identical and the href is relative,
|
|
50
58
|
// return the relative path
|
|
51
59
|
if (href.startsWith("/") && url.origin === localized.origin) {
|
|
@@ -94,6 +102,10 @@ export function localizeHref(href, options) {
|
|
|
94
102
|
* // Cross-origin links remain absolute
|
|
95
103
|
* deLocalizeHref("https://example.com/de/about")
|
|
96
104
|
* // => "https://example.com/about"
|
|
105
|
+
*
|
|
106
|
+
* // Protocol-relative links stay protocol-relative
|
|
107
|
+
* deLocalizeHref("//example.com/de/about")
|
|
108
|
+
* // => "//example.com/about"
|
|
97
109
|
* ```
|
|
98
110
|
*
|
|
99
111
|
* For server-side URL de-localization (e.g., in middleware), use `deLocalizeUrl()`
|
|
@@ -105,6 +117,10 @@ export function localizeHref(href, options) {
|
|
|
105
117
|
export function deLocalizeHref(href) {
|
|
106
118
|
const url = new URL(href, getUrlOrigin());
|
|
107
119
|
const deLocalized = deLocalizeUrl(url);
|
|
120
|
+
const protocolRelativeHref = serializeProtocolRelativeHref(href, url, deLocalized);
|
|
121
|
+
if (protocolRelativeHref !== undefined) {
|
|
122
|
+
return protocolRelativeHref;
|
|
123
|
+
}
|
|
108
124
|
// If the origin is identical and the href is relative,
|
|
109
125
|
// return the relative path instead of the full URL.
|
|
110
126
|
if (href.startsWith("/") && url.origin === deLocalized.origin) {
|
|
@@ -112,4 +128,48 @@ export function deLocalizeHref(href) {
|
|
|
112
128
|
}
|
|
113
129
|
return deLocalized.href;
|
|
114
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* @param {string} href
|
|
133
|
+
* @param {URL} url
|
|
134
|
+
* @param {URL} localized
|
|
135
|
+
* @returns {string | undefined} The serialized href, or undefined if it is not protocol-relative.
|
|
136
|
+
*/
|
|
137
|
+
function serializeProtocolRelativeHref(href, url, localized) {
|
|
138
|
+
const first = href.charCodeAt(0);
|
|
139
|
+
const second = href.charCodeAt(1);
|
|
140
|
+
if (first > 0x20 && first !== 0x2f && first !== 0x5c)
|
|
141
|
+
return undefined;
|
|
142
|
+
if (first === 0x2f && second > 0x20 && second !== 0x2f && second !== 0x5c)
|
|
143
|
+
return undefined;
|
|
144
|
+
// URL parsing removes tabs/newlines and trims leading C0 controls and spaces.
|
|
145
|
+
const normalizedHref = href.replace(/[\t\n\r]/g, "");
|
|
146
|
+
let start = 0;
|
|
147
|
+
while (normalizedHref.charCodeAt(start) <= 0x20)
|
|
148
|
+
start++;
|
|
149
|
+
let end = normalizedHref.length;
|
|
150
|
+
while (end > start && normalizedHref.charCodeAt(end - 1) <= 0x20)
|
|
151
|
+
end--;
|
|
152
|
+
const relativeHref = normalizedHref.slice(start, end);
|
|
153
|
+
// Special schemes also treat backslashes as authority separators.
|
|
154
|
+
const specialScheme = /^(https?|wss?|ftp|file):$/.test(url.protocol);
|
|
155
|
+
const separators = specialScheme ? /^[/\\]{2}/ : /^\/\//;
|
|
156
|
+
if (!separators.test(relativeHref))
|
|
157
|
+
return undefined;
|
|
158
|
+
// Keep configured protocol and port changes independent of the page's scheme.
|
|
159
|
+
if (localized.protocol !== url.protocol || localized.port !== url.port) {
|
|
160
|
+
return localized.href;
|
|
161
|
+
}
|
|
162
|
+
// Otherwise leave the scheme to the page, including outside server middleware.
|
|
163
|
+
const result = localized.href.slice(localized.protocol.length);
|
|
164
|
+
if (localized.port || url.protocol === "file:" || !url.hostname)
|
|
165
|
+
return result;
|
|
166
|
+
// URL drops default ports. Recover an explicit port from the original authority.
|
|
167
|
+
const authority = (specialScheme ? relativeHref.replace(/^[/\\]+/, "") : relativeHref.slice(2)).split(specialScheme ? /[/\\?#]/ : /[/?#]/, 1)[0];
|
|
168
|
+
const port = authority?.match(/:(\d+)$/)?.[1];
|
|
169
|
+
if (port === undefined)
|
|
170
|
+
return result;
|
|
171
|
+
const pathStart = result.indexOf("/", 2);
|
|
172
|
+
const authorityEnd = pathStart === -1 ? result.length : pathStart;
|
|
173
|
+
return `${result.slice(0, authorityEnd)}:${Number(port)}${result.slice(authorityEnd)}`;
|
|
174
|
+
}
|
|
115
175
|
//# sourceMappingURL=localize-href.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localize-href.js","sourceRoot":"","sources":["../../../src/compiler/runtime/localize-href.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,UAAU,YAAY,CAAC,IAAI,EAAE,OAAO;IACzC,MAAM,aAAa,GAAG,SAAS,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,aAAa,CAAC;IAChD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;IAE1C,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAE/C,uDAAuD;IACvD,2BAA2B;IAC3B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;QAC7D,sFAAsF;QACtF,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC;YAC9B,MAAM,sBAAsB,GAAG,WAAW,CAAC,GAAG,EAAE;gBAC/C,MAAM,EAAE,aAAa;aACrB,CAAC,CAAC;YACH,IAAI,sBAAsB,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;gBACxD,OAAO,SAAS,CAAC,IAAI,CAAC;YACvB,CAAC;QACF,CAAC;QACD,OAAO,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC;IAC/D,CAAC;IAED,OAAO,SAAS,CAAC,IAAI,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,UAAU,cAAc,CAAC,IAAI;IAClC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;IAE1C,MAAM,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAEvC,uDAAuD;IACvD,oDAAoD;IACpD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM,EAAE,CAAC;QAC/D,OAAO,WAAW,CAAC,QAAQ,GAAG,WAAW,CAAC,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC;IACrE,CAAC;IAED,OAAO,WAAW,CAAC,IAAI,CAAC;AACzB,CAAC","sourcesContent":["import { getLocale } from \"./get-locale.js\";\nimport { getUrlOrigin } from \"./get-url-origin.js\";\nimport { deLocalizeUrl, localizeUrl } from \"./localize-url.js\";\n\n/**\n * High-level URL localization function optimized for client-side UI usage.\n *\n * This is a convenience wrapper around `localizeUrl()` that provides features\n * needed in UI:\n *\n * - Accepts relative paths (e.g., \"/about\")\n * - Returns relative paths when possible\n * - Automatically detects current locale if not specified\n * - Handles string input/output instead of URL objects\n *\n * @see https://paraglidejs.com/i18n-routing\n *\n * @example\n * ```typescript\n * // In a React/Vue/Svelte component\n * const NavLink = ({ href }) => {\n * // Automatically uses current locale, keeps path relative\n * return <a href={localizeHref(href)}>...</a>;\n * };\n *\n * // Examples:\n * localizeHref(\"/about\")\n * // => \"/de/about\" (if current locale is \"de\")\n * localizeHref(\"/store\", { locale: \"fr\" })\n * // => \"/fr/store\" (explicit locale)\n *\n * // Cross-origin links remain absolute\n * localizeHref(\"https://other-site.com/about\")\n * // => \"https://other-site.com/de/about\"\n * ```\n *\n * For server-side URL localization (e.g., in middleware), use `localizeUrl()`\n * which provides more precise control over URL handling.\n *\n * @param {string} href - The href to localize (can be relative or absolute)\n * @param {object} [options] - Options for localization\n * @param {Locale} [options.locale] - Target locale. If not provided, uses `getLocale()`\n * @returns {string} The localized href, relative if input was relative\n */\nexport function localizeHref(href, options) {\n\tconst currentLocale = getLocale();\n\tconst locale = options?.locale ?? currentLocale;\n\tconst url = new URL(href, getUrlOrigin());\n\n\tconst localized = localizeUrl(url, { locale });\n\n\t// if the origin is identical and the href is relative,\n\t// return the relative path\n\tif (href.startsWith(\"/\") && url.origin === localized.origin) {\n\t\t// check for cross origin localization in which case an absolute URL must be returned.\n\t\tif (locale !== currentLocale) {\n\t\t\tconst localizedCurrentLocale = localizeUrl(url, {\n\t\t\t\tlocale: currentLocale,\n\t\t\t});\n\t\t\tif (localizedCurrentLocale.origin !== localized.origin) {\n\t\t\t\treturn localized.href;\n\t\t\t}\n\t\t}\n\t\treturn localized.pathname + localized.search + localized.hash;\n\t}\n\n\treturn localized.href;\n}\n\n/**\n * High-level URL de-localization function optimized for client-side UI usage.\n *\n * This is a convenience wrapper around `deLocalizeUrl()` that provides features\n * needed in the UI:\n *\n * - Accepts relative paths (e.g., \"/de/about\")\n * - Returns relative paths when possible\n * - Handles string input/output instead of URL objects\n *\n * @see https://paraglidejs.com/i18n-routing\n *\n * @example\n * ```typescript\n * // In a React/Vue/Svelte component\n * const LocaleSwitcher = ({ href }) => {\n * // Remove locale prefix before switching\n * const baseHref = deLocalizeHref(href);\n * return locales.map(locale =>\n * <a href={localizeHref(baseHref, { locale })}>\n * Switch to {locale}\n * </a>\n * );\n * };\n *\n * // Examples:\n * deLocalizeHref(\"/de/about\") // => \"/about\"\n * deLocalizeHref(\"/fr/store\") // => \"/store\"\n *\n * // Cross-origin links remain absolute\n * deLocalizeHref(\"https://example.com/de/about\")\n * // => \"https://example.com/about\"\n * ```\n *\n * For server-side URL de-localization (e.g., in middleware), use `deLocalizeUrl()`\n * which provides more precise control over URL handling.\n *\n * @param {string} href - The href to de-localize (can be relative or absolute)\n * @returns {string} The de-localized href, relative if input was relative\n */\nexport function deLocalizeHref(href) {\n\tconst url = new URL(href, getUrlOrigin());\n\n\tconst deLocalized = deLocalizeUrl(url);\n\n\t// If the origin is identical and the href is relative,\n\t// return the relative path instead of the full URL.\n\tif (href.startsWith(\"/\") && url.origin === deLocalized.origin) {\n\t\treturn deLocalized.pathname + deLocalized.search + deLocalized.hash;\n\t}\n\n\treturn deLocalized.href;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"localize-href.js","sourceRoot":"","sources":["../../../src/compiler/runtime/localize-href.js"],"names":[],"mappings":"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","sourcesContent":["import { getLocale } from \"./get-locale.js\";\nimport { getUrlOrigin } from \"./get-url-origin.js\";\nimport { deLocalizeUrl, localizeUrl } from \"./localize-url.js\";\n\n/**\n * High-level URL localization function optimized for client-side UI usage.\n *\n * This is a convenience wrapper around `localizeUrl()` that provides features\n * needed in UI:\n *\n * - Accepts relative paths (e.g., \"/about\")\n * - Returns relative paths when possible\n * - Automatically detects current locale if not specified\n * - Handles string input/output instead of URL objects\n *\n * @see https://paraglidejs.com/i18n-routing\n *\n * @example\n * ```typescript\n * // In a React/Vue/Svelte component\n * const NavLink = ({ href }) => {\n * // Automatically uses current locale, keeps path relative\n * return <a href={localizeHref(href)}>...</a>;\n * };\n *\n * // Examples:\n * localizeHref(\"/about\")\n * // => \"/de/about\" (if current locale is \"de\")\n * localizeHref(\"/store\", { locale: \"fr\" })\n * // => \"/fr/store\" (explicit locale)\n *\n * // Cross-origin links remain absolute\n * localizeHref(\"https://other-site.com/about\")\n * // => \"https://other-site.com/de/about\"\n *\n * // Protocol-relative links stay protocol-relative\n * localizeHref(\"//other-site.com/about\")\n * // => \"//other-site.com/de/about\"\n * ```\n *\n * For server-side URL localization (e.g., in middleware), use `localizeUrl()`\n * which provides more precise control over URL handling.\n *\n * @param {string} href - The href to localize (can be relative or absolute)\n * @param {object} [options] - Options for localization\n * @param {Locale} [options.locale] - Target locale. If not provided, uses `getLocale()`\n * @returns {string} The localized href, relative if input was relative\n */\nexport function localizeHref(href, options) {\n\tconst currentLocale = getLocale();\n\tconst locale = options?.locale ?? currentLocale;\n\tconst url = new URL(href, getUrlOrigin());\n\n\tconst localized = localizeUrl(url, { locale });\n\n\tconst protocolRelativeHref = serializeProtocolRelativeHref(\n\t\thref,\n\t\turl,\n\t\tlocalized\n\t);\n\tif (protocolRelativeHref !== undefined) {\n\t\treturn protocolRelativeHref;\n\t}\n\n\t// if the origin is identical and the href is relative,\n\t// return the relative path\n\tif (href.startsWith(\"/\") && url.origin === localized.origin) {\n\t\t// check for cross origin localization in which case an absolute URL must be returned.\n\t\tif (locale !== currentLocale) {\n\t\t\tconst localizedCurrentLocale = localizeUrl(url, {\n\t\t\t\tlocale: currentLocale,\n\t\t\t});\n\t\t\tif (localizedCurrentLocale.origin !== localized.origin) {\n\t\t\t\treturn localized.href;\n\t\t\t}\n\t\t}\n\t\treturn localized.pathname + localized.search + localized.hash;\n\t}\n\n\treturn localized.href;\n}\n\n/**\n * High-level URL de-localization function optimized for client-side UI usage.\n *\n * This is a convenience wrapper around `deLocalizeUrl()` that provides features\n * needed in the UI:\n *\n * - Accepts relative paths (e.g., \"/de/about\")\n * - Returns relative paths when possible\n * - Handles string input/output instead of URL objects\n *\n * @see https://paraglidejs.com/i18n-routing\n *\n * @example\n * ```typescript\n * // In a React/Vue/Svelte component\n * const LocaleSwitcher = ({ href }) => {\n * // Remove locale prefix before switching\n * const baseHref = deLocalizeHref(href);\n * return locales.map(locale =>\n * <a href={localizeHref(baseHref, { locale })}>\n * Switch to {locale}\n * </a>\n * );\n * };\n *\n * // Examples:\n * deLocalizeHref(\"/de/about\") // => \"/about\"\n * deLocalizeHref(\"/fr/store\") // => \"/store\"\n *\n * // Cross-origin links remain absolute\n * deLocalizeHref(\"https://example.com/de/about\")\n * // => \"https://example.com/about\"\n *\n * // Protocol-relative links stay protocol-relative\n * deLocalizeHref(\"//example.com/de/about\")\n * // => \"//example.com/about\"\n * ```\n *\n * For server-side URL de-localization (e.g., in middleware), use `deLocalizeUrl()`\n * which provides more precise control over URL handling.\n *\n * @param {string} href - The href to de-localize (can be relative or absolute)\n * @returns {string} The de-localized href, relative if input was relative\n */\nexport function deLocalizeHref(href) {\n\tconst url = new URL(href, getUrlOrigin());\n\n\tconst deLocalized = deLocalizeUrl(url);\n\n\tconst protocolRelativeHref = serializeProtocolRelativeHref(\n\t\thref,\n\t\turl,\n\t\tdeLocalized\n\t);\n\tif (protocolRelativeHref !== undefined) {\n\t\treturn protocolRelativeHref;\n\t}\n\n\t// If the origin is identical and the href is relative,\n\t// return the relative path instead of the full URL.\n\tif (href.startsWith(\"/\") && url.origin === deLocalized.origin) {\n\t\treturn deLocalized.pathname + deLocalized.search + deLocalized.hash;\n\t}\n\n\treturn deLocalized.href;\n}\n\n/**\n * @param {string} href\n * @param {URL} url\n * @param {URL} localized\n * @returns {string | undefined} The serialized href, or undefined if it is not protocol-relative.\n */\nfunction serializeProtocolRelativeHref(href, url, localized) {\n\tconst first = href.charCodeAt(0);\n\tconst second = href.charCodeAt(1);\n\tif (first > 0x20 && first !== 0x2f && first !== 0x5c) return undefined;\n\tif (first === 0x2f && second > 0x20 && second !== 0x2f && second !== 0x5c)\n\t\treturn undefined;\n\n\t// URL parsing removes tabs/newlines and trims leading C0 controls and spaces.\n\tconst normalizedHref = href.replace(/[\\t\\n\\r]/g, \"\");\n\tlet start = 0;\n\twhile (normalizedHref.charCodeAt(start) <= 0x20) start++;\n\tlet end = normalizedHref.length;\n\twhile (end > start && normalizedHref.charCodeAt(end - 1) <= 0x20) end--;\n\tconst relativeHref = normalizedHref.slice(start, end);\n\t// Special schemes also treat backslashes as authority separators.\n\tconst specialScheme = /^(https?|wss?|ftp|file):$/.test(url.protocol);\n\tconst separators = specialScheme ? /^[/\\\\]{2}/ : /^\\/\\//;\n\tif (!separators.test(relativeHref)) return undefined;\n\n\t// Keep configured protocol and port changes independent of the page's scheme.\n\tif (localized.protocol !== url.protocol || localized.port !== url.port) {\n\t\treturn localized.href;\n\t}\n\n\t// Otherwise leave the scheme to the page, including outside server middleware.\n\tconst result = localized.href.slice(localized.protocol.length);\n\tif (localized.port || url.protocol === \"file:\" || !url.hostname)\n\t\treturn result;\n\n\t// URL drops default ports. Recover an explicit port from the original authority.\n\tconst authority = (\n\t\tspecialScheme ? relativeHref.replace(/^[/\\\\]+/, \"\") : relativeHref.slice(2)\n\t).split(specialScheme ? /[/\\\\?#]/ : /[/?#]/, 1)[0];\n\tconst port = authority?.match(/:(\\d+)$/)?.[1];\n\tif (port === undefined) return result;\n\tconst pathStart = result.indexOf(\"/\", 2);\n\tconst authorityEnd = pathStart === -1 ? result.length : pathStart;\n\treturn `${result.slice(0, authorityEnd)}:${Number(port)}${result.slice(authorityEnd)}`;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { test, expect } from "vitest";
|
|
1
|
+
import { test, expect, vi } from "vitest";
|
|
2
2
|
import { createParaglide } from "../create-paraglide.js";
|
|
3
3
|
import { newProject } from "@inlang/sdk";
|
|
4
4
|
test("uses the locale from getLocale() if no locale is provided", async () => {
|
|
@@ -46,6 +46,232 @@ test("returns an absolute href if the provided href is absolute", async () => {
|
|
|
46
46
|
expect(runtime.localizeHref("http://example.com/hello", { locale: "de" })).toBe("http://example.com/de/hello");
|
|
47
47
|
expect(runtime.deLocalizeHref("http://example.com/de/hello")).toBe("http://example.com/hello");
|
|
48
48
|
});
|
|
49
|
+
test("localizeHref preserves protocol-relative authorities", async () => {
|
|
50
|
+
const runtime = await createParaglide({
|
|
51
|
+
blob: await newProject({
|
|
52
|
+
settings: { baseLocale: "en", locales: ["en", "de"] },
|
|
53
|
+
}),
|
|
54
|
+
strategy: ["url", "baseLocale"],
|
|
55
|
+
});
|
|
56
|
+
runtime.overwriteGetLocale(() => "en");
|
|
57
|
+
for (const protocol of ["http", "https"]) {
|
|
58
|
+
runtime.overwriteGetUrlOrigin(() => `${protocol}://app.example.com`);
|
|
59
|
+
for (const prefix of ["//", "/\\", "\\\\", "\\/", "///", "/\\/"]) {
|
|
60
|
+
for (const authority of [
|
|
61
|
+
"cdn.example.com:8443",
|
|
62
|
+
"cdn.example.com:80",
|
|
63
|
+
"cdn.example.com:443",
|
|
64
|
+
"app.example.com",
|
|
65
|
+
"user:p%40ss@app.example.com",
|
|
66
|
+
]) {
|
|
67
|
+
expect(runtime.localizeHref(`${prefix}${authority}/hello?q=1#section`, {
|
|
68
|
+
locale: "de",
|
|
69
|
+
})).toBe(`//${authority}/de/hello?q=1#section`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
expect(runtime.localizeHref("/hello?q=1#section", { locale: "de" })).toBe("/de/hello?q=1#section");
|
|
74
|
+
});
|
|
75
|
+
test("deLocalizeHref preserves protocol-relative authorities", async () => {
|
|
76
|
+
const runtime = await createParaglide({
|
|
77
|
+
blob: await newProject({
|
|
78
|
+
settings: { baseLocale: "en", locales: ["en", "de"] },
|
|
79
|
+
}),
|
|
80
|
+
strategy: ["url", "baseLocale"],
|
|
81
|
+
});
|
|
82
|
+
for (const protocol of ["http", "https"]) {
|
|
83
|
+
runtime.overwriteGetUrlOrigin(() => `${protocol}://app.example.com`);
|
|
84
|
+
for (const prefix of ["//", "/\\", "\\\\", "\\/", "///", "/\\/"]) {
|
|
85
|
+
for (const authority of [
|
|
86
|
+
"cdn.example.com:8443",
|
|
87
|
+
"cdn.example.com:80",
|
|
88
|
+
"cdn.example.com:443",
|
|
89
|
+
"app.example.com",
|
|
90
|
+
"user:p%40ss@app.example.com",
|
|
91
|
+
]) {
|
|
92
|
+
expect(runtime.deLocalizeHref(`${prefix}${authority}/de/hello?q=1#section`)).toBe(`//${authority}/hello?q=1#section`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
expect(runtime.deLocalizeHref("/de/hello?q=1#section")).toBe("/hello?q=1#section");
|
|
97
|
+
});
|
|
98
|
+
test("classifies protocol-relative hrefs after URL whitespace normalization", async () => {
|
|
99
|
+
const runtime = await createParaglide({
|
|
100
|
+
blob: await newProject({
|
|
101
|
+
settings: { baseLocale: "en", locales: ["en", "de"] },
|
|
102
|
+
}),
|
|
103
|
+
strategy: ["url", "baseLocale"],
|
|
104
|
+
});
|
|
105
|
+
runtime.overwriteGetLocale(() => "en");
|
|
106
|
+
runtime.overwriteGetUrlOrigin(() => "https://app.example.com");
|
|
107
|
+
for (const prefix of [
|
|
108
|
+
"/\t/",
|
|
109
|
+
"/\n/",
|
|
110
|
+
"/\r\\",
|
|
111
|
+
"/\t\\",
|
|
112
|
+
" \t//",
|
|
113
|
+
"\u0000//",
|
|
114
|
+
]) {
|
|
115
|
+
expect({
|
|
116
|
+
localized: runtime.localizeHref(`${prefix}cdn.example.com/hello`, {
|
|
117
|
+
locale: "de",
|
|
118
|
+
}),
|
|
119
|
+
deLocalized: runtime.deLocalizeHref(`${prefix}cdn.example.com/de/hello`),
|
|
120
|
+
}).toEqual({
|
|
121
|
+
localized: "//cdn.example.com/de/hello",
|
|
122
|
+
deLocalized: "//cdn.example.com/hello",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
expect(runtime.localizeHref("/%2Fcdn.example.com/hello", { locale: "de" })).toBe("/de/%2Fcdn.example.com/hello");
|
|
126
|
+
expect(runtime.deLocalizeHref("/de/%2Fcdn.example.com/hello")).toBe("/%2Fcdn.example.com/hello");
|
|
127
|
+
runtime.overwriteGetUrlOrigin(() => "custom://app.example.com");
|
|
128
|
+
expect(runtime.localizeHref("/\\hello", { locale: "de" })).toBe("/de/\\hello");
|
|
129
|
+
expect(runtime.deLocalizeHref("/de/\\hello")).toBe("/\\hello");
|
|
130
|
+
});
|
|
131
|
+
test.each(["localizeHref", "deLocalizeHref"])("%s does not interpret file path segments as ports", async (helper) => {
|
|
132
|
+
const runtime = await createParaglide({
|
|
133
|
+
blob: await newProject({
|
|
134
|
+
settings: { baseLocale: "en", locales: ["en", "de"] },
|
|
135
|
+
}),
|
|
136
|
+
strategy: ["url", "baseLocale"],
|
|
137
|
+
});
|
|
138
|
+
runtime.overwriteGetLocale(() => "en");
|
|
139
|
+
runtime.overwriteGetUrlOrigin(() => "file:///app/");
|
|
140
|
+
for (const [prefix, expectedPrefix] of [
|
|
141
|
+
["///", "///"],
|
|
142
|
+
["/\\/", "///"],
|
|
143
|
+
["//localhost/", "///"],
|
|
144
|
+
["//server/", "//server/"],
|
|
145
|
+
]) {
|
|
146
|
+
const href = `${prefix}docs:80/page?q=1#part`;
|
|
147
|
+
const result = helper === "localizeHref"
|
|
148
|
+
? runtime.localizeHref(href, { locale: "de" })
|
|
149
|
+
: runtime.deLocalizeHref(href);
|
|
150
|
+
const expected = `${expectedPrefix}${helper === "localizeHref" ? "de/" : ""}docs:80/page?q=1#part`;
|
|
151
|
+
expect(result).toBe(expected);
|
|
152
|
+
expect(new URL(result, "file:///app/").href).toBe(`file:${expected}`);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
test("protocol-relative hrefs do not inherit the server fallback protocol", async () => {
|
|
156
|
+
vi.stubGlobal("window", undefined);
|
|
157
|
+
try {
|
|
158
|
+
const runtime = await createParaglide({
|
|
159
|
+
blob: await newProject({
|
|
160
|
+
settings: { baseLocale: "en", locales: ["en", "de"] },
|
|
161
|
+
}),
|
|
162
|
+
strategy: ["url", "baseLocale"],
|
|
163
|
+
// A distinct option gives these fallback tests a runtime whose origin
|
|
164
|
+
// was not overwritten.
|
|
165
|
+
isServer: "true",
|
|
166
|
+
});
|
|
167
|
+
runtime.overwriteGetLocale(() => "en");
|
|
168
|
+
expect(runtime.getUrlOrigin()).toBe("http://fallback.com");
|
|
169
|
+
for (const prefix of ["//", "/\\"]) {
|
|
170
|
+
const localized = runtime.localizeHref(`${prefix}cdn.example.com/hello`, {
|
|
171
|
+
locale: "de",
|
|
172
|
+
});
|
|
173
|
+
const deLocalized = runtime.deLocalizeHref(`${prefix}cdn.example.com/de/hello`);
|
|
174
|
+
expect({ localized, deLocalized }).toEqual({
|
|
175
|
+
localized: "//cdn.example.com/de/hello",
|
|
176
|
+
deLocalized: "//cdn.example.com/hello",
|
|
177
|
+
});
|
|
178
|
+
expect(new URL(localized, "https://app.example.com").href).toBe("https://cdn.example.com/de/hello");
|
|
179
|
+
expect(new URL(deLocalized, "https://app.example.com").href).toBe("https://cdn.example.com/hello");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
vi.unstubAllGlobals();
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
test.each(["localizeHref", "deLocalizeHref"])("%s preserves explicit default ports on the server", async (helper) => {
|
|
187
|
+
const runtime = await createParaglide({
|
|
188
|
+
blob: await newProject({
|
|
189
|
+
settings: { baseLocale: "en", locales: ["en", "de"] },
|
|
190
|
+
}),
|
|
191
|
+
strategy: ["url", "baseLocale"],
|
|
192
|
+
isServer: "true",
|
|
193
|
+
});
|
|
194
|
+
runtime.overwriteGetLocale(() => "en");
|
|
195
|
+
expect(runtime.getUrlOrigin()).toBe("http://fallback.com");
|
|
196
|
+
for (const prefix of ["//", "/\\", "/\t/"]) {
|
|
197
|
+
for (const authority of [
|
|
198
|
+
"cdn.example.com:80",
|
|
199
|
+
"cdn.example.com:00080",
|
|
200
|
+
"user:p%40ss@cdn.example.com:80",
|
|
201
|
+
"[::1]:80",
|
|
202
|
+
"user:p%40ss@[::1]:80",
|
|
203
|
+
]) {
|
|
204
|
+
const input = `${prefix}${authority}${helper === "localizeHref" ? "/docs" : "/de/docs"}?q=1#part`;
|
|
205
|
+
const output = helper === "localizeHref"
|
|
206
|
+
? runtime.localizeHref(input, { locale: "de" })
|
|
207
|
+
: runtime.deLocalizeHref(input);
|
|
208
|
+
const resolved = new URL(output, "https://app.example.com");
|
|
209
|
+
expect(resolved.port).toBe("80");
|
|
210
|
+
expect(resolved.href).toBe(new URL(`https://${authority}${helper === "localizeHref" ? "/de/docs" : "/docs"}?q=1#part`).href);
|
|
211
|
+
expect(output.startsWith("//")).toBe(true);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
test.each([
|
|
216
|
+
["80", "8080"],
|
|
217
|
+
["8080", "80"],
|
|
218
|
+
])("configured port changes from %s to %s take precedence", async (basePort, localizedPort) => {
|
|
219
|
+
const runtime = await createParaglide({
|
|
220
|
+
blob: await newProject({
|
|
221
|
+
settings: { baseLocale: "en", locales: ["en", "de"] },
|
|
222
|
+
}),
|
|
223
|
+
strategy: ["url", "baseLocale"],
|
|
224
|
+
urlPatterns: [
|
|
225
|
+
{
|
|
226
|
+
pattern: `http://example.com:${basePort}/:path(.*)?`,
|
|
227
|
+
localized: [
|
|
228
|
+
["en", `http://example.com:${basePort}/:path(.*)?`],
|
|
229
|
+
["de", `http://de.example.com:${localizedPort}/:path(.*)?`],
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
});
|
|
234
|
+
runtime.overwriteGetLocale(() => "en");
|
|
235
|
+
runtime.overwriteGetUrlOrigin(() => "http://app.example.com");
|
|
236
|
+
for (const prefix of ["//", "/\\", "/\n/"]) {
|
|
237
|
+
expect(runtime.localizeHref(`${prefix}example.com:${basePort}/docs?q=1#part`, {
|
|
238
|
+
locale: "de",
|
|
239
|
+
})).toBe(new URL(`http://de.example.com:${localizedPort}/docs?q=1#part`).href);
|
|
240
|
+
expect(runtime.deLocalizeHref(`${prefix}de.example.com:${localizedPort}/docs?q=1#part`)).toBe(new URL(`http://example.com:${basePort}/docs?q=1#part`).href);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
test.each(["http", "https"])("protocol-relative hrefs respect domain localization between http and %s", async (protocol) => {
|
|
244
|
+
const runtime = await createParaglide({
|
|
245
|
+
blob: await newProject({
|
|
246
|
+
settings: { baseLocale: "en", locales: ["en", "de"] },
|
|
247
|
+
}),
|
|
248
|
+
strategy: ["url", "baseLocale"],
|
|
249
|
+
urlPatterns: [
|
|
250
|
+
{
|
|
251
|
+
pattern: "http://example.com/:path(.*)?",
|
|
252
|
+
localized: [
|
|
253
|
+
["en", "http://example.com/:path(.*)?"],
|
|
254
|
+
["de", `${protocol}://de.example.com/:path(.*)?`],
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
});
|
|
259
|
+
runtime.overwriteGetLocale(() => "en");
|
|
260
|
+
for (const prefix of ["//", "/\\", "/\t/"]) {
|
|
261
|
+
runtime.overwriteGetUrlOrigin(() => "http://app.example.com");
|
|
262
|
+
expect(runtime.localizeHref(`${prefix}example.com/hello?q=1#section`, {
|
|
263
|
+
locale: "de",
|
|
264
|
+
})).toBe(`${protocol === "http" ? "" : "https:"}//de.example.com/hello?q=1#section`);
|
|
265
|
+
expect(runtime.localizeHref(`${prefix}example.com:80/hello`, { locale: "de" })).toBe(protocol === "http"
|
|
266
|
+
? "//de.example.com:80/hello"
|
|
267
|
+
: "https://de.example.com/hello");
|
|
268
|
+
runtime.overwriteGetUrlOrigin(() => `${protocol}://app.example.com`);
|
|
269
|
+
expect(runtime.deLocalizeHref(`${prefix}de.example.com/hello?q=1#section`)).toBe(`${protocol === "http" ? "" : "http:"}//example.com/hello?q=1#section`);
|
|
270
|
+
expect(runtime.deLocalizeHref(`${prefix}de.example.com:${protocol === "http" ? "80" : "443"}/hello`)).toBe(protocol === "http"
|
|
271
|
+
? "//example.com:80/hello"
|
|
272
|
+
: "http://example.com/hello");
|
|
273
|
+
}
|
|
274
|
+
});
|
|
49
275
|
// useful if domain based localization is used for example
|
|
50
276
|
test("returns an absolute href if the provided href is relative but the origin of the localized href differs", async () => {
|
|
51
277
|
const runtime = await createParaglide({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localize-href.test.js","sourceRoot":"","sources":["../../../src/compiler/runtime/localize-href.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { test, expect } from \"vitest\";\nimport { createParaglide } from \"../create-paraglide.js\";\nimport { newProject } from \"@inlang/sdk\";\n\ntest(\"uses the locale from getLocale() if no locale is provided\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tstrategy: [\"url\", \"globalVariable\", \"baseLocale\"],\n\t\turlPatterns: [\n\t\t\t{\n\t\t\t\tpattern: \"http://:domain(.*)/:locale(de|en)?/:path(.*)?\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"de\", \"http://:domain(.*)/de/:path(.*)?\"],\n\t\t\t\t\t[\"en\", \"http://:domain(.*)/:path(.*)?\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\truntime.overwriteGetLocale(() => \"de\");\n\n\texpect(runtime.localizeHref(\"/hello\")).toBe(\"/de/hello\");\n\texpect(runtime.deLocalizeHref(\"/de/hello\")).toBe(\"/hello\");\n});\n\ntest(\"returns an absolute href if the provided href is absolute\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tstrategy: [\"url\", \"globalVariable\", \"baseLocale\"],\n\t\turlPatterns: [\n\t\t\t{\n\t\t\t\tpattern: \"http://:domain(.*)/:locale(de|en)?/:path(.*)?\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"de\", \"http://:domain(.*)/de/:path(.*)?\"],\n\t\t\t\t\t[\"en\", \"http://:domain(.*)/:path(.*)?\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\texpect(\n\t\truntime.localizeHref(\"http://example.com/hello\", { locale: \"de\" })\n\t).toBe(\"http://example.com/de/hello\");\n\texpect(runtime.deLocalizeHref(\"http://example.com/de/hello\")).toBe(\n\t\t\"http://example.com/hello\"\n\t);\n});\n\n// useful if domain based localization is used for example\ntest(\"returns an absolute href if the provided href is relative but the origin of the localized href differs\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tstrategy: [\"url\", \"globalVariable\", \"baseLocale\"],\n\t\turlPatterns: [\n\t\t\t{\n\t\t\t\tpattern: \"http://example.com/:path(.*)?\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"de\", \"http://de.example.com/:path(.*)?\"],\n\t\t\t\t\t[\"en\", \"http://example.com/:path(.*)?\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\t// simulating routing from current en page to de page\n\truntime.overwriteGetLocale(() => \"en\");\n\truntime.overwriteGetUrlOrigin(() => \"http://example.com\");\n\n\texpect(runtime.localizeHref(\"/hello\", { locale: \"de\" })).toBe(\n\t\t\"http://de.example.com/hello\"\n\t);\n\texpect(runtime.deLocalizeHref(\"http://de.example.com/hello\")).toBe(\n\t\t\"http://example.com/hello\"\n\t);\n\n\t// simulating routing from current de page to en page\n\truntime.overwriteGetLocale(() => \"de\");\n\n\texpect(runtime.localizeHref(\"/hello\", { locale: \"en\" })).toBe(\n\t\t\"http://example.com/hello\"\n\t);\n\texpect(runtime.deLocalizeHref(\"http://example.com/hello\")).toBe(\n\t\t\"http://example.com/hello\"\n\t);\n});\n\n// https://github.com/opral/inlang-paraglide-js/issues/362\ntest(\"adding a base path\", async () => {\n\tconst base = \"shop\";\n\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tstrategy: [\"url\"],\n\t\turlPatterns: [\n\t\t\t{\n\t\t\t\tpattern: `/{${base}/}?:path(.*)?`,\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"de\", `/{${base}/}?de/:path(.*)?`],\n\t\t\t\t\t[\"en\", `/{${base}/}?:path(.*)?`],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\t// simulating the current locale to be en\n\truntime.overwriteGetLocale(() => \"en\");\n\n\texpect(runtime.localizeHref(\"/about\")).toBe(\"/shop/about\");\n\texpect(runtime.deLocalizeHref(\"/about\")).toBe(\"/shop/about\");\n\n\t// simulating current locale to be de\n\truntime.overwriteGetLocale(() => \"de\");\n\texpect(runtime.localizeHref(\"/about\")).toBe(\"/shop/de/about\");\n\texpect(runtime.deLocalizeHref(\"/de/about\")).toBe(\"/shop/about\");\n});\n\ntest(\"default url patterns to improve out of the box experience\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\", \"fr\"],\n\t\t\t},\n\t\t}),\n\t\tisServer: \"false\",\n\t\tstrategy: [\"url\"],\n\t\turlPatterns: undefined,\n\t});\n\n\t// polyfilling window\n\tglobalThis.window = { location: new URL(\"http://example.com\") } as any;\n\n\texpect(runtime.urlPatterns).toStrictEqual([\n\t\t{\n\t\t\tpattern: \":protocol://:domain(.*)::port?/:path(.*)?\",\n\t\t\tlocalized: [\n\t\t\t\t[\"de\", \":protocol://:domain(.*)::port?/de/:path(.*)?\"],\n\t\t\t\t[\"fr\", \":protocol://:domain(.*)::port?/fr/:path(.*)?\"],\n\t\t\t\t[\"en\", \":protocol://:domain(.*)::port?/:path(.*)?\"],\n\t\t\t],\n\t\t},\n\t]);\n\n\t// expecting the baseLocale to not be included in the url\n\texpect(runtime.localizeHref(\"/about\", { locale: \"en\" })).toBe(\"/about\");\n\texpect(runtime.localizeHref(\"/about\", { locale: \"de\" })).toBe(\"/de/about\");\n\n\t// expecting root path to work\n\texpect(runtime.localizeHref(\"/\", { locale: \"en\" })).toBe(\"/\");\n\texpect(runtime.localizeHref(\"/\", { locale: \"de\" })).toBe(\"/de/\");\n\n\t// expecting de-localization to work\n\texpect(runtime.deLocalizeHref(\"/de/\")).toBe(\"/\");\n\texpect(runtime.deLocalizeHref(\"/de/about\")).toBe(\"/about\");\n\texpect(runtime.deLocalizeHref(\"/about\")).toBe(\"/about\");\n\n\t// expecting localhost to work (for development)\n\texpect(runtime.localizeHref(\"http://localhost:5173/\", { locale: \"de\" })).toBe(\n\t\t\"http://localhost:5173/de/\"\n\t);\n\n\t// de-localizing localhost\n\texpect(runtime.deLocalizeHref(\"http://localhost:5173/de\")).toBe(\n\t\t\"http://localhost:5173/\"\n\t);\n\n\t// pathnames\n\texpect(\n\t\truntime.localizeHref(\"http://localhost:5173/about\", { locale: \"de\" })\n\t).toBe(\"http://localhost:5173/de/about\");\n\n\texpect(runtime.deLocalizeHref(\"http://localhost:5173/de/about\")).toBe(\n\t\t\"http://localhost:5173/about\"\n\t);\n});\n\ntest(\"normalizes localized root hrefs and anchors\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tisServer: \"false\",\n\t\tstrategy: [\"url\"],\n\t\ttrailingSlash: \"never\",\n\t});\n\n\tglobalThis.window = { location: new URL(\"http://example.com\") } as any;\n\n\texpect(runtime.localizeHref(\"/\", { locale: \"de\" })).toBe(\"/de\");\n\texpect(runtime.localizeHref(\"/#section\", { locale: \"de\" })).toBe(\n\t\t\"/de#section\"\n\t);\n\texpect(runtime.deLocalizeHref(\"/de/\")).toBe(\"/\");\n});\n\ntest(\"normalizes mixed-case explicit locales in localizeHref\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tstrategy: [\"url\"],\n\t});\n\n\texpect(runtime.localizeHref(\"/about\", { locale: \"DE\" })).toBe(\"/de/about\");\n\n\texpect(runtime.localizeHref(\"/de/about\", { locale: \"EN\" })).toBe(\"/about\");\n});\n\ntest(\"keeps custom URLPattern path expressions on the generic fallback\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tstrategy: [\"url\"],\n\t\turlPatterns: [\n\t\t\t{\n\t\t\t\tpattern: \"/:slug(.)?\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"en\", \"/:slug(.)?\"],\n\t\t\t\t\t[\"de\", \"/de/:slug(.)?\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\truntime.overwriteGetLocale(() => \"en\");\n\truntime.overwriteGetUrlOrigin(() => \"https://example.com\");\n\n\t// `(.)` matches exactly one character in URLPattern. A prefix-only\n\t// implementation must not broaden this route into a catch-all.\n\texpect(runtime.localizeHref(\"/a\", { locale: \"de\" })).toBe(\"/de/a\");\n\texpect(runtime.localizeHref(\"/about\", { locale: \"de\" })).toBe(\"/about\");\n});\n\ntest.each([undefined, \"always\", \"never\"] as const)(\n\t\"default hrefs preserve slashes unless trailingSlash is %s\",\n\tasync (trailingSlash) => {\n\t\tconst runtime = await createParaglide({\n\t\t\tblob: await newProject({\n\t\t\t\tsettings: { baseLocale: \"de\", locales: [\"de\", \"en\"] },\n\t\t\t}),\n\t\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\t\ttrailingSlash,\n\t\t});\n\t\tconst paths = [\"/\", \"/about/\", \"/contact/\"];\n\t\tconst suffix = trailingSlash === \"never\" ? \"\" : \"/\";\n\t\texpect(\n\t\t\tpaths.flatMap((path) =>\n\t\t\t\truntime.locales.map((locale) => runtime.localizeHref(path, { locale }))\n\t\t\t)\n\t\t).toEqual([\n\t\t\t\"/\",\n\t\t\t\"/en\" + suffix,\n\t\t\t\"/about\" + suffix,\n\t\t\t\"/en/about\" + suffix,\n\t\t\t\"/contact\" + suffix,\n\t\t\t\"/en/contact\" + suffix,\n\t\t]);\n\t\texpect(runtime.localizeHref(\"/about\", { locale: \"en\" })).toBe(\n\t\t\t\"/en/about\" + (trailingSlash === \"always\" ? \"/\" : \"\")\n\t\t);\n\t\texpect(runtime.deLocalizeHref(\"/en/about/?q=test#section\")).toBe(\n\t\t\t\"/about\" + suffix + \"?q=test#section\"\n\t\t);\n\t\texpect(\n\t\t\truntime.localizeHref(\"/en/about/?q=test#section\", { locale: \"de\" })\n\t\t).toBe(\"/about\" + suffix + \"?q=test#section\");\n\t}\n);\n\ntest(\"default routes remove detected locales after repeated leading separators\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: { baseLocale: \"en\", locales: [\"en\", \"de\", \"fr\"] },\n\t\t}),\n\t\tstrategy: [\"url\", \"baseLocale\"],\n\t});\n\tfor (const suffix of [\"\", \"/\"]) {\n\t\tconst input = `https://example.com//de/about${suffix}?q=test#section`;\n\t\texpect(runtime.extractLocaleFromUrl(input)).toBe(\"de\");\n\t\texpect(runtime.localizeUrl(input, { locale: \"en\" }).href).toBe(\n\t\t\t`https://example.com/about${suffix}?q=test#section`\n\t\t);\n\t\texpect(runtime.localizeUrl(input, { locale: \"fr\" }).href).toBe(\n\t\t\t`https://example.com/fr/about${suffix}?q=test#section`\n\t\t);\n\t\texpect(runtime.deLocalizeUrl(input).href).toBe(\n\t\t\t`https://example.com/about${suffix}?q=test#section`\n\t\t);\n\t}\n});\n"]}
|
|
1
|
+
{"version":3,"file":"localize-href.test.js","sourceRoot":"","sources":["../../../src/compiler/runtime/localize-href.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { test, expect, vi } from \"vitest\";\nimport { createParaglide } from \"../create-paraglide.js\";\nimport { newProject } from \"@inlang/sdk\";\n\ntest(\"uses the locale from getLocale() if no locale is provided\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tstrategy: [\"url\", \"globalVariable\", \"baseLocale\"],\n\t\turlPatterns: [\n\t\t\t{\n\t\t\t\tpattern: \"http://:domain(.*)/:locale(de|en)?/:path(.*)?\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"de\", \"http://:domain(.*)/de/:path(.*)?\"],\n\t\t\t\t\t[\"en\", \"http://:domain(.*)/:path(.*)?\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\truntime.overwriteGetLocale(() => \"de\");\n\n\texpect(runtime.localizeHref(\"/hello\")).toBe(\"/de/hello\");\n\texpect(runtime.deLocalizeHref(\"/de/hello\")).toBe(\"/hello\");\n});\n\ntest(\"returns an absolute href if the provided href is absolute\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tstrategy: [\"url\", \"globalVariable\", \"baseLocale\"],\n\t\turlPatterns: [\n\t\t\t{\n\t\t\t\tpattern: \"http://:domain(.*)/:locale(de|en)?/:path(.*)?\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"de\", \"http://:domain(.*)/de/:path(.*)?\"],\n\t\t\t\t\t[\"en\", \"http://:domain(.*)/:path(.*)?\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\texpect(\n\t\truntime.localizeHref(\"http://example.com/hello\", { locale: \"de\" })\n\t).toBe(\"http://example.com/de/hello\");\n\texpect(runtime.deLocalizeHref(\"http://example.com/de/hello\")).toBe(\n\t\t\"http://example.com/hello\"\n\t);\n});\n\ntest(\"localizeHref preserves protocol-relative authorities\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: { baseLocale: \"en\", locales: [\"en\", \"de\"] },\n\t\t}),\n\t\tstrategy: [\"url\", \"baseLocale\"],\n\t});\n\truntime.overwriteGetLocale(() => \"en\");\n\n\tfor (const protocol of [\"http\", \"https\"]) {\n\t\truntime.overwriteGetUrlOrigin(() => `${protocol}://app.example.com`);\n\t\tfor (const prefix of [\"//\", \"/\\\\\", \"\\\\\\\\\", \"\\\\/\", \"///\", \"/\\\\/\"]) {\n\t\t\tfor (const authority of [\n\t\t\t\t\"cdn.example.com:8443\",\n\t\t\t\t\"cdn.example.com:80\",\n\t\t\t\t\"cdn.example.com:443\",\n\t\t\t\t\"app.example.com\",\n\t\t\t\t\"user:p%40ss@app.example.com\",\n\t\t\t]) {\n\t\t\t\texpect(\n\t\t\t\t\truntime.localizeHref(`${prefix}${authority}/hello?q=1#section`, {\n\t\t\t\t\t\tlocale: \"de\",\n\t\t\t\t\t})\n\t\t\t\t).toBe(`//${authority}/de/hello?q=1#section`);\n\t\t\t}\n\t\t}\n\t}\n\texpect(runtime.localizeHref(\"/hello?q=1#section\", { locale: \"de\" })).toBe(\n\t\t\"/de/hello?q=1#section\"\n\t);\n});\n\ntest(\"deLocalizeHref preserves protocol-relative authorities\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: { baseLocale: \"en\", locales: [\"en\", \"de\"] },\n\t\t}),\n\t\tstrategy: [\"url\", \"baseLocale\"],\n\t});\n\n\tfor (const protocol of [\"http\", \"https\"]) {\n\t\truntime.overwriteGetUrlOrigin(() => `${protocol}://app.example.com`);\n\t\tfor (const prefix of [\"//\", \"/\\\\\", \"\\\\\\\\\", \"\\\\/\", \"///\", \"/\\\\/\"]) {\n\t\t\tfor (const authority of [\n\t\t\t\t\"cdn.example.com:8443\",\n\t\t\t\t\"cdn.example.com:80\",\n\t\t\t\t\"cdn.example.com:443\",\n\t\t\t\t\"app.example.com\",\n\t\t\t\t\"user:p%40ss@app.example.com\",\n\t\t\t]) {\n\t\t\t\texpect(\n\t\t\t\t\truntime.deLocalizeHref(`${prefix}${authority}/de/hello?q=1#section`)\n\t\t\t\t).toBe(`//${authority}/hello?q=1#section`);\n\t\t\t}\n\t\t}\n\t}\n\texpect(runtime.deLocalizeHref(\"/de/hello?q=1#section\")).toBe(\n\t\t\"/hello?q=1#section\"\n\t);\n});\n\ntest(\"classifies protocol-relative hrefs after URL whitespace normalization\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: { baseLocale: \"en\", locales: [\"en\", \"de\"] },\n\t\t}),\n\t\tstrategy: [\"url\", \"baseLocale\"],\n\t});\n\truntime.overwriteGetLocale(() => \"en\");\n\truntime.overwriteGetUrlOrigin(() => \"https://app.example.com\");\n\n\tfor (const prefix of [\n\t\t\"/\\t/\",\n\t\t\"/\\n/\",\n\t\t\"/\\r\\\\\",\n\t\t\"/\\t\\\\\",\n\t\t\" \\t//\",\n\t\t\"\\u0000//\",\n\t]) {\n\t\texpect({\n\t\t\tlocalized: runtime.localizeHref(`${prefix}cdn.example.com/hello`, {\n\t\t\t\tlocale: \"de\",\n\t\t\t}),\n\t\t\tdeLocalized: runtime.deLocalizeHref(`${prefix}cdn.example.com/de/hello`),\n\t\t}).toEqual({\n\t\t\tlocalized: \"//cdn.example.com/de/hello\",\n\t\t\tdeLocalized: \"//cdn.example.com/hello\",\n\t\t});\n\t}\n\texpect(\n\t\truntime.localizeHref(\"/%2Fcdn.example.com/hello\", { locale: \"de\" })\n\t).toBe(\"/de/%2Fcdn.example.com/hello\");\n\texpect(runtime.deLocalizeHref(\"/de/%2Fcdn.example.com/hello\")).toBe(\n\t\t\"/%2Fcdn.example.com/hello\"\n\t);\n\truntime.overwriteGetUrlOrigin(() => \"custom://app.example.com\");\n\texpect(runtime.localizeHref(\"/\\\\hello\", { locale: \"de\" })).toBe(\n\t\t\"/de/\\\\hello\"\n\t);\n\texpect(runtime.deLocalizeHref(\"/de/\\\\hello\")).toBe(\"/\\\\hello\");\n});\n\ntest.each([\"localizeHref\", \"deLocalizeHref\"] as const)(\n\t\"%s does not interpret file path segments as ports\",\n\tasync (helper) => {\n\t\tconst runtime = await createParaglide({\n\t\t\tblob: await newProject({\n\t\t\t\tsettings: { baseLocale: \"en\", locales: [\"en\", \"de\"] },\n\t\t\t}),\n\t\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\t});\n\t\truntime.overwriteGetLocale(() => \"en\");\n\t\truntime.overwriteGetUrlOrigin(() => \"file:///app/\");\n\n\t\tfor (const [prefix, expectedPrefix] of [\n\t\t\t[\"///\", \"///\"],\n\t\t\t[\"/\\\\/\", \"///\"],\n\t\t\t[\"//localhost/\", \"///\"],\n\t\t\t[\"//server/\", \"//server/\"],\n\t\t]) {\n\t\t\tconst href = `${prefix}docs:80/page?q=1#part`;\n\t\t\tconst result =\n\t\t\t\thelper === \"localizeHref\"\n\t\t\t\t\t? runtime.localizeHref(href, { locale: \"de\" })\n\t\t\t\t\t: runtime.deLocalizeHref(href);\n\t\t\tconst expected = `${expectedPrefix}${helper === \"localizeHref\" ? \"de/\" : \"\"}docs:80/page?q=1#part`;\n\t\t\texpect(result).toBe(expected);\n\t\t\texpect(new URL(result, \"file:///app/\").href).toBe(`file:${expected}`);\n\t\t}\n\t}\n);\n\ntest(\"protocol-relative hrefs do not inherit the server fallback protocol\", async () => {\n\tvi.stubGlobal(\"window\", undefined);\n\ttry {\n\t\tconst runtime = await createParaglide({\n\t\t\tblob: await newProject({\n\t\t\t\tsettings: { baseLocale: \"en\", locales: [\"en\", \"de\"] },\n\t\t\t}),\n\t\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\t\t// A distinct option gives these fallback tests a runtime whose origin\n\t\t\t// was not overwritten.\n\t\t\tisServer: \"true\",\n\t\t});\n\t\truntime.overwriteGetLocale(() => \"en\");\n\t\texpect(runtime.getUrlOrigin()).toBe(\"http://fallback.com\");\n\n\t\tfor (const prefix of [\"//\", \"/\\\\\"]) {\n\t\t\tconst localized = runtime.localizeHref(`${prefix}cdn.example.com/hello`, {\n\t\t\t\tlocale: \"de\",\n\t\t\t});\n\t\t\tconst deLocalized = runtime.deLocalizeHref(\n\t\t\t\t`${prefix}cdn.example.com/de/hello`\n\t\t\t);\n\t\t\texpect({ localized, deLocalized }).toEqual({\n\t\t\t\tlocalized: \"//cdn.example.com/de/hello\",\n\t\t\t\tdeLocalized: \"//cdn.example.com/hello\",\n\t\t\t});\n\t\t\texpect(new URL(localized, \"https://app.example.com\").href).toBe(\n\t\t\t\t\"https://cdn.example.com/de/hello\"\n\t\t\t);\n\t\t\texpect(new URL(deLocalized, \"https://app.example.com\").href).toBe(\n\t\t\t\t\"https://cdn.example.com/hello\"\n\t\t\t);\n\t\t}\n\t} finally {\n\t\tvi.unstubAllGlobals();\n\t}\n});\n\ntest.each([\"localizeHref\", \"deLocalizeHref\"] as const)(\n\t\"%s preserves explicit default ports on the server\",\n\tasync (helper) => {\n\t\tconst runtime = await createParaglide({\n\t\t\tblob: await newProject({\n\t\t\t\tsettings: { baseLocale: \"en\", locales: [\"en\", \"de\"] },\n\t\t\t}),\n\t\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\t\tisServer: \"true\",\n\t\t});\n\t\truntime.overwriteGetLocale(() => \"en\");\n\t\texpect(runtime.getUrlOrigin()).toBe(\"http://fallback.com\");\n\n\t\tfor (const prefix of [\"//\", \"/\\\\\", \"/\\t/\"]) {\n\t\t\tfor (const authority of [\n\t\t\t\t\"cdn.example.com:80\",\n\t\t\t\t\"cdn.example.com:00080\",\n\t\t\t\t\"user:p%40ss@cdn.example.com:80\",\n\t\t\t\t\"[::1]:80\",\n\t\t\t\t\"user:p%40ss@[::1]:80\",\n\t\t\t]) {\n\t\t\t\tconst input = `${prefix}${authority}${helper === \"localizeHref\" ? \"/docs\" : \"/de/docs\"}?q=1#part`;\n\t\t\t\tconst output =\n\t\t\t\t\thelper === \"localizeHref\"\n\t\t\t\t\t\t? runtime.localizeHref(input, { locale: \"de\" })\n\t\t\t\t\t\t: runtime.deLocalizeHref(input);\n\t\t\t\tconst resolved = new URL(output, \"https://app.example.com\");\n\t\t\t\texpect(resolved.port).toBe(\"80\");\n\t\t\t\texpect(resolved.href).toBe(\n\t\t\t\t\tnew URL(\n\t\t\t\t\t\t`https://${authority}${helper === \"localizeHref\" ? \"/de/docs\" : \"/docs\"}?q=1#part`\n\t\t\t\t\t).href\n\t\t\t\t);\n\t\t\t\texpect(output.startsWith(\"//\")).toBe(true);\n\t\t\t}\n\t\t}\n\t}\n);\n\ntest.each([\n\t[\"80\", \"8080\"],\n\t[\"8080\", \"80\"],\n])(\n\t\"configured port changes from %s to %s take precedence\",\n\tasync (basePort, localizedPort) => {\n\t\tconst runtime = await createParaglide({\n\t\t\tblob: await newProject({\n\t\t\t\tsettings: { baseLocale: \"en\", locales: [\"en\", \"de\"] },\n\t\t\t}),\n\t\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\t\turlPatterns: [\n\t\t\t\t{\n\t\t\t\t\tpattern: `http://example.com:${basePort}/:path(.*)?`,\n\t\t\t\t\tlocalized: [\n\t\t\t\t\t\t[\"en\", `http://example.com:${basePort}/:path(.*)?`],\n\t\t\t\t\t\t[\"de\", `http://de.example.com:${localizedPort}/:path(.*)?`],\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t\truntime.overwriteGetLocale(() => \"en\");\n\t\truntime.overwriteGetUrlOrigin(() => \"http://app.example.com\");\n\n\t\tfor (const prefix of [\"//\", \"/\\\\\", \"/\\n/\"]) {\n\t\t\texpect(\n\t\t\t\truntime.localizeHref(`${prefix}example.com:${basePort}/docs?q=1#part`, {\n\t\t\t\t\tlocale: \"de\",\n\t\t\t\t})\n\t\t\t).toBe(\n\t\t\t\tnew URL(`http://de.example.com:${localizedPort}/docs?q=1#part`).href\n\t\t\t);\n\t\t\texpect(\n\t\t\t\truntime.deLocalizeHref(\n\t\t\t\t\t`${prefix}de.example.com:${localizedPort}/docs?q=1#part`\n\t\t\t\t)\n\t\t\t).toBe(new URL(`http://example.com:${basePort}/docs?q=1#part`).href);\n\t\t}\n\t}\n);\n\ntest.each([\"http\", \"https\"])(\n\t\"protocol-relative hrefs respect domain localization between http and %s\",\n\tasync (protocol) => {\n\t\tconst runtime = await createParaglide({\n\t\t\tblob: await newProject({\n\t\t\t\tsettings: { baseLocale: \"en\", locales: [\"en\", \"de\"] },\n\t\t\t}),\n\t\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\t\turlPatterns: [\n\t\t\t\t{\n\t\t\t\t\tpattern: \"http://example.com/:path(.*)?\",\n\t\t\t\t\tlocalized: [\n\t\t\t\t\t\t[\"en\", \"http://example.com/:path(.*)?\"],\n\t\t\t\t\t\t[\"de\", `${protocol}://de.example.com/:path(.*)?`],\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t\truntime.overwriteGetLocale(() => \"en\");\n\n\t\tfor (const prefix of [\"//\", \"/\\\\\", \"/\\t/\"]) {\n\t\t\truntime.overwriteGetUrlOrigin(() => \"http://app.example.com\");\n\t\t\texpect(\n\t\t\t\truntime.localizeHref(`${prefix}example.com/hello?q=1#section`, {\n\t\t\t\t\tlocale: \"de\",\n\t\t\t\t})\n\t\t\t).toBe(\n\t\t\t\t`${protocol === \"http\" ? \"\" : \"https:\"}//de.example.com/hello?q=1#section`\n\t\t\t);\n\t\t\texpect(\n\t\t\t\truntime.localizeHref(`${prefix}example.com:80/hello`, { locale: \"de\" })\n\t\t\t).toBe(\n\t\t\t\tprotocol === \"http\"\n\t\t\t\t\t? \"//de.example.com:80/hello\"\n\t\t\t\t\t: \"https://de.example.com/hello\"\n\t\t\t);\n\t\t\truntime.overwriteGetUrlOrigin(() => `${protocol}://app.example.com`);\n\t\t\texpect(\n\t\t\t\truntime.deLocalizeHref(`${prefix}de.example.com/hello?q=1#section`)\n\t\t\t).toBe(\n\t\t\t\t`${protocol === \"http\" ? \"\" : \"http:\"}//example.com/hello?q=1#section`\n\t\t\t);\n\t\t\texpect(\n\t\t\t\truntime.deLocalizeHref(\n\t\t\t\t\t`${prefix}de.example.com:${protocol === \"http\" ? \"80\" : \"443\"}/hello`\n\t\t\t\t)\n\t\t\t).toBe(\n\t\t\t\tprotocol === \"http\"\n\t\t\t\t\t? \"//example.com:80/hello\"\n\t\t\t\t\t: \"http://example.com/hello\"\n\t\t\t);\n\t\t}\n\t}\n);\n\n// useful if domain based localization is used for example\ntest(\"returns an absolute href if the provided href is relative but the origin of the localized href differs\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tstrategy: [\"url\", \"globalVariable\", \"baseLocale\"],\n\t\turlPatterns: [\n\t\t\t{\n\t\t\t\tpattern: \"http://example.com/:path(.*)?\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"de\", \"http://de.example.com/:path(.*)?\"],\n\t\t\t\t\t[\"en\", \"http://example.com/:path(.*)?\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\t// simulating routing from current en page to de page\n\truntime.overwriteGetLocale(() => \"en\");\n\truntime.overwriteGetUrlOrigin(() => \"http://example.com\");\n\n\texpect(runtime.localizeHref(\"/hello\", { locale: \"de\" })).toBe(\n\t\t\"http://de.example.com/hello\"\n\t);\n\texpect(runtime.deLocalizeHref(\"http://de.example.com/hello\")).toBe(\n\t\t\"http://example.com/hello\"\n\t);\n\n\t// simulating routing from current de page to en page\n\truntime.overwriteGetLocale(() => \"de\");\n\n\texpect(runtime.localizeHref(\"/hello\", { locale: \"en\" })).toBe(\n\t\t\"http://example.com/hello\"\n\t);\n\texpect(runtime.deLocalizeHref(\"http://example.com/hello\")).toBe(\n\t\t\"http://example.com/hello\"\n\t);\n});\n\n// https://github.com/opral/inlang-paraglide-js/issues/362\ntest(\"adding a base path\", async () => {\n\tconst base = \"shop\";\n\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tstrategy: [\"url\"],\n\t\turlPatterns: [\n\t\t\t{\n\t\t\t\tpattern: `/{${base}/}?:path(.*)?`,\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"de\", `/{${base}/}?de/:path(.*)?`],\n\t\t\t\t\t[\"en\", `/{${base}/}?:path(.*)?`],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\t// simulating the current locale to be en\n\truntime.overwriteGetLocale(() => \"en\");\n\n\texpect(runtime.localizeHref(\"/about\")).toBe(\"/shop/about\");\n\texpect(runtime.deLocalizeHref(\"/about\")).toBe(\"/shop/about\");\n\n\t// simulating current locale to be de\n\truntime.overwriteGetLocale(() => \"de\");\n\texpect(runtime.localizeHref(\"/about\")).toBe(\"/shop/de/about\");\n\texpect(runtime.deLocalizeHref(\"/de/about\")).toBe(\"/shop/about\");\n});\n\ntest(\"default url patterns to improve out of the box experience\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\", \"fr\"],\n\t\t\t},\n\t\t}),\n\t\tisServer: \"false\",\n\t\tstrategy: [\"url\"],\n\t\turlPatterns: undefined,\n\t});\n\n\t// polyfilling window\n\tglobalThis.window = { location: new URL(\"http://example.com\") } as any;\n\n\texpect(runtime.urlPatterns).toStrictEqual([\n\t\t{\n\t\t\tpattern: \":protocol://:domain(.*)::port?/:path(.*)?\",\n\t\t\tlocalized: [\n\t\t\t\t[\"de\", \":protocol://:domain(.*)::port?/de/:path(.*)?\"],\n\t\t\t\t[\"fr\", \":protocol://:domain(.*)::port?/fr/:path(.*)?\"],\n\t\t\t\t[\"en\", \":protocol://:domain(.*)::port?/:path(.*)?\"],\n\t\t\t],\n\t\t},\n\t]);\n\n\t// expecting the baseLocale to not be included in the url\n\texpect(runtime.localizeHref(\"/about\", { locale: \"en\" })).toBe(\"/about\");\n\texpect(runtime.localizeHref(\"/about\", { locale: \"de\" })).toBe(\"/de/about\");\n\n\t// expecting root path to work\n\texpect(runtime.localizeHref(\"/\", { locale: \"en\" })).toBe(\"/\");\n\texpect(runtime.localizeHref(\"/\", { locale: \"de\" })).toBe(\"/de/\");\n\n\t// expecting de-localization to work\n\texpect(runtime.deLocalizeHref(\"/de/\")).toBe(\"/\");\n\texpect(runtime.deLocalizeHref(\"/de/about\")).toBe(\"/about\");\n\texpect(runtime.deLocalizeHref(\"/about\")).toBe(\"/about\");\n\n\t// expecting localhost to work (for development)\n\texpect(runtime.localizeHref(\"http://localhost:5173/\", { locale: \"de\" })).toBe(\n\t\t\"http://localhost:5173/de/\"\n\t);\n\n\t// de-localizing localhost\n\texpect(runtime.deLocalizeHref(\"http://localhost:5173/de\")).toBe(\n\t\t\"http://localhost:5173/\"\n\t);\n\n\t// pathnames\n\texpect(\n\t\truntime.localizeHref(\"http://localhost:5173/about\", { locale: \"de\" })\n\t).toBe(\"http://localhost:5173/de/about\");\n\n\texpect(runtime.deLocalizeHref(\"http://localhost:5173/de/about\")).toBe(\n\t\t\"http://localhost:5173/about\"\n\t);\n});\n\ntest(\"normalizes localized root hrefs and anchors\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tisServer: \"false\",\n\t\tstrategy: [\"url\"],\n\t\ttrailingSlash: \"never\",\n\t});\n\n\tglobalThis.window = { location: new URL(\"http://example.com\") } as any;\n\n\texpect(runtime.localizeHref(\"/\", { locale: \"de\" })).toBe(\"/de\");\n\texpect(runtime.localizeHref(\"/#section\", { locale: \"de\" })).toBe(\n\t\t\"/de#section\"\n\t);\n\texpect(runtime.deLocalizeHref(\"/de/\")).toBe(\"/\");\n});\n\ntest(\"normalizes mixed-case explicit locales in localizeHref\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tstrategy: [\"url\"],\n\t});\n\n\texpect(runtime.localizeHref(\"/about\", { locale: \"DE\" })).toBe(\"/de/about\");\n\n\texpect(runtime.localizeHref(\"/de/about\", { locale: \"EN\" })).toBe(\"/about\");\n});\n\ntest(\"keeps custom URLPattern path expressions on the generic fallback\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: {\n\t\t\t\tbaseLocale: \"en\",\n\t\t\t\tlocales: [\"en\", \"de\"],\n\t\t\t},\n\t\t}),\n\t\tstrategy: [\"url\"],\n\t\turlPatterns: [\n\t\t\t{\n\t\t\t\tpattern: \"/:slug(.)?\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"en\", \"/:slug(.)?\"],\n\t\t\t\t\t[\"de\", \"/de/:slug(.)?\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\truntime.overwriteGetLocale(() => \"en\");\n\truntime.overwriteGetUrlOrigin(() => \"https://example.com\");\n\n\t// `(.)` matches exactly one character in URLPattern. A prefix-only\n\t// implementation must not broaden this route into a catch-all.\n\texpect(runtime.localizeHref(\"/a\", { locale: \"de\" })).toBe(\"/de/a\");\n\texpect(runtime.localizeHref(\"/about\", { locale: \"de\" })).toBe(\"/about\");\n});\n\ntest.each([undefined, \"always\", \"never\"] as const)(\n\t\"default hrefs preserve slashes unless trailingSlash is %s\",\n\tasync (trailingSlash) => {\n\t\tconst runtime = await createParaglide({\n\t\t\tblob: await newProject({\n\t\t\t\tsettings: { baseLocale: \"de\", locales: [\"de\", \"en\"] },\n\t\t\t}),\n\t\t\tstrategy: [\"url\", \"baseLocale\"],\n\t\t\ttrailingSlash,\n\t\t});\n\t\tconst paths = [\"/\", \"/about/\", \"/contact/\"];\n\t\tconst suffix = trailingSlash === \"never\" ? \"\" : \"/\";\n\t\texpect(\n\t\t\tpaths.flatMap((path) =>\n\t\t\t\truntime.locales.map((locale) => runtime.localizeHref(path, { locale }))\n\t\t\t)\n\t\t).toEqual([\n\t\t\t\"/\",\n\t\t\t\"/en\" + suffix,\n\t\t\t\"/about\" + suffix,\n\t\t\t\"/en/about\" + suffix,\n\t\t\t\"/contact\" + suffix,\n\t\t\t\"/en/contact\" + suffix,\n\t\t]);\n\t\texpect(runtime.localizeHref(\"/about\", { locale: \"en\" })).toBe(\n\t\t\t\"/en/about\" + (trailingSlash === \"always\" ? \"/\" : \"\")\n\t\t);\n\t\texpect(runtime.deLocalizeHref(\"/en/about/?q=test#section\")).toBe(\n\t\t\t\"/about\" + suffix + \"?q=test#section\"\n\t\t);\n\t\texpect(\n\t\t\truntime.localizeHref(\"/en/about/?q=test#section\", { locale: \"de\" })\n\t\t).toBe(\"/about\" + suffix + \"?q=test#section\");\n\t}\n);\n\ntest(\"default routes remove detected locales after repeated leading separators\", async () => {\n\tconst runtime = await createParaglide({\n\t\tblob: await newProject({\n\t\t\tsettings: { baseLocale: \"en\", locales: [\"en\", \"de\", \"fr\"] },\n\t\t}),\n\t\tstrategy: [\"url\", \"baseLocale\"],\n\t});\n\tfor (const suffix of [\"\", \"/\"]) {\n\t\tconst input = `https://example.com//de/about${suffix}?q=test#section`;\n\t\texpect(runtime.extractLocaleFromUrl(input)).toBe(\"de\");\n\t\texpect(runtime.localizeUrl(input, { locale: \"en\" }).href).toBe(\n\t\t\t`https://example.com/about${suffix}?q=test#section`\n\t\t);\n\t\texpect(runtime.localizeUrl(input, { locale: \"fr\" }).href).toBe(\n\t\t\t`https://example.com/fr/about${suffix}?q=test#section`\n\t\t);\n\t\texpect(runtime.deLocalizeUrl(input).href).toBe(\n\t\t\t`https://example.com/about${suffix}?q=test#section`\n\t\t);\n\t}\n});\n"]}
|
|
@@ -3,7 +3,7 @@ import { clearLocaleCookieCache } from "./extract-locale-from-cookie.js";
|
|
|
3
3
|
import { localizeUrl } from "./localize-url.js";
|
|
4
4
|
import { getStrategyForUrl } from "./route-strategy.js";
|
|
5
5
|
import { customClientStrategies, isCustomStrategy } from "./strategy.js";
|
|
6
|
-
import { cookieDomain, cookieMaxAge, cookieName, isServer, localStorageKey, experimentalStaticLocale, strategy, TREE_SHAKE_COOKIE_STRATEGY_USED, TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED, TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED, TREE_SHAKE_URL_STRATEGY_USED, } from "./variables.js";
|
|
6
|
+
import { cookieDomain, cookieMaxAge, cookieName, cookieSameSite, cookieSecure, isServer, localStorageKey, experimentalStaticLocale, strategy, TREE_SHAKE_COOKIE_STRATEGY_USED, TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED, TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED, TREE_SHAKE_URL_STRATEGY_USED, } from "./variables.js";
|
|
7
7
|
/**
|
|
8
8
|
* Navigates to the localized URL, or reloads the current page
|
|
9
9
|
*
|
|
@@ -89,10 +89,23 @@ export let setLocale = (newLocale, options) => {
|
|
|
89
89
|
continue;
|
|
90
90
|
}
|
|
91
91
|
// set the cookie
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
let cookieString = `${cookieName}=${newLocale}; path=/; max-age=${cookieMaxAge}`;
|
|
93
|
+
if (cookieDomain) {
|
|
94
|
+
cookieString += `; domain=${cookieDomain}`;
|
|
95
|
+
}
|
|
96
|
+
if (cookieSameSite) {
|
|
97
|
+
cookieString += `; samesite=${cookieSameSite}`;
|
|
98
|
+
}
|
|
99
|
+
if (cookieSecure === true ||
|
|
100
|
+
(cookieSecure === "auto" && window.location?.protocol === "https:")) {
|
|
101
|
+
cookieString += "; secure";
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
document.cookie = cookieString;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// document.cookie throws in sandboxed iframes and other opaque origins
|
|
108
|
+
}
|
|
96
109
|
clearLocaleCookieCache();
|
|
97
110
|
}
|
|
98
111
|
else if (strat === "baseLocale") {
|
|
@@ -118,7 +131,12 @@ export let setLocale = (newLocale, options) => {
|
|
|
118
131
|
strat === "localStorage" &&
|
|
119
132
|
typeof window !== "undefined") {
|
|
120
133
|
// set the localStorage
|
|
121
|
-
|
|
134
|
+
try {
|
|
135
|
+
localStorage.setItem(localStorageKey, newLocale);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// localStorage throws when storage access is denied
|
|
139
|
+
}
|
|
122
140
|
}
|
|
123
141
|
else if (isCustomStrategy(strat) && customClientStrategies.has(strat)) {
|
|
124
142
|
const handler = customClientStrategies.get(strat);
|