@inlang/paraglide-js 2.23.2 → 2.24.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/dist/bundler-plugins/per-locale-build/vite-environments.js +12 -7
- package/dist/bundler-plugins/per-locale-build/vite-environments.js.map +1 -1
- package/dist/bundler-plugins/unplugin.js +134 -4
- package/dist/bundler-plugins/unplugin.js.map +1 -1
- package/dist/bundler-plugins/unplugin.test.js +77 -0
- package/dist/bundler-plugins/unplugin.test.js.map +1 -1
- package/dist/cli/commands/compile/command.js +8 -1
- package/dist/cli/commands/compile/command.js.map +1 -1
- package/dist/cli/commands/compile/command.test.js +45 -0
- package/dist/cli/commands/compile/command.test.js.map +1 -1
- package/dist/compiler/compile.js +0 -15
- package/dist/compiler/compile.js.map +1 -1
- package/dist/compiler/compile.test.js +0 -52
- package/dist/compiler/compile.test.js.map +1 -1
- package/dist/compiler/compiler-options.d.ts +8 -0
- package/dist/compiler/compiler-options.js.map +1 -1
- package/dist/compiler/runtime/create-runtime.d.ts +1 -0
- package/dist/compiler/runtime/create-runtime.js +11 -0
- package/dist/compiler/runtime/create-runtime.js.map +1 -1
- package/dist/compiler/runtime/exec-url-pattern.d.ts +9 -0
- package/dist/compiler/runtime/exec-url-pattern.js +31 -0
- package/dist/compiler/runtime/exec-url-pattern.js.map +1 -0
- package/dist/compiler/runtime/extract-locale-from-url.js +8 -3
- package/dist/compiler/runtime/extract-locale-from-url.js.map +1 -1
- package/dist/compiler/runtime/generate-static-localized-urls.js +12 -6
- package/dist/compiler/runtime/generate-static-localized-urls.js.map +1 -1
- package/dist/compiler/runtime/generate-static-localized-urls.test.js +46 -0
- package/dist/compiler/runtime/generate-static-localized-urls.test.js.map +1 -1
- package/dist/compiler/runtime/localize-href.bench.d.ts +1 -0
- package/dist/compiler/runtime/localize-href.bench.js +77 -0
- package/dist/compiler/runtime/localize-href.bench.js.map +1 -0
- package/dist/compiler/runtime/localize-href.test.js +43 -0
- package/dist/compiler/runtime/localize-href.test.js.map +1 -1
- package/dist/compiler/runtime/localize-url.d.ts +14 -0
- package/dist/compiler/runtime/localize-url.js +376 -18
- package/dist/compiler/runtime/localize-url.js.map +1 -1
- package/dist/compiler/runtime/localize-url.test.js +163 -0
- package/dist/compiler/runtime/localize-url.test.js.map +1 -1
- package/dist/compiler/runtime/normalize-trailing-slash.d.ts +10 -0
- package/dist/compiler/runtime/normalize-trailing-slash.js +23 -0
- package/dist/compiler/runtime/normalize-trailing-slash.js.map +1 -0
- package/dist/compiler/runtime/route-strategy.js +4 -2
- package/dist/compiler/runtime/route-strategy.js.map +1 -1
- package/dist/compiler/runtime/should-redirect.js +4 -1
- package/dist/compiler/runtime/should-redirect.js.map +1 -1
- package/dist/compiler/runtime/should-redirect.test.js +30 -2
- package/dist/compiler/runtime/should-redirect.test.js.map +1 -1
- package/dist/compiler/runtime/type.d.ts +1 -0
- package/dist/compiler/runtime/type.js.map +1 -1
- package/dist/compiler/runtime/variables.d.ts +6 -0
- package/dist/compiler/runtime/variables.js +6 -0
- package/dist/compiler/runtime/variables.js.map +1 -1
- package/dist/compiler/server/middleware.js +6 -2
- package/dist/compiler/server/middleware.js.map +1 -1
- package/dist/compiler/server/middleware.test.js +34 -0
- package/dist/compiler/server/middleware.test.js.map +1 -1
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +9 -0
- package/dist/index.test.js.map +1 -0
- 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.d.ts +1 -0
- package/dist/services/file-handling/write-output.bench.js +24 -0
- package/dist/services/file-handling/write-output.bench.js.map +1 -0
- package/dist/services/file-handling/write-output.js +16 -20
- package/dist/services/file-handling/write-output.js.map +1 -1
- package/dist/services/file-handling/write-output.test.js +27 -0
- package/dist/services/file-handling/write-output.test.js.map +1 -1
- package/package.json +2 -2
- package/dist/services/account/index.d.ts +0 -14
- package/dist/services/account/index.js +0 -43
- package/dist/services/account/index.js.map +0 -1
|
@@ -148,6 +148,23 @@ test("default url patterns to improve out of the box experience", async () => {
|
|
|
148
148
|
expect(runtime.localizeHref("http://localhost:5173/about", { locale: "de" })).toBe("http://localhost:5173/de/about");
|
|
149
149
|
expect(runtime.deLocalizeHref("http://localhost:5173/de/about")).toBe("http://localhost:5173/about");
|
|
150
150
|
});
|
|
151
|
+
test("normalizes localized root hrefs and anchors", async () => {
|
|
152
|
+
const runtime = await createParaglide({
|
|
153
|
+
blob: await newProject({
|
|
154
|
+
settings: {
|
|
155
|
+
baseLocale: "en",
|
|
156
|
+
locales: ["en", "de"],
|
|
157
|
+
},
|
|
158
|
+
}),
|
|
159
|
+
isServer: "false",
|
|
160
|
+
strategy: ["url"],
|
|
161
|
+
trailingSlash: "never",
|
|
162
|
+
});
|
|
163
|
+
globalThis.window = { location: new URL("http://example.com") };
|
|
164
|
+
expect(runtime.localizeHref("/", { locale: "de" })).toBe("/de");
|
|
165
|
+
expect(runtime.localizeHref("/#section", { locale: "de" })).toBe("/de#section");
|
|
166
|
+
expect(runtime.deLocalizeHref("/de/")).toBe("/");
|
|
167
|
+
});
|
|
151
168
|
test("normalizes mixed-case explicit locales in localizeHref", async () => {
|
|
152
169
|
const runtime = await createParaglide({
|
|
153
170
|
blob: await newProject({
|
|
@@ -161,4 +178,30 @@ test("normalizes mixed-case explicit locales in localizeHref", async () => {
|
|
|
161
178
|
expect(runtime.localizeHref("/about", { locale: "DE" })).toBe("/de/about");
|
|
162
179
|
expect(runtime.localizeHref("/de/about", { locale: "EN" })).toBe("/about");
|
|
163
180
|
});
|
|
181
|
+
test("keeps custom URLPattern path expressions on the generic fallback", async () => {
|
|
182
|
+
const runtime = await createParaglide({
|
|
183
|
+
blob: await newProject({
|
|
184
|
+
settings: {
|
|
185
|
+
baseLocale: "en",
|
|
186
|
+
locales: ["en", "de"],
|
|
187
|
+
},
|
|
188
|
+
}),
|
|
189
|
+
strategy: ["url"],
|
|
190
|
+
urlPatterns: [
|
|
191
|
+
{
|
|
192
|
+
pattern: "/:slug(.)?",
|
|
193
|
+
localized: [
|
|
194
|
+
["en", "/:slug(.)?"],
|
|
195
|
+
["de", "/de/:slug(.)?"],
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
});
|
|
200
|
+
runtime.overwriteGetLocale(() => "en");
|
|
201
|
+
runtime.overwriteGetUrlOrigin(() => "https://example.com");
|
|
202
|
+
// `(.)` matches exactly one character in URLPattern. A prefix-only
|
|
203
|
+
// implementation must not broaden this route into a catch-all.
|
|
204
|
+
expect(runtime.localizeHref("/a", { locale: "de" })).toBe("/de/a");
|
|
205
|
+
expect(runtime.localizeHref("/about", { locale: "de" })).toBe("/about");
|
|
206
|
+
});
|
|
164
207
|
//# sourceMappingURL=localize-href.test.js.map
|
|
@@ -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 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"]}
|
|
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"]}
|
|
@@ -93,3 +93,17 @@ export declare function deLocalizeUrl(url: string | URL): URL;
|
|
|
93
93
|
* @returns {Record<string, string | null | undefined>} An object containing all named groups from the match.
|
|
94
94
|
*/
|
|
95
95
|
export declare function aggregateGroups(match: any): Record<string, string | null | undefined>;
|
|
96
|
+
export type FastPathPattern = {
|
|
97
|
+
protocol: string | undefined;
|
|
98
|
+
hostname: string | undefined;
|
|
99
|
+
port: string | undefined;
|
|
100
|
+
pathnamePrefix: string;
|
|
101
|
+
pathMode: "segments" | "catch-all-optional" | "catch-all-required";
|
|
102
|
+
};
|
|
103
|
+
export type FastPathRoute = {
|
|
104
|
+
base: FastPathPattern;
|
|
105
|
+
localized: Array<{
|
|
106
|
+
locale: string;
|
|
107
|
+
pattern: FastPathPattern;
|
|
108
|
+
}>;
|
|
109
|
+
};
|
|
@@ -2,7 +2,9 @@ import { extractLocaleFromUrl } from "./extract-locale-from-url.js";
|
|
|
2
2
|
import { getLocale } from "./get-locale.js";
|
|
3
3
|
import { getUrlOrigin } from "./get-url-origin.js";
|
|
4
4
|
import { assertIsLocale, toLocale } from "./check-locale.js";
|
|
5
|
-
import { baseLocale, TREE_SHAKE_DEFAULT_URL_PATTERN_USED, urlPatterns, } from "./variables.js";
|
|
5
|
+
import { baseLocale, TREE_SHAKE_DEFAULT_URL_PATTERN_USED, trailingSlash, urlPatterns, } from "./variables.js";
|
|
6
|
+
import { normalizeTrailingSlash } from "./normalize-trailing-slash.js";
|
|
7
|
+
import { execUrlPattern } from "./exec-url-pattern.js";
|
|
6
8
|
/**
|
|
7
9
|
* Lower-level URL localization function, primarily used in server contexts.
|
|
8
10
|
*
|
|
@@ -54,12 +56,24 @@ export function localizeUrl(url, options) {
|
|
|
54
56
|
if (TREE_SHAKE_DEFAULT_URL_PATTERN_USED) {
|
|
55
57
|
return localizeUrlDefaultPattern(url, targetLocale);
|
|
56
58
|
}
|
|
57
|
-
const
|
|
59
|
+
const originalUrl = typeof url === "string" ? new URL(url) : url;
|
|
60
|
+
const urlObj = trailingSlash === undefined
|
|
61
|
+
? originalUrl
|
|
62
|
+
: normalizeTrailingSlash(new URL(originalUrl));
|
|
58
63
|
// Iterate over URL patterns
|
|
59
64
|
for (const element of urlPatterns) {
|
|
65
|
+
// Most applications use a locale prefix (and sometimes a fixed domain)
|
|
66
|
+
// with a trailing catch-all path. Those routes do not need URLPattern's
|
|
67
|
+
// parser or matcher on every call. Keep the generic matcher below as the
|
|
68
|
+
// fallback for every other URLPattern shape. This stays inside the outer
|
|
69
|
+
// loop so specific patterns retain precedence over a later catch-all.
|
|
70
|
+
const fastPathLocalized = localizeUrlFastPath(urlObj, targetLocale, element);
|
|
71
|
+
if (fastPathLocalized !== undefined) {
|
|
72
|
+
return fastPathLocalized;
|
|
73
|
+
}
|
|
60
74
|
// match localized patterns
|
|
61
75
|
for (const [, localizedPattern] of element.localized) {
|
|
62
|
-
const match =
|
|
76
|
+
const match = execUrlPattern(getUrlPattern(localizedPattern, urlObj), urlObj);
|
|
63
77
|
if (!match) {
|
|
64
78
|
continue;
|
|
65
79
|
}
|
|
@@ -68,19 +82,19 @@ export function localizeUrl(url, options) {
|
|
|
68
82
|
continue;
|
|
69
83
|
}
|
|
70
84
|
const localizedUrl = fillPattern(targetPattern, aggregateGroups(match), urlObj.origin);
|
|
71
|
-
return fillMissingUrlParts(localizedUrl, match);
|
|
85
|
+
return normalizeTrailingSlash(fillMissingUrlParts(localizedUrl, match));
|
|
72
86
|
}
|
|
73
|
-
const unlocalizedMatch =
|
|
87
|
+
const unlocalizedMatch = execUrlPattern(getUrlPattern(element.pattern, urlObj), urlObj);
|
|
74
88
|
if (unlocalizedMatch) {
|
|
75
89
|
const targetPattern = element.localized.find(([locale]) => locale === targetLocale)?.[1];
|
|
76
90
|
if (targetPattern) {
|
|
77
91
|
const localizedUrl = fillPattern(targetPattern, aggregateGroups(unlocalizedMatch), urlObj.origin);
|
|
78
|
-
return fillMissingUrlParts(localizedUrl, unlocalizedMatch);
|
|
92
|
+
return normalizeTrailingSlash(fillMissingUrlParts(localizedUrl, unlocalizedMatch));
|
|
79
93
|
}
|
|
80
94
|
}
|
|
81
95
|
}
|
|
82
96
|
// If no match found, return the original URL
|
|
83
|
-
return
|
|
97
|
+
return originalUrl;
|
|
84
98
|
}
|
|
85
99
|
/**
|
|
86
100
|
* https://github.com/opral/inlang-paraglide-js/issues/381
|
|
@@ -90,11 +104,11 @@ export function localizeUrl(url, options) {
|
|
|
90
104
|
* @returns {URL}
|
|
91
105
|
*/
|
|
92
106
|
function localizeUrlDefaultPattern(url, locale) {
|
|
93
|
-
const urlObj = typeof url === "string" ? new URL(url, getUrlOrigin()) : new URL(url);
|
|
107
|
+
const urlObj = normalizeTrailingSlash(typeof url === "string" ? new URL(url, getUrlOrigin()) : new URL(url));
|
|
94
108
|
const currentLocale = extractLocaleFromUrl(urlObj);
|
|
95
109
|
// If current locale matches target locale, no change needed
|
|
96
110
|
if (currentLocale === locale) {
|
|
97
|
-
return urlObj;
|
|
111
|
+
return normalizeTrailingSlash(urlObj);
|
|
98
112
|
}
|
|
99
113
|
const pathSegments = urlObj.pathname.split("/").filter(Boolean);
|
|
100
114
|
// If current path starts with a locale, remove it
|
|
@@ -109,7 +123,7 @@ function localizeUrlDefaultPattern(url, locale) {
|
|
|
109
123
|
// For other locales, add prefix
|
|
110
124
|
urlObj.pathname = "/" + locale + "/" + pathSegments.join("/");
|
|
111
125
|
}
|
|
112
|
-
return urlObj;
|
|
126
|
+
return normalizeTrailingSlash(urlObj);
|
|
113
127
|
}
|
|
114
128
|
/**
|
|
115
129
|
* Low-level URL de-localization function, primarily used in server contexts.
|
|
@@ -154,28 +168,35 @@ export function deLocalizeUrl(url) {
|
|
|
154
168
|
if (TREE_SHAKE_DEFAULT_URL_PATTERN_USED) {
|
|
155
169
|
return deLocalizeUrlDefaultPattern(url);
|
|
156
170
|
}
|
|
157
|
-
const
|
|
171
|
+
const originalUrl = typeof url === "string" ? new URL(url) : url;
|
|
172
|
+
const urlObj = trailingSlash === undefined
|
|
173
|
+
? originalUrl
|
|
174
|
+
: normalizeTrailingSlash(new URL(originalUrl));
|
|
158
175
|
// Iterate over URL patterns
|
|
159
176
|
for (const element of urlPatterns) {
|
|
177
|
+
const fastPathDeLocalized = deLocalizeUrlFastPath(urlObj, element);
|
|
178
|
+
if (fastPathDeLocalized !== undefined) {
|
|
179
|
+
return fastPathDeLocalized;
|
|
180
|
+
}
|
|
160
181
|
// Iterate over localized versions
|
|
161
182
|
for (const [, localizedPattern] of element.localized) {
|
|
162
|
-
const match =
|
|
183
|
+
const match = execUrlPattern(getUrlPattern(localizedPattern, urlObj), urlObj);
|
|
163
184
|
if (match) {
|
|
164
185
|
// Convert localized URL back to the base pattern
|
|
165
186
|
const groups = aggregateGroups(match);
|
|
166
187
|
const baseUrl = fillPattern(element.pattern, groups, urlObj.origin);
|
|
167
|
-
return fillMissingUrlParts(baseUrl, match);
|
|
188
|
+
return normalizeTrailingSlash(fillMissingUrlParts(baseUrl, match));
|
|
168
189
|
}
|
|
169
190
|
}
|
|
170
191
|
// match unlocalized pattern
|
|
171
|
-
const unlocalizedMatch =
|
|
192
|
+
const unlocalizedMatch = execUrlPattern(getUrlPattern(element.pattern, urlObj), urlObj);
|
|
172
193
|
if (unlocalizedMatch) {
|
|
173
194
|
const baseUrl = fillPattern(element.pattern, aggregateGroups(unlocalizedMatch), urlObj.origin);
|
|
174
|
-
return fillMissingUrlParts(baseUrl, unlocalizedMatch);
|
|
195
|
+
return normalizeTrailingSlash(fillMissingUrlParts(baseUrl, unlocalizedMatch));
|
|
175
196
|
}
|
|
176
197
|
}
|
|
177
198
|
// no match found return the original url
|
|
178
|
-
return
|
|
199
|
+
return originalUrl;
|
|
179
200
|
}
|
|
180
201
|
/**
|
|
181
202
|
* De-localizes a URL using the default pattern (/:locale/*)
|
|
@@ -183,13 +204,13 @@ export function deLocalizeUrl(url) {
|
|
|
183
204
|
* @returns {URL}
|
|
184
205
|
*/
|
|
185
206
|
function deLocalizeUrlDefaultPattern(url) {
|
|
186
|
-
const urlObj = typeof url === "string" ? new URL(url, getUrlOrigin()) : new URL(url);
|
|
207
|
+
const urlObj = normalizeTrailingSlash(typeof url === "string" ? new URL(url, getUrlOrigin()) : new URL(url));
|
|
187
208
|
const pathSegments = urlObj.pathname.split("/").filter(Boolean);
|
|
188
209
|
// If first segment is a locale, remove it
|
|
189
210
|
if (pathSegments.length > 0 && toLocale(pathSegments[0])) {
|
|
190
211
|
urlObj.pathname = "/" + pathSegments.slice(1).join("/");
|
|
191
212
|
}
|
|
192
|
-
return urlObj;
|
|
213
|
+
return normalizeTrailingSlash(urlObj);
|
|
193
214
|
}
|
|
194
215
|
/**
|
|
195
216
|
* Takes matches of implicit wildcards in the UrlPattern (when a part is missing
|
|
@@ -314,4 +335,341 @@ export function aggregateGroups(match) {
|
|
|
314
335
|
...match.username.groups,
|
|
315
336
|
};
|
|
316
337
|
}
|
|
338
|
+
/** @type {Map<string, URLPattern>} */
|
|
339
|
+
const urlPatternCache = new Map();
|
|
340
|
+
const URL_PATTERN_CACHE_LIMIT = 128;
|
|
341
|
+
const ABSOLUTE_URL_PATTERN = /^(?:[A-Za-z][A-Za-z\d+.-]*|:[A-Za-z][A-Za-z\d_-]*):\/\//;
|
|
342
|
+
/**
|
|
343
|
+
* URLPattern's base URL affects relative patterns. Absolute patterns only
|
|
344
|
+
* depend on the pattern itself, while root-relative patterns also depend on
|
|
345
|
+
* the URL origin. Other relative patterns are deliberately not cached because
|
|
346
|
+
* their semantics depend on the complete base URL.
|
|
347
|
+
*
|
|
348
|
+
* @param {string} pattern
|
|
349
|
+
* @param {URL} url
|
|
350
|
+
* @returns {URLPattern}
|
|
351
|
+
*/
|
|
352
|
+
function getUrlPattern(pattern, url) {
|
|
353
|
+
const isAbsolutePattern = ABSOLUTE_URL_PATTERN.test(pattern);
|
|
354
|
+
const isRootRelativePattern = pattern.startsWith("/");
|
|
355
|
+
if (!isAbsolutePattern && !isRootRelativePattern) {
|
|
356
|
+
return new URLPattern(pattern, url.href);
|
|
357
|
+
}
|
|
358
|
+
const key = isAbsolutePattern
|
|
359
|
+
? pattern
|
|
360
|
+
: JSON.stringify([url.origin, pattern]);
|
|
361
|
+
const cached = urlPatternCache.get(key);
|
|
362
|
+
if (cached !== undefined) {
|
|
363
|
+
// Refresh the entry so frequently used patterns stay in the bounded cache.
|
|
364
|
+
urlPatternCache.delete(key);
|
|
365
|
+
urlPatternCache.set(key, cached);
|
|
366
|
+
return cached;
|
|
367
|
+
}
|
|
368
|
+
const compiled = new URLPattern(pattern, url.href);
|
|
369
|
+
if (urlPatternCache.size >= URL_PATTERN_CACHE_LIMIT) {
|
|
370
|
+
const oldestKey = urlPatternCache.keys().next().value;
|
|
371
|
+
if (oldestKey !== undefined)
|
|
372
|
+
urlPatternCache.delete(oldestKey);
|
|
373
|
+
}
|
|
374
|
+
urlPatternCache.set(key, compiled);
|
|
375
|
+
return compiled;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* A small, deliberately conservative subset of URLPattern routing.
|
|
379
|
+
*
|
|
380
|
+
* The compiler emits routes such as `/:path(.*)?`, `/de/:path*`, or
|
|
381
|
+
* `https://example.com/:path*`. For those routes matching is equivalent to a
|
|
382
|
+
* pathname prefix check and (optionally) an origin check. Everything that has
|
|
383
|
+
* a dynamic host, a custom path regexp, or another URLPattern modifier keeps
|
|
384
|
+
* using the generic implementation above.
|
|
385
|
+
*
|
|
386
|
+
* @typedef {{
|
|
387
|
+
* protocol: string | undefined;
|
|
388
|
+
* hostname: string | undefined;
|
|
389
|
+
* port: string | undefined;
|
|
390
|
+
* pathnamePrefix: string;
|
|
391
|
+
* pathMode: "segments" | "catch-all-optional" | "catch-all-required";
|
|
392
|
+
* }} FastPathPattern
|
|
393
|
+
* @typedef {{
|
|
394
|
+
* base: FastPathPattern;
|
|
395
|
+
* localized: Array<{ locale: string; pattern: FastPathPattern }>;
|
|
396
|
+
* }} FastPathRoute
|
|
397
|
+
*/
|
|
398
|
+
/** @type {WeakMap<object, FastPathRoute | null>} */
|
|
399
|
+
const fastPathRouteCache = new WeakMap();
|
|
400
|
+
/**
|
|
401
|
+
* @param {URL} urlObj
|
|
402
|
+
* @param {string} targetLocale
|
|
403
|
+
* @param {{ pattern: string; localized: Array<[string, string]> }} element
|
|
404
|
+
* @returns {URL | undefined}
|
|
405
|
+
*/
|
|
406
|
+
function localizeUrlFastPath(urlObj, targetLocale, element) {
|
|
407
|
+
const route = getFastPathRoute(element);
|
|
408
|
+
if (route === null)
|
|
409
|
+
return undefined;
|
|
410
|
+
// Preserve URLPattern's ordering: localized patterns are checked before
|
|
411
|
+
// the unlocalized pattern, and the first matching pattern wins.
|
|
412
|
+
for (const localized of route.localized) {
|
|
413
|
+
const suffix = matchFastPathPattern(localized.pattern, urlObj);
|
|
414
|
+
if (suffix === undefined)
|
|
415
|
+
continue;
|
|
416
|
+
const target = route.localized.find((candidate) => candidate.locale === targetLocale)?.pattern;
|
|
417
|
+
if (target === undefined)
|
|
418
|
+
continue;
|
|
419
|
+
return applyFastPathPattern(target, suffix, urlObj);
|
|
420
|
+
}
|
|
421
|
+
const suffix = matchFastPathPattern(route.base, urlObj);
|
|
422
|
+
if (suffix !== undefined) {
|
|
423
|
+
const target = route.localized.find((candidate) => candidate.locale === targetLocale)?.pattern;
|
|
424
|
+
if (target !== undefined) {
|
|
425
|
+
return applyFastPathPattern(target, suffix, urlObj);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* @param {URL} urlObj
|
|
432
|
+
* @param {{ pattern: string; localized: Array<[string, string]> }} element
|
|
433
|
+
* @returns {URL | undefined}
|
|
434
|
+
*/
|
|
435
|
+
function deLocalizeUrlFastPath(urlObj, element) {
|
|
436
|
+
const route = getFastPathRoute(element);
|
|
437
|
+
if (route === null)
|
|
438
|
+
return undefined;
|
|
439
|
+
for (const localized of route.localized) {
|
|
440
|
+
const suffix = matchFastPathPattern(localized.pattern, urlObj);
|
|
441
|
+
if (suffix !== undefined) {
|
|
442
|
+
return applyFastPathPattern(route.base, suffix, urlObj);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const suffix = matchFastPathPattern(route.base, urlObj);
|
|
446
|
+
if (suffix !== undefined) {
|
|
447
|
+
return applyFastPathPattern(route.base, suffix, urlObj);
|
|
448
|
+
}
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* @param {{ pattern: string; localized: Array<[string, string]> }} element
|
|
453
|
+
* @returns {FastPathRoute | null}
|
|
454
|
+
*/
|
|
455
|
+
function getFastPathRoute(element) {
|
|
456
|
+
const cached = fastPathRouteCache.get(element);
|
|
457
|
+
if (cached !== undefined)
|
|
458
|
+
return cached;
|
|
459
|
+
const base = parseFastPathPattern(element.pattern);
|
|
460
|
+
if (base === undefined) {
|
|
461
|
+
fastPathRouteCache.set(element, null);
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
const localized = [];
|
|
465
|
+
for (const [locale, pattern] of element.localized) {
|
|
466
|
+
const parsed = parseFastPathPattern(pattern);
|
|
467
|
+
if (parsed === undefined || parsed.pathMode !== base.pathMode) {
|
|
468
|
+
fastPathRouteCache.set(element, null);
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
localized.push({ locale, pattern: parsed });
|
|
472
|
+
}
|
|
473
|
+
const route = { base, localized };
|
|
474
|
+
fastPathRouteCache.set(element, route);
|
|
475
|
+
return route;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Parse only catch-all path patterns. In particular, do not treat `:path(.)?`
|
|
479
|
+
* as a catch-all: URLPattern's `(.)` means exactly one character and cannot be
|
|
480
|
+
* represented by this prefix matcher without changing routing semantics.
|
|
481
|
+
*
|
|
482
|
+
* @param {string} pattern
|
|
483
|
+
* @returns {FastPathPattern | undefined}
|
|
484
|
+
*/
|
|
485
|
+
function parseFastPathPattern(pattern) {
|
|
486
|
+
const wildcard = pattern.match(/\/:path(?:\(\.\*\)(?:\?)?|\*)$/);
|
|
487
|
+
if (wildcard === null || wildcard.index === undefined)
|
|
488
|
+
return undefined;
|
|
489
|
+
const prefix = pattern.slice(0, wildcard.index);
|
|
490
|
+
const originAndPath = parseFastPathOriginAndPath(prefix);
|
|
491
|
+
if (originAndPath === undefined)
|
|
492
|
+
return undefined;
|
|
493
|
+
const pathMode = wildcard[0].endsWith("*")
|
|
494
|
+
? "segments"
|
|
495
|
+
: wildcard[0].endsWith("?")
|
|
496
|
+
? "catch-all-optional"
|
|
497
|
+
: "catch-all-required";
|
|
498
|
+
return { ...originAndPath, pathMode };
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* @param {string} prefix
|
|
502
|
+
* @returns {FastPathPattern | undefined}
|
|
503
|
+
*/
|
|
504
|
+
function parseFastPathOriginAndPath(prefix) {
|
|
505
|
+
if (prefix === "" || prefix.startsWith("/")) {
|
|
506
|
+
if (hasUrlPatternSyntax(prefix))
|
|
507
|
+
return undefined;
|
|
508
|
+
return {
|
|
509
|
+
protocol: undefined,
|
|
510
|
+
hostname: undefined,
|
|
511
|
+
port: undefined,
|
|
512
|
+
pathnamePrefix: normalizePathPrefix(prefix),
|
|
513
|
+
pathMode: "catch-all-optional",
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
// A `:protocol://host` pattern is common in generated configurations. The
|
|
517
|
+
// protocol is intentionally left unconstrained, just like URLPattern.
|
|
518
|
+
const dynamicProtocol = prefix.match(/^:protocol:\/\/([^/]+)(\/.*)?$/);
|
|
519
|
+
const staticOrigin = prefix.match(/^([A-Za-z][A-Za-z\d+.-]*):\/\/([^/]+)(\/.*)?$/);
|
|
520
|
+
if (dynamicProtocol !== null) {
|
|
521
|
+
const dynamicHost = dynamicProtocol[1] ?? "";
|
|
522
|
+
const hostMatch = dynamicHost.match(/^([^:(){}?*+]+)(?::(\d+))?$/);
|
|
523
|
+
if (hostMatch === null ||
|
|
524
|
+
(dynamicProtocol[2] !== undefined &&
|
|
525
|
+
hasUrlPatternSyntax(dynamicProtocol[2]))) {
|
|
526
|
+
return undefined;
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
protocol: undefined,
|
|
530
|
+
hostname: (hostMatch[1] ?? "").toLowerCase(),
|
|
531
|
+
port: hostMatch[2],
|
|
532
|
+
pathnamePrefix: normalizePathPrefix(dynamicProtocol[2] ?? ""),
|
|
533
|
+
pathMode: "catch-all-optional",
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
if (staticOrigin === null)
|
|
537
|
+
return undefined;
|
|
538
|
+
const host = staticOrigin[2] ?? "";
|
|
539
|
+
const pathname = staticOrigin[3];
|
|
540
|
+
const hostMatch = host.match(/^([^:(){}?*+]+)(?::(\d+))?$/);
|
|
541
|
+
if (hostMatch === null ||
|
|
542
|
+
(pathname !== undefined && hasUrlPatternSyntax(pathname))) {
|
|
543
|
+
return undefined;
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
protocol: `${staticOrigin[1]}:`,
|
|
547
|
+
hostname: (hostMatch[1] ?? "").toLowerCase(),
|
|
548
|
+
port: normalizePatternPort(hostMatch[2], `${staticOrigin[1]}:`),
|
|
549
|
+
pathnamePrefix: normalizePathPrefix(pathname ?? ""),
|
|
550
|
+
pathMode: "catch-all-optional",
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* @param {string | undefined} port
|
|
555
|
+
* @param {string} protocol
|
|
556
|
+
* @returns {string | undefined}
|
|
557
|
+
*/
|
|
558
|
+
function normalizePatternPort(port, protocol) {
|
|
559
|
+
if (port === "80" && protocol === "http:")
|
|
560
|
+
return "";
|
|
561
|
+
if (port === "443" && protocol === "https:")
|
|
562
|
+
return "";
|
|
563
|
+
return port;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* @param {string} value
|
|
567
|
+
* @returns {boolean}
|
|
568
|
+
*/
|
|
569
|
+
function hasUrlPatternSyntax(value) {
|
|
570
|
+
return /[:(){}?*+]/.test(value);
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* @param {string} pathname
|
|
574
|
+
* @returns {string}
|
|
575
|
+
*/
|
|
576
|
+
function normalizePathPrefix(pathname) {
|
|
577
|
+
if (pathname === "" || pathname === "/")
|
|
578
|
+
return "/";
|
|
579
|
+
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* @param {FastPathPattern} pattern
|
|
583
|
+
* @param {URL} urlObj
|
|
584
|
+
* @returns {string | undefined}
|
|
585
|
+
*/
|
|
586
|
+
function matchFastPathPattern(pattern, urlObj) {
|
|
587
|
+
if (pattern.protocol !== undefined && pattern.protocol !== urlObj.protocol) {
|
|
588
|
+
return undefined;
|
|
589
|
+
}
|
|
590
|
+
if (pattern.hostname !== undefined &&
|
|
591
|
+
pattern.hostname !== urlObj.hostname.toLowerCase()) {
|
|
592
|
+
return undefined;
|
|
593
|
+
}
|
|
594
|
+
if (pattern.hostname !== undefined) {
|
|
595
|
+
const expectedPort = pattern.port ?? defaultPortForProtocol(pattern.protocol ?? urlObj.protocol);
|
|
596
|
+
if (expectedPort !== urlObj.port)
|
|
597
|
+
return undefined;
|
|
598
|
+
}
|
|
599
|
+
const prefix = pattern.pathnamePrefix;
|
|
600
|
+
if (prefix === "/") {
|
|
601
|
+
if (urlObj.pathname.startsWith("//"))
|
|
602
|
+
return undefined;
|
|
603
|
+
if (pattern.pathMode === "segments") {
|
|
604
|
+
return isNonEmptyPathSegments(urlObj.pathname)
|
|
605
|
+
? urlObj.pathname
|
|
606
|
+
: undefined;
|
|
607
|
+
}
|
|
608
|
+
return urlObj.pathname;
|
|
609
|
+
}
|
|
610
|
+
if (urlObj.pathname === prefix) {
|
|
611
|
+
return pattern.pathMode === "catch-all-required" ? undefined : "";
|
|
612
|
+
}
|
|
613
|
+
if (urlObj.pathname.startsWith(`${prefix}/`)) {
|
|
614
|
+
const suffix = urlObj.pathname.slice(prefix.length);
|
|
615
|
+
if (suffix.startsWith("//"))
|
|
616
|
+
return undefined;
|
|
617
|
+
if (pattern.pathMode === "segments" &&
|
|
618
|
+
!isNonEmptyPathSegments(suffix)) {
|
|
619
|
+
return undefined;
|
|
620
|
+
}
|
|
621
|
+
return suffix;
|
|
622
|
+
}
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* @param {string} pathname
|
|
627
|
+
* @returns {boolean}
|
|
628
|
+
*/
|
|
629
|
+
function isNonEmptyPathSegments(pathname) {
|
|
630
|
+
return pathname.length > 1 && !pathname.endsWith("/") && !pathname.includes("//");
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* URLPattern treats the default port as empty in URL instances.
|
|
634
|
+
*
|
|
635
|
+
* @param {string} protocol
|
|
636
|
+
* @returns {string}
|
|
637
|
+
*/
|
|
638
|
+
function defaultPortForProtocol(protocol) {
|
|
639
|
+
if (protocol === "http:")
|
|
640
|
+
return "";
|
|
641
|
+
if (protocol === "https:")
|
|
642
|
+
return "";
|
|
643
|
+
return "";
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* @param {FastPathPattern} pattern
|
|
647
|
+
* @param {string} suffix
|
|
648
|
+
* @param {URL} source
|
|
649
|
+
* @returns {URL}
|
|
650
|
+
*/
|
|
651
|
+
function applyFastPathPattern(pattern, suffix, source) {
|
|
652
|
+
const localized = new URL(source.href);
|
|
653
|
+
if (pattern.protocol !== undefined)
|
|
654
|
+
localized.protocol = pattern.protocol;
|
|
655
|
+
if (pattern.hostname !== undefined) {
|
|
656
|
+
localized.hostname = pattern.hostname;
|
|
657
|
+
localized.port =
|
|
658
|
+
pattern.port ?? defaultPortForProtocol(localized.protocol);
|
|
659
|
+
}
|
|
660
|
+
localized.pathname = joinFastPathPrefix(pattern.pathnamePrefix, suffix);
|
|
661
|
+
return localized;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* @param {string} prefix
|
|
665
|
+
* @param {string} suffix
|
|
666
|
+
* @returns {string}
|
|
667
|
+
*/
|
|
668
|
+
function joinFastPathPrefix(prefix, suffix) {
|
|
669
|
+
if (prefix === "/")
|
|
670
|
+
return suffix === "" ? "/" : suffix;
|
|
671
|
+
if (suffix === "")
|
|
672
|
+
return prefix;
|
|
673
|
+
return `${prefix}${suffix.startsWith("/") ? suffix : `/${suffix}`}`;
|
|
674
|
+
}
|
|
317
675
|
//# sourceMappingURL=localize-url.js.map
|