@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.
Files changed (72) hide show
  1. package/dist/bundler-plugins/per-locale-build/vite-environments.js +12 -7
  2. package/dist/bundler-plugins/per-locale-build/vite-environments.js.map +1 -1
  3. package/dist/bundler-plugins/unplugin.js +134 -4
  4. package/dist/bundler-plugins/unplugin.js.map +1 -1
  5. package/dist/bundler-plugins/unplugin.test.js +77 -0
  6. package/dist/bundler-plugins/unplugin.test.js.map +1 -1
  7. package/dist/cli/commands/compile/command.js +8 -1
  8. package/dist/cli/commands/compile/command.js.map +1 -1
  9. package/dist/cli/commands/compile/command.test.js +45 -0
  10. package/dist/cli/commands/compile/command.test.js.map +1 -1
  11. package/dist/compiler/compile.js +0 -15
  12. package/dist/compiler/compile.js.map +1 -1
  13. package/dist/compiler/compile.test.js +0 -52
  14. package/dist/compiler/compile.test.js.map +1 -1
  15. package/dist/compiler/compiler-options.d.ts +8 -0
  16. package/dist/compiler/compiler-options.js.map +1 -1
  17. package/dist/compiler/runtime/create-runtime.d.ts +1 -0
  18. package/dist/compiler/runtime/create-runtime.js +11 -0
  19. package/dist/compiler/runtime/create-runtime.js.map +1 -1
  20. package/dist/compiler/runtime/exec-url-pattern.d.ts +9 -0
  21. package/dist/compiler/runtime/exec-url-pattern.js +31 -0
  22. package/dist/compiler/runtime/exec-url-pattern.js.map +1 -0
  23. package/dist/compiler/runtime/extract-locale-from-url.js +8 -3
  24. package/dist/compiler/runtime/extract-locale-from-url.js.map +1 -1
  25. package/dist/compiler/runtime/generate-static-localized-urls.js +12 -6
  26. package/dist/compiler/runtime/generate-static-localized-urls.js.map +1 -1
  27. package/dist/compiler/runtime/generate-static-localized-urls.test.js +46 -0
  28. package/dist/compiler/runtime/generate-static-localized-urls.test.js.map +1 -1
  29. package/dist/compiler/runtime/localize-href.bench.d.ts +1 -0
  30. package/dist/compiler/runtime/localize-href.bench.js +77 -0
  31. package/dist/compiler/runtime/localize-href.bench.js.map +1 -0
  32. package/dist/compiler/runtime/localize-href.test.js +43 -0
  33. package/dist/compiler/runtime/localize-href.test.js.map +1 -1
  34. package/dist/compiler/runtime/localize-url.d.ts +14 -0
  35. package/dist/compiler/runtime/localize-url.js +376 -18
  36. package/dist/compiler/runtime/localize-url.js.map +1 -1
  37. package/dist/compiler/runtime/localize-url.test.js +163 -0
  38. package/dist/compiler/runtime/localize-url.test.js.map +1 -1
  39. package/dist/compiler/runtime/normalize-trailing-slash.d.ts +10 -0
  40. package/dist/compiler/runtime/normalize-trailing-slash.js +23 -0
  41. package/dist/compiler/runtime/normalize-trailing-slash.js.map +1 -0
  42. package/dist/compiler/runtime/route-strategy.js +4 -2
  43. package/dist/compiler/runtime/route-strategy.js.map +1 -1
  44. package/dist/compiler/runtime/should-redirect.js +4 -1
  45. package/dist/compiler/runtime/should-redirect.js.map +1 -1
  46. package/dist/compiler/runtime/should-redirect.test.js +30 -2
  47. package/dist/compiler/runtime/should-redirect.test.js.map +1 -1
  48. package/dist/compiler/runtime/type.d.ts +1 -0
  49. package/dist/compiler/runtime/type.js.map +1 -1
  50. package/dist/compiler/runtime/variables.d.ts +6 -0
  51. package/dist/compiler/runtime/variables.js +6 -0
  52. package/dist/compiler/runtime/variables.js.map +1 -1
  53. package/dist/compiler/server/middleware.js +6 -2
  54. package/dist/compiler/server/middleware.js.map +1 -1
  55. package/dist/compiler/server/middleware.test.js +34 -0
  56. package/dist/compiler/server/middleware.test.js.map +1 -1
  57. package/dist/index.test.d.ts +1 -0
  58. package/dist/index.test.js +9 -0
  59. package/dist/index.test.js.map +1 -0
  60. package/dist/services/env-variables/index.js +1 -1
  61. package/dist/services/env-variables/index.js.map +1 -1
  62. package/dist/services/file-handling/write-output.bench.d.ts +1 -0
  63. package/dist/services/file-handling/write-output.bench.js +24 -0
  64. package/dist/services/file-handling/write-output.bench.js.map +1 -0
  65. package/dist/services/file-handling/write-output.js +16 -20
  66. package/dist/services/file-handling/write-output.js.map +1 -1
  67. package/dist/services/file-handling/write-output.test.js +27 -0
  68. package/dist/services/file-handling/write-output.test.js.map +1 -1
  69. package/package.json +2 -2
  70. package/dist/services/account/index.d.ts +0 -14
  71. package/dist/services/account/index.js +0 -43
  72. package/dist/services/account/index.js.map +0 -1
@@ -295,6 +295,14 @@ export type CompilerOptions = {
295
295
  * https://paraglidejs.com/strategy#url
296
296
  */
297
297
  urlPatterns?: Runtime["urlPatterns"];
298
+ /**
299
+ * Controls trailing slash canonicalization for localized URLs.
300
+ *
301
+ * Canonicalization happens before URL pattern matching and after URL
302
+ * localization. If omitted, Paraglide keeps its existing trailing slash
303
+ * behavior.
304
+ */
305
+ trailingSlash?: "always" | "never";
298
306
  /**
299
307
  * Whether to include an eslint-disable comment at the top of each .js file.
300
308
  *
@@ -1 +1 @@
1
- {"version":3,"file":"compiler-options.js","sourceRoot":"","sources":["../../src/compiler/compiler-options.ts"],"names":[],"mappings":"AAgCA,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACrC,eAAe,EAAE,iBAAiB;IAClC,aAAa,EAAE,IAAI;IACnB,2BAA2B,EAAE,IAAI;IACjC,kBAAkB,EAAE,IAAI;IACxB,UAAU,EAAE,IAAI;IAChB,kBAAkB,EAAE,KAAK;IACzB,WAAW,EAAE,IAAI;IACjB,wBAAwB,EAAE,KAAK;IAC/B,qCAAqC,EAAE,KAAK;IAC5C,eAAe,EAAE,kBAAkB;IACnC,QAAQ,EAAE,+BAA+B;IACzC,QAAQ,EAAE,CAAC,QAAQ,EAAE,gBAAgB,EAAE,YAAY,CAAC;IACpD,UAAU,EAAE,kBAAkB;IAC9B,YAAY,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG;IAChC,YAAY,EAAE,EAAE;CAC4B,CAAC","sourcesContent":["import type { Runtime } from \"./runtime/type.js\";\n\nexport type RouteStrategy =\n\t| {\n\t\t\t/**\n\t\t\t * URLPattern-compatible matcher.\n\t\t\t */\n\t\t\tmatch: string;\n\t\t\t/**\n\t\t\t * Strategy override for matching requests.\n\t\t\t */\n\t\t\tstrategy: Runtime[\"strategy\"];\n\t\t\t/**\n\t\t\t * Prevent mutually exclusive configuration (`exclude` + `strategy`).\n\t\t\t */\n\t\t\texclude?: never;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * URLPattern-compatible matcher.\n\t\t\t */\n\t\t\tmatch: string;\n\t\t\t/**\n\t\t\t * Exclude matching requests from i18n middleware behavior.\n\t\t\t */\n\t\t\texclude: true;\n\t\t\t/**\n\t\t\t * Prevent mutually exclusive configuration (`exclude` + `strategy`).\n\t\t\t */\n\t\t\tstrategy?: never;\n\t };\n\nexport const defaultCompilerOptions = {\n\toutputStructure: \"message-modules\",\n\temitGitIgnore: true,\n\tincludeEslintDisableComment: true,\n\temitPrettierIgnore: true,\n\temitReadme: true,\n\temitTsDeclarations: false,\n\tcleanOutdir: true,\n\tdisableAsyncLocalStorage: false,\n\texperimentalMiddlewareLocaleSplitting: false,\n\tlocalStorageKey: \"PARAGLIDE_LOCALE\",\n\tisServer: \"typeof window === 'undefined'\",\n\tstrategy: [\"cookie\", \"globalVariable\", \"baseLocale\"],\n\tcookieName: \"PARAGLIDE_LOCALE\",\n\tcookieMaxAge: 60 * 60 * 24 * 400,\n\tcookieDomain: \"\",\n} as const satisfies Partial<CompilerOptions>;\n\nexport type CompilerOptions = {\n\t/**\n\t * The path to the inlang project.\n\t *\n\t * @example\n\t * ```diff\n\t * await compile({\n\t * + project: \"./project.inlang\",\n\t * outdir: \"./src/paraglide\"\n\t * })\n\t * ```\n\t */\n\tproject: string;\n\t/**\n\t * The path to the output directory.\n\t *\n\t * @example\n\t * ```diff\n\t * await compile({\n\t * project: \"./project.inlang\",\n\t * + outdir: \"./src/paraglide\"\n\t * })\n\t * ```\n\t */\n\toutdir: string;\n\t/**\n\t * The strategy to use for getting the locale.\n\t *\n\t * The order of the strategy defines the precedence of matches.\n\t *\n\t * For example, in `['url', 'cookie', 'baseLocale']`, the locale will be\n\t * first tried to be detected in the url, then in a cookie, and finally\n\t * fallback to the base locale.\n\t *\n\t * The default ensures that the browser takes a cookie approach,\n\t * server-side takes the globalVariable (because cookie is unavailable),\n\t * whereas both fallback to the base locale if not available.\n\t *\n\t * Custom strategies with the pattern `custom-[A-Za-z0-9]+` are supported.\n\t *\n\t * @default [\"cookie\", \"globalVariable\", \"baseLocale\"]\n\t */\n\tstrategy?: Runtime[\"strategy\"];\n\t/**\n\t * Route-level strategy overrides.\n\t *\n\t * Routes are matched in declaration order. The first matching rule wins.\n\t * `match` uses URLPattern syntax.\n\t *\n\t * - `strategy`: Override locale resolution for the matched route.\n\t * - `exclude: true`: Skip i18n middleware behavior for the matched route.\n\t *\n\t * @example\n\t * ```ts\n\t * routeStrategies: [\n\t * { match: \"/dashboard/:path(.*)?\", strategy: [\"cookie\", \"baseLocale\"] },\n\t * { match: \"/api/:path(.*)?\", exclude: true }\n\t * ]\n\t * ```\n\t */\n\trouteStrategies?: RouteStrategy[];\n\t/**\n\t * Whether or not to use experimental middleware locale splitting.\n\t *\n\t * ⚠️ This feature is experimental and only works in SSR/SSG environment\n\t * without client-side routing. Do not rely on this feature for production.\n\t *\n\t * This feature is part of the exploration of per locale splitting. The\n\t * issue is ongoing and can be followed here [#88](https://github.com/opral/inlang-paraglide-js/issues/88).\n\t *\n\t * - The client bundle will tree-shake all messages (have close to 0kb JS).\n\t * - The server middleware will inject the used messages into the HTML.\n\t * - The client will re-trieve the messages from the injected HTML.\n\t *\n\t * @default false\n\t */\n\texperimentalMiddlewareLocaleSplitting?: boolean;\n\t/**\n\t * The name of the localStorage key to use for the localStorage strategy.\n\t *\n\t * @default 'PARAGLIDE_LOCALE'\n\t */\n\tlocalStorageKey?: string;\n\t/**\n\t * Tree-shaking flag if the code is running on the server.\n\t *\n\t * Dependent on the bundler, this flag must be adapted to\n\t * enable tree-shaking.\n\t *\n\t * @example\n\t * // vite\n\t * isServer: \"import.meta.env.SSR\"\n\t *\n\t * @default typeof window === \"undefined\"\n\t */\n\tisServer?: string;\n\t/**\n\t * Compile-time locale constant used for per-locale tree-shaking.\n\t *\n\t * This is experimental and opt-in. It should be a JavaScript expression\n\t * (not a quoted literal) that resolves to a locale or `undefined` at build time.\n\t * You can pass a string literal (e.g. `\"de\"`) for programmatic builds, or an\n\t * injected env/define expression for bundler-driven builds.\n\t *\n\t * High-level flow for per-locale builds:\n\t *\n\t * client request (/)\n\t * |\n\t * v\n\t * +----------------------+\n\t * | detect locale |\n\t * | (cookie/Accept-L) |\n\t * | pick build prefix |\n\t * +----------------------+\n\t * | if \"de\"\n\t * v\n\t * serve dist/de/... <----> serve dist/en/...\n\t * (HTML + assets) (HTML + assets)\n\t *\n\t * @see https://github.com/opral/paraglide-js/issues/88#issuecomment-3634754638\n\t *\n\t * @example\n\t * // Vite define\n\t * experimentalStaticLocale: \"typeof __PARAGLIDE_STATIC_LOCALE__ === 'undefined' ? undefined : __PARAGLIDE_STATIC_LOCALE__\"\n\t *\n\t * @example\n\t * // Programmatic compile (literal)\n\t * experimentalStaticLocale: '\"de\"'\n\t */\n\texperimentalStaticLocale?: string;\n\t/**\n\t * The name of the cookie to use for the cookie strategy.\n\t *\n\t * @default 'PARAGLIDE_LOCALE'\n\t */\n\tcookieName?: string;\n\t/**\n\t * The max-age in seconds of the cookie until it expires.\n\t *\n\t * @default 60 * 60 * 24 * 400\n\t */\n\tcookieMaxAge?: number;\n\t/**\n\t * The host to which the cookie will be sent.\n\t * If undefined or empty, the domain attribute is omitted from the cookie, scoping it to the exact current domain only (no subdomains).\n\t * If specified, the cookie will be available to the specified domain and all its subdomains.\n\t *\n\t * Use this when you need cookies to be shared across subdomains (e.g., between `app.example.com` and `api.example.com`).\n\t * The default behavior (no domain) ensures better compatibility with server-side cookies that don't specify a domain attribute.\n\t *\n\t * @example\n\t * ```ts\n\t * // Default: exact domain only (compatible with server-side cookies)\n\t * cookieDomain: undefined // Cookie: \"PARAGLIDE_LOCALE=en; path=/; max-age=34560000\"\n\t *\n\t * // Subdomain sharing: available across all subdomains\n\t * cookieDomain: \"example.com\" // Cookie: \"PARAGLIDE_LOCALE=en; path=/; max-age=34560000; domain=example.com\"\n\t * ```\n\t *\n\t * @default \"\" (no domain attribute, exact domain only)\n\t */\n\tcookieDomain?: string;\n\t/**\n\t * The `additionalFiles` option is an array of paths to additional files that should be copied to the output directory.\n\t *\n\t * @example\n\t *\n\t * ```diff\n\t * await compile({\n\t * project: \"./project.inlang\",\n\t * outdir: \"./src/paraglide\",\n\t * additionalFiles: [\n\t * + \"my-file.js\": \"console.log('hello')\"\n\t * ]\n\t * })\n\t * ```\n\t *\n\t * The output will look like this:\n\t *\n\t * ```diff\n\t * - outdir/\n\t * - messages/\n\t * + - my-file.js\n\t * - messages.js\n\t * - runtime.js\n\t * ```\n\t */\n\tadditionalFiles?: Record<string, string>;\n\t/**\n\t * If `emitPrettierIgnore` is set to `true` a `.prettierignore` file will be emitted in the output directory. Defaults to `true`.\n\t *\n\t * ```diff\n\t * - outdir/\n\t * - messages/\n\t * + - .prettierignore\n\t * - messages.js\n\t * - runtime.js\n\t * ```\n\t *\n\t * @default true\n\t */\n\temitPrettierIgnore?: boolean;\n\t/**\n\t * If `emitReadme` is set to `true` a `README.md` file will be emitted in the output directory. Defaults to `true`.\n\t *\n\t * The README helps LLMs understand the compiled output.\n\t * See https://llmstxt.org/ for format guidelines.\n\t *\n\t * ```diff\n\t * - outdir/\n\t * - messages/\n\t * + - README.md\n\t * - messages.js\n\t * - runtime.js\n\t * ```\n\t *\n\t * @default true\n\t */\n\temitReadme?: boolean;\n\t/**\n\t * Emit `.d.ts` files for the generated output using the TypeScript compiler.\n\t *\n\t * Useful when `allowJs: true` cannot be set in your `tsconfig.json`\n\t * (e.g., due to project constraints or conflicting compiler options), or when\n\t * an editor language server caches stale JSDoc types for generated messages.\n\t *\n\t * Requires `typescript` to be resolvable in your toolchain.\n\t *\n\t * **Note:** Enabling this option reduces compiler speed because TypeScript\n\t * needs to generate declaration files for all output modules.\n\t *\n\t * **Note:** With TypeScript 5/6 the declarations are generated in-process.\n\t * TypeScript 7+ no longer provides the compiler API, so its `tsc` CLI is\n\t * invoked in a child process instead; the emitted declarations are\n\t * semantically equivalent but differ cosmetically between the two.\n\t *\n\t * @example\n\t * ```ts\n\t * await compile({\n\t * project: \"./project.inlang\",\n\t * outdir: \"./src/paraglide\",\n\t * emitTsDeclarations: true,\n\t * });\n\t * ```\n\t *\n\t * @default false\n\t */\n\temitTsDeclarations?: boolean;\n\t/**\n\t * https://paraglidejs.com/strategy#url\n\t */\n\turlPatterns?: Runtime[\"urlPatterns\"];\n\t/**\n\t * Whether to include an eslint-disable comment at the top of each .js file.\n\t *\n\t * @default true\n\t */\n\tincludeEslintDisableComment?: boolean;\n\t/**\n\t * Replaces AsyncLocalStorage with a synchronous implementation.\n\t *\n\t * Leave AsyncLocalStorage enabled by default. This option is a\n\t * compatibility fallback for runtimes that do not provide\n\t * AsyncLocalStorage or `node:async_hooks`.\n\t *\n\t * ⚠️ WARNING: Only use this option when your runtime also guarantees\n\t * request isolation. Disabling AsyncLocalStorage in multi-request\n\t * server environments risks cross-request pollution where state from\n\t * one request could leak into another concurrent request.\n\t */\n\tdisableAsyncLocalStorage?: boolean;\n\t/**\n\t * If `emitGitIgnore` is set to `true` a `.gitignore` file will be emitted in the output directory. Defaults to `true`.\n\t *\n\t * ```diff\n\t * - outdir/\n\t * - messages/\n\t * + - .gitignore\n\t * - messages.js\n\t * - runtime.js\n\t * ```\n\t *\n\t * @default true\n\t */\n\temitGitIgnore?: boolean;\n\t/**\n\t * The `outputStructure` defines how modules are structured in the output.\n\t *\n\t * - `message-modules` - Each module is a message. This is the default.\n\t * - `locale-modules` - Each module is a locale.\n\t *\n\t * It is recommended to use `locale-modules` for development and `message-modules` for production.\n\t * Bundlers speed up the dev mode by bypassing bundling which can lead to many http requests\n\t * during the dev mode with `message-modules`. See https://github.com/opral/inlang-paraglide-js/issues/486.\n\t *\n\t * **`message-modules`**\n\t *\n\t * Messages have their own module which eases tree-shaking for bundlers.\n\t *\n\t * ```diff\n\t * - outdir/\n\t * - messages/\n\t * + - blue_elephant_tree/\n\t * + - index.js\n\t * + - en.js\n\t * + - fr.js\n\t * + - sky_phone_bottle/\n\t * + - index.js\n\t * + - en.js\n\t * + - fr.js\n\t * - ...\n\t * - messages.js\n\t * - runtime.js\n\t * ```\n\t *\n\t * **`locale-modules`**\n\t *\n\t * Messages are bundled in a per locale module. Bundlers often struggle with tree-shaking this structure,\n\t * which can lead to more inefficient tree-shaking and larger bundle sizes compared to `message-modules`.\n\t *\n\t * The benefit are substantially fewer files which is needed in large projects.\n\t *\n\t * ```diff\n\t * - outdir/\n\t * - messages/\n\t * + - de.js\n\t * + - en.js\n\t * + - fr.js\n\t * - ...\n\t * - messages.js\n\t * - runtime.js\n\t * ```\n\t *\n\t * @default \"message-modules\"\n\t */\n\toutputStructure?: \"locale-modules\" | \"message-modules\";\n\t/**\n\t * Whether to clean the output directory before writing the new files.\n\t *\n\t * @default true\n\t */\n\tcleanOutdir?: boolean;\n\n\t/**\n\t * The file system to use. Defaults to `await import('node:fs')`.\n\t *\n\t * Useful for testing the paraglide compiler by mocking the fs.\n\t */\n\tfs?: any;\n};\n"]}
1
+ {"version":3,"file":"compiler-options.js","sourceRoot":"","sources":["../../src/compiler/compiler-options.ts"],"names":[],"mappings":"AAgCA,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACrC,eAAe,EAAE,iBAAiB;IAClC,aAAa,EAAE,IAAI;IACnB,2BAA2B,EAAE,IAAI;IACjC,kBAAkB,EAAE,IAAI;IACxB,UAAU,EAAE,IAAI;IAChB,kBAAkB,EAAE,KAAK;IACzB,WAAW,EAAE,IAAI;IACjB,wBAAwB,EAAE,KAAK;IAC/B,qCAAqC,EAAE,KAAK;IAC5C,eAAe,EAAE,kBAAkB;IACnC,QAAQ,EAAE,+BAA+B;IACzC,QAAQ,EAAE,CAAC,QAAQ,EAAE,gBAAgB,EAAE,YAAY,CAAC;IACpD,UAAU,EAAE,kBAAkB;IAC9B,YAAY,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG;IAChC,YAAY,EAAE,EAAE;CAC4B,CAAC","sourcesContent":["import type { Runtime } from \"./runtime/type.js\";\n\nexport type RouteStrategy =\n\t| {\n\t\t\t/**\n\t\t\t * URLPattern-compatible matcher.\n\t\t\t */\n\t\t\tmatch: string;\n\t\t\t/**\n\t\t\t * Strategy override for matching requests.\n\t\t\t */\n\t\t\tstrategy: Runtime[\"strategy\"];\n\t\t\t/**\n\t\t\t * Prevent mutually exclusive configuration (`exclude` + `strategy`).\n\t\t\t */\n\t\t\texclude?: never;\n\t }\n\t| {\n\t\t\t/**\n\t\t\t * URLPattern-compatible matcher.\n\t\t\t */\n\t\t\tmatch: string;\n\t\t\t/**\n\t\t\t * Exclude matching requests from i18n middleware behavior.\n\t\t\t */\n\t\t\texclude: true;\n\t\t\t/**\n\t\t\t * Prevent mutually exclusive configuration (`exclude` + `strategy`).\n\t\t\t */\n\t\t\tstrategy?: never;\n\t };\n\nexport const defaultCompilerOptions = {\n\toutputStructure: \"message-modules\",\n\temitGitIgnore: true,\n\tincludeEslintDisableComment: true,\n\temitPrettierIgnore: true,\n\temitReadme: true,\n\temitTsDeclarations: false,\n\tcleanOutdir: true,\n\tdisableAsyncLocalStorage: false,\n\texperimentalMiddlewareLocaleSplitting: false,\n\tlocalStorageKey: \"PARAGLIDE_LOCALE\",\n\tisServer: \"typeof window === 'undefined'\",\n\tstrategy: [\"cookie\", \"globalVariable\", \"baseLocale\"],\n\tcookieName: \"PARAGLIDE_LOCALE\",\n\tcookieMaxAge: 60 * 60 * 24 * 400,\n\tcookieDomain: \"\",\n} as const satisfies Partial<CompilerOptions>;\n\nexport type CompilerOptions = {\n\t/**\n\t * The path to the inlang project.\n\t *\n\t * @example\n\t * ```diff\n\t * await compile({\n\t * + project: \"./project.inlang\",\n\t * outdir: \"./src/paraglide\"\n\t * })\n\t * ```\n\t */\n\tproject: string;\n\t/**\n\t * The path to the output directory.\n\t *\n\t * @example\n\t * ```diff\n\t * await compile({\n\t * project: \"./project.inlang\",\n\t * + outdir: \"./src/paraglide\"\n\t * })\n\t * ```\n\t */\n\toutdir: string;\n\t/**\n\t * The strategy to use for getting the locale.\n\t *\n\t * The order of the strategy defines the precedence of matches.\n\t *\n\t * For example, in `['url', 'cookie', 'baseLocale']`, the locale will be\n\t * first tried to be detected in the url, then in a cookie, and finally\n\t * fallback to the base locale.\n\t *\n\t * The default ensures that the browser takes a cookie approach,\n\t * server-side takes the globalVariable (because cookie is unavailable),\n\t * whereas both fallback to the base locale if not available.\n\t *\n\t * Custom strategies with the pattern `custom-[A-Za-z0-9]+` are supported.\n\t *\n\t * @default [\"cookie\", \"globalVariable\", \"baseLocale\"]\n\t */\n\tstrategy?: Runtime[\"strategy\"];\n\t/**\n\t * Route-level strategy overrides.\n\t *\n\t * Routes are matched in declaration order. The first matching rule wins.\n\t * `match` uses URLPattern syntax.\n\t *\n\t * - `strategy`: Override locale resolution for the matched route.\n\t * - `exclude: true`: Skip i18n middleware behavior for the matched route.\n\t *\n\t * @example\n\t * ```ts\n\t * routeStrategies: [\n\t * { match: \"/dashboard/:path(.*)?\", strategy: [\"cookie\", \"baseLocale\"] },\n\t * { match: \"/api/:path(.*)?\", exclude: true }\n\t * ]\n\t * ```\n\t */\n\trouteStrategies?: RouteStrategy[];\n\t/**\n\t * Whether or not to use experimental middleware locale splitting.\n\t *\n\t * ⚠️ This feature is experimental and only works in SSR/SSG environment\n\t * without client-side routing. Do not rely on this feature for production.\n\t *\n\t * This feature is part of the exploration of per locale splitting. The\n\t * issue is ongoing and can be followed here [#88](https://github.com/opral/inlang-paraglide-js/issues/88).\n\t *\n\t * - The client bundle will tree-shake all messages (have close to 0kb JS).\n\t * - The server middleware will inject the used messages into the HTML.\n\t * - The client will re-trieve the messages from the injected HTML.\n\t *\n\t * @default false\n\t */\n\texperimentalMiddlewareLocaleSplitting?: boolean;\n\t/**\n\t * The name of the localStorage key to use for the localStorage strategy.\n\t *\n\t * @default 'PARAGLIDE_LOCALE'\n\t */\n\tlocalStorageKey?: string;\n\t/**\n\t * Tree-shaking flag if the code is running on the server.\n\t *\n\t * Dependent on the bundler, this flag must be adapted to\n\t * enable tree-shaking.\n\t *\n\t * @example\n\t * // vite\n\t * isServer: \"import.meta.env.SSR\"\n\t *\n\t * @default typeof window === \"undefined\"\n\t */\n\tisServer?: string;\n\t/**\n\t * Compile-time locale constant used for per-locale tree-shaking.\n\t *\n\t * This is experimental and opt-in. It should be a JavaScript expression\n\t * (not a quoted literal) that resolves to a locale or `undefined` at build time.\n\t * You can pass a string literal (e.g. `\"de\"`) for programmatic builds, or an\n\t * injected env/define expression for bundler-driven builds.\n\t *\n\t * High-level flow for per-locale builds:\n\t *\n\t * client request (/)\n\t * |\n\t * v\n\t * +----------------------+\n\t * | detect locale |\n\t * | (cookie/Accept-L) |\n\t * | pick build prefix |\n\t * +----------------------+\n\t * | if \"de\"\n\t * v\n\t * serve dist/de/... <----> serve dist/en/...\n\t * (HTML + assets) (HTML + assets)\n\t *\n\t * @see https://github.com/opral/paraglide-js/issues/88#issuecomment-3634754638\n\t *\n\t * @example\n\t * // Vite define\n\t * experimentalStaticLocale: \"typeof __PARAGLIDE_STATIC_LOCALE__ === 'undefined' ? undefined : __PARAGLIDE_STATIC_LOCALE__\"\n\t *\n\t * @example\n\t * // Programmatic compile (literal)\n\t * experimentalStaticLocale: '\"de\"'\n\t */\n\texperimentalStaticLocale?: string;\n\t/**\n\t * The name of the cookie to use for the cookie strategy.\n\t *\n\t * @default 'PARAGLIDE_LOCALE'\n\t */\n\tcookieName?: string;\n\t/**\n\t * The max-age in seconds of the cookie until it expires.\n\t *\n\t * @default 60 * 60 * 24 * 400\n\t */\n\tcookieMaxAge?: number;\n\t/**\n\t * The host to which the cookie will be sent.\n\t * If undefined or empty, the domain attribute is omitted from the cookie, scoping it to the exact current domain only (no subdomains).\n\t * If specified, the cookie will be available to the specified domain and all its subdomains.\n\t *\n\t * Use this when you need cookies to be shared across subdomains (e.g., between `app.example.com` and `api.example.com`).\n\t * The default behavior (no domain) ensures better compatibility with server-side cookies that don't specify a domain attribute.\n\t *\n\t * @example\n\t * ```ts\n\t * // Default: exact domain only (compatible with server-side cookies)\n\t * cookieDomain: undefined // Cookie: \"PARAGLIDE_LOCALE=en; path=/; max-age=34560000\"\n\t *\n\t * // Subdomain sharing: available across all subdomains\n\t * cookieDomain: \"example.com\" // Cookie: \"PARAGLIDE_LOCALE=en; path=/; max-age=34560000; domain=example.com\"\n\t * ```\n\t *\n\t * @default \"\" (no domain attribute, exact domain only)\n\t */\n\tcookieDomain?: string;\n\t/**\n\t * The `additionalFiles` option is an array of paths to additional files that should be copied to the output directory.\n\t *\n\t * @example\n\t *\n\t * ```diff\n\t * await compile({\n\t * project: \"./project.inlang\",\n\t * outdir: \"./src/paraglide\",\n\t * additionalFiles: [\n\t * + \"my-file.js\": \"console.log('hello')\"\n\t * ]\n\t * })\n\t * ```\n\t *\n\t * The output will look like this:\n\t *\n\t * ```diff\n\t * - outdir/\n\t * - messages/\n\t * + - my-file.js\n\t * - messages.js\n\t * - runtime.js\n\t * ```\n\t */\n\tadditionalFiles?: Record<string, string>;\n\t/**\n\t * If `emitPrettierIgnore` is set to `true` a `.prettierignore` file will be emitted in the output directory. Defaults to `true`.\n\t *\n\t * ```diff\n\t * - outdir/\n\t * - messages/\n\t * + - .prettierignore\n\t * - messages.js\n\t * - runtime.js\n\t * ```\n\t *\n\t * @default true\n\t */\n\temitPrettierIgnore?: boolean;\n\t/**\n\t * If `emitReadme` is set to `true` a `README.md` file will be emitted in the output directory. Defaults to `true`.\n\t *\n\t * The README helps LLMs understand the compiled output.\n\t * See https://llmstxt.org/ for format guidelines.\n\t *\n\t * ```diff\n\t * - outdir/\n\t * - messages/\n\t * + - README.md\n\t * - messages.js\n\t * - runtime.js\n\t * ```\n\t *\n\t * @default true\n\t */\n\temitReadme?: boolean;\n\t/**\n\t * Emit `.d.ts` files for the generated output using the TypeScript compiler.\n\t *\n\t * Useful when `allowJs: true` cannot be set in your `tsconfig.json`\n\t * (e.g., due to project constraints or conflicting compiler options), or when\n\t * an editor language server caches stale JSDoc types for generated messages.\n\t *\n\t * Requires `typescript` to be resolvable in your toolchain.\n\t *\n\t * **Note:** Enabling this option reduces compiler speed because TypeScript\n\t * needs to generate declaration files for all output modules.\n\t *\n\t * **Note:** With TypeScript 5/6 the declarations are generated in-process.\n\t * TypeScript 7+ no longer provides the compiler API, so its `tsc` CLI is\n\t * invoked in a child process instead; the emitted declarations are\n\t * semantically equivalent but differ cosmetically between the two.\n\t *\n\t * @example\n\t * ```ts\n\t * await compile({\n\t * project: \"./project.inlang\",\n\t * outdir: \"./src/paraglide\",\n\t * emitTsDeclarations: true,\n\t * });\n\t * ```\n\t *\n\t * @default false\n\t */\n\temitTsDeclarations?: boolean;\n\t/**\n\t * https://paraglidejs.com/strategy#url\n\t */\n\turlPatterns?: Runtime[\"urlPatterns\"];\n\t/**\n\t * Controls trailing slash canonicalization for localized URLs.\n\t *\n\t * Canonicalization happens before URL pattern matching and after URL\n\t * localization. If omitted, Paraglide keeps its existing trailing slash\n\t * behavior.\n\t */\n\ttrailingSlash?: \"always\" | \"never\";\n\t/**\n\t * Whether to include an eslint-disable comment at the top of each .js file.\n\t *\n\t * @default true\n\t */\n\tincludeEslintDisableComment?: boolean;\n\t/**\n\t * Replaces AsyncLocalStorage with a synchronous implementation.\n\t *\n\t * Leave AsyncLocalStorage enabled by default. This option is a\n\t * compatibility fallback for runtimes that do not provide\n\t * AsyncLocalStorage or `node:async_hooks`.\n\t *\n\t * ⚠️ WARNING: Only use this option when your runtime also guarantees\n\t * request isolation. Disabling AsyncLocalStorage in multi-request\n\t * server environments risks cross-request pollution where state from\n\t * one request could leak into another concurrent request.\n\t */\n\tdisableAsyncLocalStorage?: boolean;\n\t/**\n\t * If `emitGitIgnore` is set to `true` a `.gitignore` file will be emitted in the output directory. Defaults to `true`.\n\t *\n\t * ```diff\n\t * - outdir/\n\t * - messages/\n\t * + - .gitignore\n\t * - messages.js\n\t * - runtime.js\n\t * ```\n\t *\n\t * @default true\n\t */\n\temitGitIgnore?: boolean;\n\t/**\n\t * The `outputStructure` defines how modules are structured in the output.\n\t *\n\t * - `message-modules` - Each module is a message. This is the default.\n\t * - `locale-modules` - Each module is a locale.\n\t *\n\t * It is recommended to use `locale-modules` for development and `message-modules` for production.\n\t * Bundlers speed up the dev mode by bypassing bundling which can lead to many http requests\n\t * during the dev mode with `message-modules`. See https://github.com/opral/inlang-paraglide-js/issues/486.\n\t *\n\t * **`message-modules`**\n\t *\n\t * Messages have their own module which eases tree-shaking for bundlers.\n\t *\n\t * ```diff\n\t * - outdir/\n\t * - messages/\n\t * + - blue_elephant_tree/\n\t * + - index.js\n\t * + - en.js\n\t * + - fr.js\n\t * + - sky_phone_bottle/\n\t * + - index.js\n\t * + - en.js\n\t * + - fr.js\n\t * - ...\n\t * - messages.js\n\t * - runtime.js\n\t * ```\n\t *\n\t * **`locale-modules`**\n\t *\n\t * Messages are bundled in a per locale module. Bundlers often struggle with tree-shaking this structure,\n\t * which can lead to more inefficient tree-shaking and larger bundle sizes compared to `message-modules`.\n\t *\n\t * The benefit are substantially fewer files which is needed in large projects.\n\t *\n\t * ```diff\n\t * - outdir/\n\t * - messages/\n\t * + - de.js\n\t * + - en.js\n\t * + - fr.js\n\t * - ...\n\t * - messages.js\n\t * - runtime.js\n\t * ```\n\t *\n\t * @default \"message-modules\"\n\t */\n\toutputStructure?: \"locale-modules\" | \"message-modules\";\n\t/**\n\t * Whether to clean the output directory before writing the new files.\n\t *\n\t * @default true\n\t */\n\tcleanOutdir?: boolean;\n\n\t/**\n\t * The file system to use. Defaults to `await import('node:fs')`.\n\t *\n\t * Useful for testing the paraglide compiler by mocking the fs.\n\t */\n\tfs?: any;\n};\n"]}
@@ -12,6 +12,7 @@ export declare function createRuntimeFile(args: {
12
12
  cookieMaxAge: NonNullable<CompilerOptions["cookieMaxAge"]>;
13
13
  cookieDomain: CompilerOptions["cookieDomain"];
14
14
  urlPatterns?: CompilerOptions["urlPatterns"];
15
+ trailingSlash?: CompilerOptions["trailingSlash"];
15
16
  experimentalMiddlewareLocaleSplitting: CompilerOptions["experimentalMiddlewareLocaleSplitting"];
16
17
  isServer: CompilerOptions["isServer"];
17
18
  experimentalStaticLocale?: CompilerOptions["experimentalStaticLocale"];
@@ -58,6 +58,9 @@ ${injectCode("./variables.js")
58
58
  .replace(`export const TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED = false;`, `const TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED = ${allUsedStrategies.has("globalVariable")};`)
59
59
  .replace(`export const TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED = false;`, `const TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED = ${allUsedStrategies.has("preferredLanguage")};`)
60
60
  .replace(`export const urlPatterns = [];`, `export const urlPatterns = ${JSON.stringify(urlPatterns, null, 2)};`)
61
+ .replace(`export const trailingSlash = undefined;`, `export const trailingSlash = ${args.compilerOptions.trailingSlash === undefined
62
+ ? "undefined"
63
+ : JSON.stringify(args.compilerOptions.trailingSlash)};`)
61
64
  .replace(`export const disableAsyncLocalStorage = false;`, `export const disableAsyncLocalStorage = ${args.compilerOptions.disableAsyncLocalStorage};`)
62
65
  .replace(`export const TREE_SHAKE_DEFAULT_URL_PATTERN_USED = false;`, `const TREE_SHAKE_DEFAULT_URL_PATTERN_USED = ${defaultUrlPatternUsed};`)
63
66
  .replace(`export const experimentalMiddlewareLocaleSplitting = false;`, `export const experimentalMiddlewareLocaleSplitting = ${args.compilerOptions.experimentalMiddlewareLocaleSplitting};`)
@@ -86,6 +89,14 @@ ${injectCode("./get-url-origin.js")}
86
89
 
87
90
  ${injectCode("./check-locale.js")}
88
91
 
92
+ ${injectCode("./normalize-trailing-slash.js", {
93
+ privateExports: ["normalizeTrailingSlash"],
94
+ })}
95
+
96
+ ${injectCode("./exec-url-pattern.js", {
97
+ privateExports: ["execUrlPattern"],
98
+ })}
99
+
89
100
  ${injectCode("./extract-locale-from-request.js")}
90
101
 
91
102
  ${injectCode("./extract-locale-from-request-async.js")}
@@ -1 +1 @@
1
- {"version":3,"file":"create-runtime.js","sourceRoot":"","sources":["../../../src/compiler/runtime/create-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"node:fs\";\nimport type { CompilerOptions } from \"../compiler-options.js\";\nimport { perLocaleBuildStaticLocaleExpression } from \"../per-locale-build.js\";\n\n/**\n * Returns the code for the `runtime.js` module\n */\nexport function createRuntimeFile(args: {\n\tbaseLocale: string;\n\tlocales: string[];\n\tcompilerOptions: {\n\t\tstrategy: NonNullable<CompilerOptions[\"strategy\"]>;\n\t\trouteStrategies?: CompilerOptions[\"routeStrategies\"];\n\t\tcookieName: NonNullable<CompilerOptions[\"cookieName\"]>;\n\t\tcookieMaxAge: NonNullable<CompilerOptions[\"cookieMaxAge\"]>;\n\t\tcookieDomain: CompilerOptions[\"cookieDomain\"];\n\t\turlPatterns?: CompilerOptions[\"urlPatterns\"];\n\t\texperimentalMiddlewareLocaleSplitting: CompilerOptions[\"experimentalMiddlewareLocaleSplitting\"];\n\t\tisServer: CompilerOptions[\"isServer\"];\n\t\texperimentalStaticLocale?: CompilerOptions[\"experimentalStaticLocale\"];\n\t\tlocalStorageKey: CompilerOptions[\"localStorageKey\"];\n\t\tdisableAsyncLocalStorage: NonNullable<\n\t\t\tCompilerOptions[\"disableAsyncLocalStorage\"]\n\t\t>;\n\t};\n}): string {\n\tconst urlPatterns = args.compilerOptions.urlPatterns ?? [];\n\tconst routeStrategies = args.compilerOptions.routeStrategies ?? [];\n\n\tlet defaultUrlPatternUsed = false;\n\n\t// add default urlPatterns for a good out of the box experience\n\tif (args.compilerOptions.urlPatterns === undefined) {\n\t\tdefaultUrlPatternUsed = true;\n\t\turlPatterns.push({\n\t\t\tpattern: `:protocol://:domain(.*)::port?/:path(.*)?`,\n\t\t\tlocalized: [],\n\t\t});\n\t\tfor (const locale of args.locales) {\n\t\t\tif (locale === args.baseLocale) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\turlPatterns[0]?.localized.push([\n\t\t\t\tlocale,\n\t\t\t\t`:protocol://:domain(.*)::port?/${locale}/:path(.*)?`,\n\t\t\t]);\n\t\t}\n\t\turlPatterns[0]?.localized.push([\n\t\t\targs.baseLocale,\n\t\t\t`:protocol://:domain(.*)::port?/:path(.*)?`,\n\t\t]);\n\t}\n\tconst strategiesUsedByRoutes = routeStrategies.flatMap((routeStrategy) =>\n\t\t\"strategy\" in routeStrategy ? routeStrategy.strategy : []\n\t);\n\tconst allUsedStrategies = new Set([\n\t\t...args.compilerOptions.strategy,\n\t\t...strategiesUsedByRoutes,\n\t]);\n\tconst needsUrlPatternPolyfill =\n\t\t!defaultUrlPatternUsed || routeStrategies.length > 0;\n\n\t// verify that urlPatterns' locales are valid\n\tfor (const urlPattern of urlPatterns) {\n\t\tfor (const [locale] of urlPattern.localized) {\n\t\t\tif (!args.locales.includes(locale)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid locale \"${locale}\" in urlPatterns. It must be one of the locales defined in the \"locales\" array.`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst code = `\n${needsUrlPatternPolyfill ? `import \"@inlang/paraglide-js/urlpattern-polyfill\";` : \"/** @type {any} */\\nconst URLPattern = {}\"}\n\n${injectCode(\"./variables.js\")\n\t.replace(\n\t\t`export const baseLocale = \"en\";`,\n\t\t`export const baseLocale = \"${args.baseLocale}\";`\n\t)\n\t.replace(\n\t\t`export const locales = /** @type {readonly string[]} */ ([\"en\", \"de\"]);`,\n\t\t`export const locales = /** @type {const} */ (${JSON.stringify(args.locales)});`\n\t)\n\t.replace(\n\t\t`export const strategy = [\"globalVariable\"];`,\n\t\t`export const strategy = ${JSON.stringify(args.compilerOptions.strategy, null, 2)};`\n\t)\n\t.replace(`<cookie-name>`, `${args.compilerOptions.cookieName}`)\n\t.replace(`60 * 60 * 24 * 400`, `${args.compilerOptions.cookieMaxAge}`)\n\t.replace(`<cookie-domain>`, `${args.compilerOptions.cookieDomain}`)\n\t.replace(\n\t\t`export const routeStrategies = [];`,\n\t\t`export const routeStrategies = ${JSON.stringify(routeStrategies, null, 2)};`\n\t)\n\t.replace(\n\t\t`export const TREE_SHAKE_COOKIE_STRATEGY_USED = false;`,\n\t\t`const TREE_SHAKE_COOKIE_STRATEGY_USED = ${allUsedStrategies.has(\"cookie\")};`\n\t)\n\t.replace(\n\t\t`export const TREE_SHAKE_URL_STRATEGY_USED = false;`,\n\t\t`const TREE_SHAKE_URL_STRATEGY_USED = ${allUsedStrategies.has(\"url\")};`\n\t)\n\t.replace(\n\t\t`export const TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED = false;`,\n\t\t`const TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED = ${allUsedStrategies.has(\"globalVariable\")};`\n\t)\n\t.replace(\n\t\t`export const TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED = false;`,\n\t\t`const TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED = ${allUsedStrategies.has(\"preferredLanguage\")};`\n\t)\n\t.replace(\n\t\t`export const urlPatterns = [];`,\n\t\t`export const urlPatterns = ${JSON.stringify(urlPatterns, null, 2)};`\n\t)\n\t.replace(\n\t\t`export const disableAsyncLocalStorage = false;`,\n\t\t`export const disableAsyncLocalStorage = ${args.compilerOptions.disableAsyncLocalStorage};`\n\t)\n\t.replace(\n\t\t`export const TREE_SHAKE_DEFAULT_URL_PATTERN_USED = false;`,\n\t\t`const TREE_SHAKE_DEFAULT_URL_PATTERN_USED = ${defaultUrlPatternUsed};`\n\t)\n\t.replace(\n\t\t`export const experimentalMiddlewareLocaleSplitting = false;`,\n\t\t`export const experimentalMiddlewareLocaleSplitting = ${args.compilerOptions.experimentalMiddlewareLocaleSplitting};`\n\t)\n\t.replace(\n\t\t`export const isServer = typeof window === \"undefined\";`,\n\t\t`export const isServer = ${args.compilerOptions.isServer};`\n\t)\n\t.replace(\n\t\t`export const experimentalStaticLocale = undefined;`,\n\t\targs.compilerOptions.experimentalStaticLocale\n\t\t\t? `export const experimentalStaticLocale = ${\n\t\t\t\t\targs.compilerOptions.experimentalStaticLocale ===\n\t\t\t\t\tperLocaleBuildStaticLocaleExpression\n\t\t\t\t\t\t? args.compilerOptions.experimentalStaticLocale\n\t\t\t\t\t\t: `assertIsLocale(${args.compilerOptions.experimentalStaticLocale})`\n\t\t\t\t};`\n\t\t\t: `export const experimentalStaticLocale = undefined;`\n\t)\n\t.replace(\n\t\t`export const localStorageKey = \"PARAGLIDE_LOCALE\";`,\n\t\t`export const localStorageKey = \"${args.compilerOptions.localStorageKey}\";`\n\t)\n\t.replace(\n\t\t`export const TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED = false;`,\n\t\t`const TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED = ${allUsedStrategies.has(\"localStorage\")};`\n\t)}\n\n/** @type {any} */ (globalThis).__paraglide =\n\t/** @type {any} */ (globalThis).__paraglide ?? {};\n/** @type {any} */ (globalThis).__paraglide.ssr =\n\t/** @type {any} */ (globalThis).__paraglide.ssr ?? {};\n\n${injectCode(\"./get-locale.js\")}\n\n${injectCode(\"./get-text-direction.js\")}\n\n${injectCode(\"./set-locale.js\")}\n\n${injectCode(\"./get-url-origin.js\")}\n\n${injectCode(\"./check-locale.js\")}\n\n${injectCode(\"./extract-locale-from-request.js\")}\n\n${injectCode(\"./extract-locale-from-request-async.js\")}\n\n${injectCode(\"./extract-locale-from-cookie.js\", {\n\tprivateExports: [\"clearLocaleCookieCache\"],\n})}\n\n${injectCode(\"./extract-locale-from-header.js\")}\n\n${injectCode(\"./extract-locale-from-navigator.js\")}\n\n${injectCode(\"./extract-locale-from-url.js\")}\n\n${injectCode(\"./localize-url.js\")}\n\n${injectCode(\"./route-strategy.js\")}\n\n${injectCode(\"./should-redirect.js\")}\n\n${injectCode(\"./localize-href.js\")}\n\n${injectCode(\"./track-message-call.js\")}\n\n${injectCode(\"./generate-static-localized-urls.js\")}\n\n${injectCode(\"./strategy.js\")}\n\n${injectCode(\"./type-definitions.js\").replace(\n\t`@typedef {string} Locale`,\n\t`@typedef {typeof locales[number]} Locale`\n)}\n`;\n\n\treturn code;\n}\n\n/**\n * Load a file from the current directory.\n *\n * Prunes the imports on top of the file as the runtime is\n * self-contained.\n *\n * @param {string} path\n * @param {{ privateExports?: string[] }} [options]\n * @returns {string}\n */\nfunction injectCode(\n\tpath: string,\n\toptions?: { privateExports?: string[] }\n): string {\n\tconst code = fs.readFileSync(new URL(path, import.meta.url), \"utf-8\");\n\t// Regex to match single-line and multi-line imports\n\tconst importRegex = /import\\s+[\\s\\S]*?from\\s+['\"][^'\"]+['\"]\\s*;?/g;\n\tconst sourceMapRegex = /\\/\\/# sourceMappingURL=.*$/gm;\n\tconst blockSourceMapRegex = /\\/\\*# sourceMappingURL=.*?\\*\\//g;\n\tlet injected = code\n\t\t.replace(importRegex, \"\")\n\t\t.replace(sourceMapRegex, \"\")\n\t\t.replace(blockSourceMapRegex, \"\")\n\t\t.trim();\n\tfor (const exportName of options?.privateExports ?? []) {\n\t\tinjected = injected.replace(\n\t\t\tnew RegExp(`\\\\bexport\\\\s+function\\\\s+${exportName}\\\\b`, \"g\"),\n\t\t\t`function ${exportName}`\n\t\t);\n\t}\n\treturn injected;\n}\n"]}
1
+ {"version":3,"file":"create-runtime.js","sourceRoot":"","sources":["../../../src/compiler/runtime/create-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"node:fs\";\nimport type { CompilerOptions } from \"../compiler-options.js\";\nimport { perLocaleBuildStaticLocaleExpression } from \"../per-locale-build.js\";\n\n/**\n * Returns the code for the `runtime.js` module\n */\nexport function createRuntimeFile(args: {\n\tbaseLocale: string;\n\tlocales: string[];\n\tcompilerOptions: {\n\t\tstrategy: NonNullable<CompilerOptions[\"strategy\"]>;\n\t\trouteStrategies?: CompilerOptions[\"routeStrategies\"];\n\t\tcookieName: NonNullable<CompilerOptions[\"cookieName\"]>;\n\t\tcookieMaxAge: NonNullable<CompilerOptions[\"cookieMaxAge\"]>;\n\t\tcookieDomain: CompilerOptions[\"cookieDomain\"];\n\t\turlPatterns?: CompilerOptions[\"urlPatterns\"];\n\t\ttrailingSlash?: CompilerOptions[\"trailingSlash\"];\n\t\texperimentalMiddlewareLocaleSplitting: CompilerOptions[\"experimentalMiddlewareLocaleSplitting\"];\n\t\tisServer: CompilerOptions[\"isServer\"];\n\t\texperimentalStaticLocale?: CompilerOptions[\"experimentalStaticLocale\"];\n\t\tlocalStorageKey: CompilerOptions[\"localStorageKey\"];\n\t\tdisableAsyncLocalStorage: NonNullable<\n\t\t\tCompilerOptions[\"disableAsyncLocalStorage\"]\n\t\t>;\n\t};\n}): string {\n\tconst urlPatterns = args.compilerOptions.urlPatterns ?? [];\n\tconst routeStrategies = args.compilerOptions.routeStrategies ?? [];\n\n\tlet defaultUrlPatternUsed = false;\n\n\t// add default urlPatterns for a good out of the box experience\n\tif (args.compilerOptions.urlPatterns === undefined) {\n\t\tdefaultUrlPatternUsed = true;\n\t\turlPatterns.push({\n\t\t\tpattern: `:protocol://:domain(.*)::port?/:path(.*)?`,\n\t\t\tlocalized: [],\n\t\t});\n\t\tfor (const locale of args.locales) {\n\t\t\tif (locale === args.baseLocale) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\turlPatterns[0]?.localized.push([\n\t\t\t\tlocale,\n\t\t\t\t`:protocol://:domain(.*)::port?/${locale}/:path(.*)?`,\n\t\t\t]);\n\t\t}\n\t\turlPatterns[0]?.localized.push([\n\t\t\targs.baseLocale,\n\t\t\t`:protocol://:domain(.*)::port?/:path(.*)?`,\n\t\t]);\n\t}\n\tconst strategiesUsedByRoutes = routeStrategies.flatMap((routeStrategy) =>\n\t\t\"strategy\" in routeStrategy ? routeStrategy.strategy : []\n\t);\n\tconst allUsedStrategies = new Set([\n\t\t...args.compilerOptions.strategy,\n\t\t...strategiesUsedByRoutes,\n\t]);\n\tconst needsUrlPatternPolyfill =\n\t\t!defaultUrlPatternUsed || routeStrategies.length > 0;\n\n\t// verify that urlPatterns' locales are valid\n\tfor (const urlPattern of urlPatterns) {\n\t\tfor (const [locale] of urlPattern.localized) {\n\t\t\tif (!args.locales.includes(locale)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid locale \"${locale}\" in urlPatterns. It must be one of the locales defined in the \"locales\" array.`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst code = `\n${needsUrlPatternPolyfill ? `import \"@inlang/paraglide-js/urlpattern-polyfill\";` : \"/** @type {any} */\\nconst URLPattern = {}\"}\n\n${injectCode(\"./variables.js\")\n\t.replace(\n\t\t`export const baseLocale = \"en\";`,\n\t\t`export const baseLocale = \"${args.baseLocale}\";`\n\t)\n\t.replace(\n\t\t`export const locales = /** @type {readonly string[]} */ ([\"en\", \"de\"]);`,\n\t\t`export const locales = /** @type {const} */ (${JSON.stringify(args.locales)});`\n\t)\n\t.replace(\n\t\t`export const strategy = [\"globalVariable\"];`,\n\t\t`export const strategy = ${JSON.stringify(args.compilerOptions.strategy, null, 2)};`\n\t)\n\t.replace(`<cookie-name>`, `${args.compilerOptions.cookieName}`)\n\t.replace(`60 * 60 * 24 * 400`, `${args.compilerOptions.cookieMaxAge}`)\n\t.replace(`<cookie-domain>`, `${args.compilerOptions.cookieDomain}`)\n\t.replace(\n\t\t`export const routeStrategies = [];`,\n\t\t`export const routeStrategies = ${JSON.stringify(routeStrategies, null, 2)};`\n\t)\n\t.replace(\n\t\t`export const TREE_SHAKE_COOKIE_STRATEGY_USED = false;`,\n\t\t`const TREE_SHAKE_COOKIE_STRATEGY_USED = ${allUsedStrategies.has(\"cookie\")};`\n\t)\n\t.replace(\n\t\t`export const TREE_SHAKE_URL_STRATEGY_USED = false;`,\n\t\t`const TREE_SHAKE_URL_STRATEGY_USED = ${allUsedStrategies.has(\"url\")};`\n\t)\n\t.replace(\n\t\t`export const TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED = false;`,\n\t\t`const TREE_SHAKE_GLOBAL_VARIABLE_STRATEGY_USED = ${allUsedStrategies.has(\"globalVariable\")};`\n\t)\n\t.replace(\n\t\t`export const TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED = false;`,\n\t\t`const TREE_SHAKE_PREFERRED_LANGUAGE_STRATEGY_USED = ${allUsedStrategies.has(\"preferredLanguage\")};`\n\t)\n\t.replace(\n\t\t`export const urlPatterns = [];`,\n\t\t`export const urlPatterns = ${JSON.stringify(urlPatterns, null, 2)};`\n\t)\n\t.replace(\n\t\t`export const trailingSlash = undefined;`,\n\t\t`export const trailingSlash = ${\n\t\t\targs.compilerOptions.trailingSlash === undefined\n\t\t\t\t? \"undefined\"\n\t\t\t\t: JSON.stringify(args.compilerOptions.trailingSlash)\n\t\t};`\n\t)\n\t.replace(\n\t\t`export const disableAsyncLocalStorage = false;`,\n\t\t`export const disableAsyncLocalStorage = ${args.compilerOptions.disableAsyncLocalStorage};`\n\t)\n\t.replace(\n\t\t`export const TREE_SHAKE_DEFAULT_URL_PATTERN_USED = false;`,\n\t\t`const TREE_SHAKE_DEFAULT_URL_PATTERN_USED = ${defaultUrlPatternUsed};`\n\t)\n\t.replace(\n\t\t`export const experimentalMiddlewareLocaleSplitting = false;`,\n\t\t`export const experimentalMiddlewareLocaleSplitting = ${args.compilerOptions.experimentalMiddlewareLocaleSplitting};`\n\t)\n\t.replace(\n\t\t`export const isServer = typeof window === \"undefined\";`,\n\t\t`export const isServer = ${args.compilerOptions.isServer};`\n\t)\n\t.replace(\n\t\t`export const experimentalStaticLocale = undefined;`,\n\t\targs.compilerOptions.experimentalStaticLocale\n\t\t\t? `export const experimentalStaticLocale = ${\n\t\t\t\t\targs.compilerOptions.experimentalStaticLocale ===\n\t\t\t\t\tperLocaleBuildStaticLocaleExpression\n\t\t\t\t\t\t? args.compilerOptions.experimentalStaticLocale\n\t\t\t\t\t\t: `assertIsLocale(${args.compilerOptions.experimentalStaticLocale})`\n\t\t\t\t};`\n\t\t\t: `export const experimentalStaticLocale = undefined;`\n\t)\n\t.replace(\n\t\t`export const localStorageKey = \"PARAGLIDE_LOCALE\";`,\n\t\t`export const localStorageKey = \"${args.compilerOptions.localStorageKey}\";`\n\t)\n\t.replace(\n\t\t`export const TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED = false;`,\n\t\t`const TREE_SHAKE_LOCAL_STORAGE_STRATEGY_USED = ${allUsedStrategies.has(\"localStorage\")};`\n\t)}\n\n/** @type {any} */ (globalThis).__paraglide =\n\t/** @type {any} */ (globalThis).__paraglide ?? {};\n/** @type {any} */ (globalThis).__paraglide.ssr =\n\t/** @type {any} */ (globalThis).__paraglide.ssr ?? {};\n\n${injectCode(\"./get-locale.js\")}\n\n${injectCode(\"./get-text-direction.js\")}\n\n${injectCode(\"./set-locale.js\")}\n\n${injectCode(\"./get-url-origin.js\")}\n\n${injectCode(\"./check-locale.js\")}\n\n${injectCode(\"./normalize-trailing-slash.js\", {\n\tprivateExports: [\"normalizeTrailingSlash\"],\n})}\n\n${injectCode(\"./exec-url-pattern.js\", {\n\tprivateExports: [\"execUrlPattern\"],\n})}\n\n${injectCode(\"./extract-locale-from-request.js\")}\n\n${injectCode(\"./extract-locale-from-request-async.js\")}\n\n${injectCode(\"./extract-locale-from-cookie.js\", {\n\tprivateExports: [\"clearLocaleCookieCache\"],\n})}\n\n${injectCode(\"./extract-locale-from-header.js\")}\n\n${injectCode(\"./extract-locale-from-navigator.js\")}\n\n${injectCode(\"./extract-locale-from-url.js\")}\n\n${injectCode(\"./localize-url.js\")}\n\n${injectCode(\"./route-strategy.js\")}\n\n${injectCode(\"./should-redirect.js\")}\n\n${injectCode(\"./localize-href.js\")}\n\n${injectCode(\"./track-message-call.js\")}\n\n${injectCode(\"./generate-static-localized-urls.js\")}\n\n${injectCode(\"./strategy.js\")}\n\n${injectCode(\"./type-definitions.js\").replace(\n\t`@typedef {string} Locale`,\n\t`@typedef {typeof locales[number]} Locale`\n)}\n`;\n\n\treturn code;\n}\n\n/**\n * Load a file from the current directory.\n *\n * Prunes the imports on top of the file as the runtime is\n * self-contained.\n *\n * @param {string} path\n * @param {{ privateExports?: string[] }} [options]\n * @returns {string}\n */\nfunction injectCode(\n\tpath: string,\n\toptions?: { privateExports?: string[] }\n): string {\n\tconst code = fs.readFileSync(new URL(path, import.meta.url), \"utf-8\");\n\t// Regex to match single-line and multi-line imports\n\tconst importRegex = /import\\s+[\\s\\S]*?from\\s+['\"][^'\"]+['\"]\\s*;?/g;\n\tconst sourceMapRegex = /\\/\\/# sourceMappingURL=.*$/gm;\n\tconst blockSourceMapRegex = /\\/\\*# sourceMappingURL=.*?\\*\\//g;\n\tlet injected = code\n\t\t.replace(importRegex, \"\")\n\t\t.replace(sourceMapRegex, \"\")\n\t\t.replace(blockSourceMapRegex, \"\")\n\t\t.trim();\n\tfor (const exportName of options?.privateExports ?? []) {\n\t\tinjected = injected.replace(\n\t\t\tnew RegExp(`\\\\bexport\\\\s+function\\\\s+${exportName}\\\\b`, \"g\"),\n\t\t\t`function ${exportName}`\n\t\t);\n\t}\n\treturn injected;\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Matches a canonical URL while allowing configured patterns to retain their
3
+ * existing trailing slash style.
4
+ *
5
+ * @param {URLPattern} pattern
6
+ * @param {URL} url
7
+ * @returns {any}
8
+ */
9
+ export declare function execUrlPattern(pattern: URLPattern, url: URL): any;
@@ -0,0 +1,31 @@
1
+ import { trailingSlash } from "./variables.js";
2
+ /**
3
+ * Matches a canonical URL while allowing configured patterns to retain their
4
+ * existing trailing slash style.
5
+ *
6
+ * @param {URLPattern} pattern
7
+ * @param {URL} url
8
+ * @returns {any}
9
+ */
10
+ export function execUrlPattern(pattern, url) {
11
+ if (trailingSlash === undefined || url.pathname === "/") {
12
+ return pattern.exec(url.href);
13
+ }
14
+ const alias = new URL(url);
15
+ if (trailingSlash === "always") {
16
+ alias.pathname = alias.pathname.replace(/\/+$/, "") || "/";
17
+ // Prefer the slashless alias so the canonical slash does not become part
18
+ // of a terminal wildcard capture.
19
+ return pattern.exec(alias.href) ?? pattern.exec(url.href);
20
+ }
21
+ else if (trailingSlash === "never") {
22
+ const match = pattern.exec(url.href);
23
+ if (match) {
24
+ return match;
25
+ }
26
+ alias.pathname = alias.pathname.replace(/\/+$/, "") + "/";
27
+ return pattern.exec(alias.href);
28
+ }
29
+ return pattern.exec(url.href);
30
+ }
31
+ //# sourceMappingURL=exec-url-pattern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-url-pattern.js","sourceRoot":"","sources":["../../../src/compiler/runtime/exec-url-pattern.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,OAAO,EAAE,GAAG;IAC1C,IAAI,aAAa,KAAK,SAAS,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,EAAE,CAAC;QACzD,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,aAAa,KAAK,QAAQ,EAAE,CAAC;QAChC,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;QAC3D,yEAAyE;QACzE,kCAAkC;QAClC,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3D,CAAC;SAAM,IAAI,aAAa,KAAK,OAAO,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,KAAK,EAAE,CAAC;YACX,OAAO,KAAK,CAAC;QACd,CAAC;QACD,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC;QAC1D,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC","sourcesContent":["import { trailingSlash } from \"./variables.js\";\n\n/**\n * Matches a canonical URL while allowing configured patterns to retain their\n * existing trailing slash style.\n *\n * @param {URLPattern} pattern\n * @param {URL} url\n * @returns {any}\n */\nexport function execUrlPattern(pattern, url) {\n\tif (trailingSlash === undefined || url.pathname === \"/\") {\n\t\treturn pattern.exec(url.href);\n\t}\n\n\tconst alias = new URL(url);\n\tif (trailingSlash === \"always\") {\n\t\talias.pathname = alias.pathname.replace(/\\/+$/, \"\") || \"/\";\n\t\t// Prefer the slashless alias so the canonical slash does not become part\n\t\t// of a terminal wildcard capture.\n\t\treturn pattern.exec(alias.href) ?? pattern.exec(url.href);\n\t} else if (trailingSlash === \"never\") {\n\t\tconst match = pattern.exec(url.href);\n\t\tif (match) {\n\t\t\treturn match;\n\t\t}\n\t\talias.pathname = alias.pathname.replace(/\\/+$/, \"\") + \"/\";\n\t\treturn pattern.exec(alias.href);\n\t}\n\treturn pattern.exec(url.href);\n}\n"]}
@@ -1,5 +1,7 @@
1
1
  import { toLocale } from "./check-locale.js";
2
2
  import { baseLocale, TREE_SHAKE_DEFAULT_URL_PATTERN_USED, urlPatterns, } from "./variables.js";
3
+ import { normalizeTrailingSlash } from "./normalize-trailing-slash.js";
4
+ import { execUrlPattern } from "./exec-url-pattern.js";
3
5
  /**
4
6
  * If extractLocaleFromUrl is called many times on the same page and the URL
5
7
  * hasn't changed, we don't need to recompute it every time which can get expensive.
@@ -28,14 +30,17 @@ export function extractLocaleFromUrl(url) {
28
30
  /** @type {Locale | undefined} */
29
31
  let result;
30
32
  if (TREE_SHAKE_DEFAULT_URL_PATTERN_USED) {
31
- result = defaultUrlPatternExtractLocale(url);
33
+ const urlObj = typeof url === "string"
34
+ ? new URL(url, "http://example.com")
35
+ : new URL(url);
36
+ result = defaultUrlPatternExtractLocale(normalizeTrailingSlash(urlObj));
32
37
  }
33
38
  else {
34
- const urlObj = typeof url === "string" ? new URL(url) : url;
39
+ const urlObj = normalizeTrailingSlash(typeof url === "string" ? new URL(url) : new URL(url));
35
40
  // Iterate over URL patterns
36
41
  for (const element of urlPatterns) {
37
42
  for (const [locale, localizedPattern] of element.localized) {
38
- const match = new URLPattern(localizedPattern, urlObj.href).exec(urlObj.href);
43
+ const match = execUrlPattern(new URLPattern(localizedPattern, urlObj.href), urlObj);
39
44
  if (match) {
40
45
  result = locale;
41
46
  break;
@@ -1 +1 @@
1
- {"version":3,"file":"extract-locale-from-url.js","sourceRoot":"","sources":["../../../src/compiler/runtime/extract-locale-from-url.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EACN,UAAU,EACV,mCAAmC,EACnC,WAAW,GACX,MAAM,gBAAgB,CAAC;AAExB;;;;;GAKG;AACH,+BAA+B;AAC/B,IAAI,SAAS,CAAC;AACd,+BAA+B;AAC/B,IAAI,YAAY,CAAC;AAEjB;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAG;IACvC,MAAM,SAAS,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;IAE3D,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,YAAY,CAAC;IACrB,CAAC;IAED,iCAAiC;IACjC,IAAI,MAAM,CAAC;IACX,IAAI,mCAAmC,EAAE,CAAC;QACzC,MAAM,GAAG,8BAA8B,CAAC,GAAG,CAAC,CAAC;IAC9C,CAAC;SAAM,CAAC;QACP,MAAM,MAAM,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QAE5D,4BAA4B;QAC5B,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;YACnC,KAAK,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBAC5D,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAC/D,MAAM,CAAC,IAAI,CACX,CAAC;gBAEF,IAAI,KAAK,EAAE,CAAC;oBACX,MAAM,GAAG,MAAM,CAAC;oBAChB,MAAM;gBACP,CAAC;YACF,CAAC;YACD,IAAI,MAAM;gBAAE,MAAM;QACnB,CAAC;IACF,CAAC;IAED,SAAS,GAAG,SAAS,CAAC;IACtB,YAAY,GAAG,MAAM,CAAC;IACtB,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,SAAS,8BAA8B,CAAC,GAAG;IAC1C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC;IAClD,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChE,OAAO,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC;AAChD,CAAC","sourcesContent":["import { toLocale } from \"./check-locale.js\";\nimport {\n\tbaseLocale,\n\tTREE_SHAKE_DEFAULT_URL_PATTERN_USED,\n\turlPatterns,\n} from \"./variables.js\";\n\n/**\n * If extractLocaleFromUrl is called many times on the same page and the URL\n * hasn't changed, we don't need to recompute it every time which can get expensive.\n * We might use a LRU cache if needed, but for now storing only the last result is enough.\n * https://github.com/opral/monorepo/pull/3575#discussion_r2066731243\n */\n/** @type {string|undefined} */\nlet cachedUrl;\n/** @type {Locale|undefined} */\nlet cachedLocale;\n\n/**\n * Extracts the locale from a given URL using native URLPattern.\n *\n * The built-in default `/:locale/...` routing is case-insensitive because it\n * canonicalizes the first path segment with `toLocale()`. Custom `urlPatterns`\n * keep URLPattern's normal exact matching semantics for path segments.\n *\n * @param {URL|string} url - The full URL from which to extract the locale.\n * @returns {Locale|undefined} The extracted locale, or undefined if no locale is found.\n */\nexport function extractLocaleFromUrl(url) {\n\tconst urlString = typeof url === \"string\" ? url : url.href;\n\n\tif (cachedUrl === urlString) {\n\t\treturn cachedLocale;\n\t}\n\n\t/** @type {Locale | undefined} */\n\tlet result;\n\tif (TREE_SHAKE_DEFAULT_URL_PATTERN_USED) {\n\t\tresult = defaultUrlPatternExtractLocale(url);\n\t} else {\n\t\tconst urlObj = typeof url === \"string\" ? new URL(url) : url;\n\n\t\t// Iterate over URL patterns\n\t\tfor (const element of urlPatterns) {\n\t\t\tfor (const [locale, localizedPattern] of element.localized) {\n\t\t\t\tconst match = new URLPattern(localizedPattern, urlObj.href).exec(\n\t\t\t\t\turlObj.href\n\t\t\t\t);\n\n\t\t\t\tif (match) {\n\t\t\t\t\tresult = locale;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result) break;\n\t\t}\n\t}\n\n\tcachedUrl = urlString;\n\tcachedLocale = result;\n\treturn result;\n}\n\n/**\n * https://github.com/opral/inlang-paraglide-js/issues/381\n *\n * @param {URL | string} url - The full URL from which to extract the locale.\n * @returns {Locale | undefined} The extracted locale, or undefined if no locale is found.\n */\nfunction defaultUrlPatternExtractLocale(url) {\n\tconst urlObj = new URL(url, \"http://example.com\");\n\tconst pathSegments = urlObj.pathname.split(\"/\").filter(Boolean);\n\treturn toLocale(pathSegments[0]) || baseLocale;\n}\n"]}
1
+ {"version":3,"file":"extract-locale-from-url.js","sourceRoot":"","sources":["../../../src/compiler/runtime/extract-locale-from-url.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EACN,UAAU,EACV,mCAAmC,EACnC,WAAW,GACX,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD;;;;;GAKG;AACH,+BAA+B;AAC/B,IAAI,SAAS,CAAC;AACd,+BAA+B;AAC/B,IAAI,YAAY,CAAC;AAEjB;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAG;IACvC,MAAM,SAAS,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;IAE3D,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,YAAY,CAAC;IACrB,CAAC;IAED,iCAAiC;IACjC,IAAI,MAAM,CAAC;IACX,IAAI,mCAAmC,EAAE,CAAC;QACzC,MAAM,MAAM,GACX,OAAO,GAAG,KAAK,QAAQ;YACtB,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,oBAAoB,CAAC;YACpC,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACjB,MAAM,GAAG,8BAA8B,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC,CAAC;IACzE,CAAC;SAAM,CAAC;QACP,MAAM,MAAM,GAAG,sBAAsB,CACpC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CACrD,CAAC;QAEF,4BAA4B;QAC5B,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;YACnC,KAAK,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBAC5D,MAAM,KAAK,GAAG,cAAc,CAC3B,IAAI,UAAU,CAAC,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,EAC7C,MAAM,CACN,CAAC;gBAEF,IAAI,KAAK,EAAE,CAAC;oBACX,MAAM,GAAG,MAAM,CAAC;oBAChB,MAAM;gBACP,CAAC;YACF,CAAC;YACD,IAAI,MAAM;gBAAE,MAAM;QACnB,CAAC;IACF,CAAC;IAED,SAAS,GAAG,SAAS,CAAC;IACtB,YAAY,GAAG,MAAM,CAAC;IACtB,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,SAAS,8BAA8B,CAAC,GAAG;IAC1C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC;IAClD,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChE,OAAO,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC;AAChD,CAAC","sourcesContent":["import { toLocale } from \"./check-locale.js\";\nimport {\n\tbaseLocale,\n\tTREE_SHAKE_DEFAULT_URL_PATTERN_USED,\n\turlPatterns,\n} from \"./variables.js\";\nimport { normalizeTrailingSlash } from \"./normalize-trailing-slash.js\";\nimport { execUrlPattern } from \"./exec-url-pattern.js\";\n\n/**\n * If extractLocaleFromUrl is called many times on the same page and the URL\n * hasn't changed, we don't need to recompute it every time which can get expensive.\n * We might use a LRU cache if needed, but for now storing only the last result is enough.\n * https://github.com/opral/monorepo/pull/3575#discussion_r2066731243\n */\n/** @type {string|undefined} */\nlet cachedUrl;\n/** @type {Locale|undefined} */\nlet cachedLocale;\n\n/**\n * Extracts the locale from a given URL using native URLPattern.\n *\n * The built-in default `/:locale/...` routing is case-insensitive because it\n * canonicalizes the first path segment with `toLocale()`. Custom `urlPatterns`\n * keep URLPattern's normal exact matching semantics for path segments.\n *\n * @param {URL|string} url - The full URL from which to extract the locale.\n * @returns {Locale|undefined} The extracted locale, or undefined if no locale is found.\n */\nexport function extractLocaleFromUrl(url) {\n\tconst urlString = typeof url === \"string\" ? url : url.href;\n\n\tif (cachedUrl === urlString) {\n\t\treturn cachedLocale;\n\t}\n\n\t/** @type {Locale | undefined} */\n\tlet result;\n\tif (TREE_SHAKE_DEFAULT_URL_PATTERN_USED) {\n\t\tconst urlObj =\n\t\t\ttypeof url === \"string\"\n\t\t\t\t? new URL(url, \"http://example.com\")\n\t\t\t\t: new URL(url);\n\t\tresult = defaultUrlPatternExtractLocale(normalizeTrailingSlash(urlObj));\n\t} else {\n\t\tconst urlObj = normalizeTrailingSlash(\n\t\t\ttypeof url === \"string\" ? new URL(url) : new URL(url)\n\t\t);\n\n\t\t// Iterate over URL patterns\n\t\tfor (const element of urlPatterns) {\n\t\t\tfor (const [locale, localizedPattern] of element.localized) {\n\t\t\t\tconst match = execUrlPattern(\n\t\t\t\t\tnew URLPattern(localizedPattern, urlObj.href),\n\t\t\t\t\turlObj\n\t\t\t\t);\n\n\t\t\t\tif (match) {\n\t\t\t\t\tresult = locale;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result) break;\n\t\t}\n\t}\n\n\tcachedUrl = urlString;\n\tcachedLocale = result;\n\treturn result;\n}\n\n/**\n * https://github.com/opral/inlang-paraglide-js/issues/381\n *\n * @param {URL | string} url - The full URL from which to extract the locale.\n * @returns {Locale | undefined} The extracted locale, or undefined if no locale is found.\n */\nfunction defaultUrlPatternExtractLocale(url) {\n\tconst urlObj = new URL(url, \"http://example.com\");\n\tconst pathSegments = urlObj.pathname.split(\"/\").filter(Boolean);\n\treturn toLocale(pathSegments[0]) || baseLocale;\n}\n"]}
@@ -1,5 +1,7 @@
1
1
  import { localizeUrl } from "./localize-url.js";
2
- import { locales, baseLocale, TREE_SHAKE_DEFAULT_URL_PATTERN_USED, urlPatterns, } from "./variables.js";
2
+ import { normalizeTrailingSlash } from "./normalize-trailing-slash.js";
3
+ import { execUrlPattern } from "./exec-url-pattern.js";
4
+ import { locales, baseLocale, TREE_SHAKE_DEFAULT_URL_PATTERN_USED, trailingSlash, urlPatterns, } from "./variables.js";
3
5
  /**
4
6
  * Generates localized URL variants for all provided URLs based on your configured locales and URL patterns.
5
7
  *
@@ -49,9 +51,12 @@ export function generateStaticLocalizedUrls(urls) {
49
51
  // For default URL pattern, we can optimize the generation
50
52
  if (TREE_SHAKE_DEFAULT_URL_PATTERN_USED) {
51
53
  for (const urlInput of urls) {
52
- const url = urlInput instanceof URL
54
+ const originalUrl = urlInput instanceof URL
53
55
  ? urlInput
54
56
  : new URL(urlInput, "http://localhost");
57
+ const url = trailingSlash === undefined
58
+ ? originalUrl
59
+ : normalizeTrailingSlash(new URL(originalUrl));
55
60
  // Base locale doesn't get a prefix
56
61
  localizedUrls.add(url);
57
62
  // Other locales get their code as prefix
@@ -59,7 +64,7 @@ export function generateStaticLocalizedUrls(urls) {
59
64
  if (locale !== baseLocale) {
60
65
  const localizedPath = `/${locale}${url.pathname}${url.search}${url.hash}`;
61
66
  const localizedUrl = new URL(localizedPath, url.origin);
62
- localizedUrls.add(localizedUrl);
67
+ localizedUrls.add(normalizeTrailingSlash(localizedUrl));
63
68
  }
64
69
  }
65
70
  }
@@ -67,15 +72,16 @@ export function generateStaticLocalizedUrls(urls) {
67
72
  }
68
73
  // For custom URL patterns, we need to use localizeUrl for each URL and locale
69
74
  for (const urlInput of urls) {
70
- const url = urlInput instanceof URL
75
+ const originalUrl = urlInput instanceof URL
71
76
  ? urlInput
72
77
  : new URL(urlInput, "http://localhost");
78
+ const url = normalizeTrailingSlash(new URL(originalUrl));
73
79
  // Try each URL pattern to find one that matches
74
80
  let patternFound = false;
75
81
  for (const pattern of urlPatterns) {
76
82
  try {
77
83
  // Try to match the unlocalized pattern
78
- const unlocalizedMatch = new URLPattern(pattern.pattern, url.href).exec(url.href);
84
+ const unlocalizedMatch = execUrlPattern(new URLPattern(pattern.pattern, url.href), url);
79
85
  if (!unlocalizedMatch)
80
86
  continue;
81
87
  patternFound = true;
@@ -106,7 +112,7 @@ export function generateStaticLocalizedUrls(urls) {
106
112
  }
107
113
  // If no pattern matched, use the URL as is
108
114
  if (!patternFound) {
109
- localizedUrls.add(url);
115
+ localizedUrls.add(originalUrl);
110
116
  }
111
117
  }
112
118
  return Array.from(localizedUrls);
@@ -1 +1 @@
1
- {"version":3,"file":"generate-static-localized-urls.js","sourceRoot":"","sources":["../../../src/compiler/runtime/generate-static-localized-urls.js"],"names":[],"mappings":"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","sourcesContent":["import { localizeUrl } from \"./localize-url.js\";\nimport {\n\tlocales,\n\tbaseLocale,\n\tTREE_SHAKE_DEFAULT_URL_PATTERN_USED,\n\turlPatterns,\n} from \"./variables.js\";\n\n/**\n * Generates localized URL variants for all provided URLs based on your configured locales and URL patterns.\n *\n * This function is essential for Static Site Generation (SSG) where you need to tell your framework\n * which pages to pre-render at build time. It's also useful for generating sitemaps and\n * `<link rel=\"alternate\" hreflang>` tags for SEO.\n *\n * The function respects your `urlPatterns` configuration - if you have translated pathnames\n * (e.g., `/about` → `/ueber-uns` for German), it will generate the correct localized paths.\n *\n * @see https://paraglidejs.com/static-site-generation\n *\n * @example\n * // Basic usage - generate all locale variants for a list of paths\n * const localizedUrls = generateStaticLocalizedUrls([\n * \"/\",\n * \"/about\",\n * \"/blog/post-1\",\n * ]);\n * // Returns URL objects for each locale:\n * // [\"/en/\", \"/de/\", \"/en/about\", \"/de/about\", \"/en/blog/post-1\", \"/de/blog/post-1\"]\n *\n * @example\n * // Use with framework SSG APIs\n * // SvelteKit\n * export function entries() {\n * const paths = [\"/\", \"/about\", \"/contact\"];\n * return generateStaticLocalizedUrls(paths).map(url => ({\n * locale: extractLocaleFromUrl(url)\n * }));\n * }\n *\n * @example\n * // Sitemap generation\n * const allPages = [\"/\", \"/about\", \"/blog\"];\n * const sitemapUrls = generateStaticLocalizedUrls(allPages);\n *\n * @param {(string | URL)[]} urls - List of canonical URLs or paths to generate localized versions for.\n * Can be absolute URLs (`https://example.com/about`) or paths (`/about`).\n * Paths are resolved against `http://localhost` internally.\n * @returns {URL[]} Array of URL objects representing all localized variants.\n * The order follows each input URL with all its locale variants before moving to the next URL.\n */\nexport function generateStaticLocalizedUrls(urls) {\n\t/** @type {Set<URL>} */\n\tconst localizedUrls = new Set();\n\n\t// For default URL pattern, we can optimize the generation\n\tif (TREE_SHAKE_DEFAULT_URL_PATTERN_USED) {\n\t\tfor (const urlInput of urls) {\n\t\t\tconst url =\n\t\t\t\turlInput instanceof URL\n\t\t\t\t\t? urlInput\n\t\t\t\t\t: new URL(urlInput, \"http://localhost\");\n\n\t\t\t// Base locale doesn't get a prefix\n\t\t\tlocalizedUrls.add(url);\n\n\t\t\t// Other locales get their code as prefix\n\t\t\tfor (const locale of locales) {\n\t\t\t\tif (locale !== baseLocale) {\n\t\t\t\t\tconst localizedPath = `/${locale}${url.pathname}${url.search}${url.hash}`;\n\t\t\t\t\tconst localizedUrl = new URL(localizedPath, url.origin);\n\t\t\t\t\tlocalizedUrls.add(localizedUrl);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn Array.from(localizedUrls);\n\t}\n\n\t// For custom URL patterns, we need to use localizeUrl for each URL and locale\n\tfor (const urlInput of urls) {\n\t\tconst url =\n\t\t\turlInput instanceof URL\n\t\t\t\t? urlInput\n\t\t\t\t: new URL(urlInput, \"http://localhost\");\n\n\t\t// Try each URL pattern to find one that matches\n\t\tlet patternFound = false;\n\t\tfor (const pattern of urlPatterns) {\n\t\t\ttry {\n\t\t\t\t// Try to match the unlocalized pattern\n\t\t\t\tconst unlocalizedMatch = new URLPattern(pattern.pattern, url.href).exec(\n\t\t\t\t\turl.href\n\t\t\t\t);\n\n\t\t\t\tif (!unlocalizedMatch) continue;\n\n\t\t\t\tpatternFound = true;\n\n\t\t\t\t// Track unique localized URLs to avoid duplicates when patterns are the same\n\t\t\t\tconst seenUrls = new Set();\n\n\t\t\t\t// Generate localized URL for each locale\n\t\t\t\tfor (const [locale] of pattern.localized) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst localizedUrl = localizeUrl(url, { locale });\n\t\t\t\t\t\tconst urlString = localizedUrl.href;\n\n\t\t\t\t\t\t// Only add if we haven't seen this exact URL before\n\t\t\t\t\t\tif (!seenUrls.has(urlString)) {\n\t\t\t\t\t\t\tseenUrls.add(urlString);\n\t\t\t\t\t\t\tlocalizedUrls.add(localizedUrl);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Skip if localization fails for this locale\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t} catch {\n\t\t\t\t// Skip if pattern matching fails\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t// If no pattern matched, use the URL as is\n\t\tif (!patternFound) {\n\t\t\tlocalizedUrls.add(url);\n\t\t}\n\t}\n\n\treturn Array.from(localizedUrls);\n}\n"]}
1
+ {"version":3,"file":"generate-static-localized-urls.js","sourceRoot":"","sources":["../../../src/compiler/runtime/generate-static-localized-urls.js"],"names":[],"mappings":"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","sourcesContent":["import { localizeUrl } from \"./localize-url.js\";\nimport { normalizeTrailingSlash } from \"./normalize-trailing-slash.js\";\nimport { execUrlPattern } from \"./exec-url-pattern.js\";\nimport {\n\tlocales,\n\tbaseLocale,\n\tTREE_SHAKE_DEFAULT_URL_PATTERN_USED,\n\ttrailingSlash,\n\turlPatterns,\n} from \"./variables.js\";\n\n/**\n * Generates localized URL variants for all provided URLs based on your configured locales and URL patterns.\n *\n * This function is essential for Static Site Generation (SSG) where you need to tell your framework\n * which pages to pre-render at build time. It's also useful for generating sitemaps and\n * `<link rel=\"alternate\" hreflang>` tags for SEO.\n *\n * The function respects your `urlPatterns` configuration - if you have translated pathnames\n * (e.g., `/about` → `/ueber-uns` for German), it will generate the correct localized paths.\n *\n * @see https://paraglidejs.com/static-site-generation\n *\n * @example\n * // Basic usage - generate all locale variants for a list of paths\n * const localizedUrls = generateStaticLocalizedUrls([\n * \"/\",\n * \"/about\",\n * \"/blog/post-1\",\n * ]);\n * // Returns URL objects for each locale:\n * // [\"/en/\", \"/de/\", \"/en/about\", \"/de/about\", \"/en/blog/post-1\", \"/de/blog/post-1\"]\n *\n * @example\n * // Use with framework SSG APIs\n * // SvelteKit\n * export function entries() {\n * const paths = [\"/\", \"/about\", \"/contact\"];\n * return generateStaticLocalizedUrls(paths).map(url => ({\n * locale: extractLocaleFromUrl(url)\n * }));\n * }\n *\n * @example\n * // Sitemap generation\n * const allPages = [\"/\", \"/about\", \"/blog\"];\n * const sitemapUrls = generateStaticLocalizedUrls(allPages);\n *\n * @param {(string | URL)[]} urls - List of canonical URLs or paths to generate localized versions for.\n * Can be absolute URLs (`https://example.com/about`) or paths (`/about`).\n * Paths are resolved against `http://localhost` internally.\n * @returns {URL[]} Array of URL objects representing all localized variants.\n * The order follows each input URL with all its locale variants before moving to the next URL.\n */\nexport function generateStaticLocalizedUrls(urls) {\n\t/** @type {Set<URL>} */\n\tconst localizedUrls = new Set();\n\n\t// For default URL pattern, we can optimize the generation\n\tif (TREE_SHAKE_DEFAULT_URL_PATTERN_USED) {\n\t\tfor (const urlInput of urls) {\n\t\t\tconst originalUrl =\n\t\t\t\turlInput instanceof URL\n\t\t\t\t\t? urlInput\n\t\t\t\t\t: new URL(urlInput, \"http://localhost\");\n\t\t\tconst url =\n\t\t\t\ttrailingSlash === undefined\n\t\t\t\t\t? originalUrl\n\t\t\t\t\t: normalizeTrailingSlash(new URL(originalUrl));\n\n\t\t\t// Base locale doesn't get a prefix\n\t\t\tlocalizedUrls.add(url);\n\n\t\t\t// Other locales get their code as prefix\n\t\t\tfor (const locale of locales) {\n\t\t\t\tif (locale !== baseLocale) {\n\t\t\t\t\tconst localizedPath = `/${locale}${url.pathname}${url.search}${url.hash}`;\n\t\t\t\t\tconst localizedUrl = new URL(localizedPath, url.origin);\n\t\t\t\t\tlocalizedUrls.add(normalizeTrailingSlash(localizedUrl));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn Array.from(localizedUrls);\n\t}\n\n\t// For custom URL patterns, we need to use localizeUrl for each URL and locale\n\tfor (const urlInput of urls) {\n\t\tconst originalUrl =\n\t\t\turlInput instanceof URL\n\t\t\t\t? urlInput\n\t\t\t\t: new URL(urlInput, \"http://localhost\");\n\t\tconst url = normalizeTrailingSlash(new URL(originalUrl));\n\n\t\t// Try each URL pattern to find one that matches\n\t\tlet patternFound = false;\n\t\tfor (const pattern of urlPatterns) {\n\t\t\ttry {\n\t\t\t\t// Try to match the unlocalized pattern\n\t\t\t\tconst unlocalizedMatch = execUrlPattern(\n\t\t\t\t\tnew URLPattern(pattern.pattern, url.href),\n\t\t\t\t\turl\n\t\t\t\t);\n\n\t\t\t\tif (!unlocalizedMatch) continue;\n\n\t\t\t\tpatternFound = true;\n\n\t\t\t\t// Track unique localized URLs to avoid duplicates when patterns are the same\n\t\t\t\tconst seenUrls = new Set();\n\n\t\t\t\t// Generate localized URL for each locale\n\t\t\t\tfor (const [locale] of pattern.localized) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst localizedUrl = localizeUrl(url, { locale });\n\t\t\t\t\t\tconst urlString = localizedUrl.href;\n\n\t\t\t\t\t\t// Only add if we haven't seen this exact URL before\n\t\t\t\t\t\tif (!seenUrls.has(urlString)) {\n\t\t\t\t\t\t\tseenUrls.add(urlString);\n\t\t\t\t\t\t\tlocalizedUrls.add(localizedUrl);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Skip if localization fails for this locale\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t} catch {\n\t\t\t\t// Skip if pattern matching fails\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t// If no pattern matched, use the URL as is\n\t\tif (!patternFound) {\n\t\t\tlocalizedUrls.add(originalUrl);\n\t\t}\n\t}\n\n\treturn Array.from(localizedUrls);\n}\n"]}
@@ -178,4 +178,50 @@ test("generates localized URLs from paths", async () => {
178
178
  // All URLs should be URL objects
179
179
  expect(urls.every((url) => url instanceof URL)).toBe(true);
180
180
  });
181
+ test("applies trailing slash canonicalization to default pattern output", 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
+ trailingSlash: "never",
191
+ });
192
+ const urls = runtime.generateStaticLocalizedUrls(["/", "/about/"]);
193
+ expect(urls.map((url) => url.pathname).sort()).toEqual([
194
+ "/",
195
+ "/about",
196
+ "/de",
197
+ "/de/about",
198
+ ]);
199
+ });
200
+ test("canonicalizes custom pattern input but preserves unmatched URLs", async () => {
201
+ const runtime = await createParaglide({
202
+ blob: await newProject({
203
+ settings: {
204
+ baseLocale: "en",
205
+ locales: ["en", "de"],
206
+ },
207
+ }),
208
+ strategy: ["url"],
209
+ trailingSlash: "never",
210
+ urlPatterns: [
211
+ {
212
+ pattern: "/about",
213
+ localized: [
214
+ ["en", "/about"],
215
+ ["de", "/de/ueber"],
216
+ ],
217
+ },
218
+ ],
219
+ });
220
+ const urls = runtime.generateStaticLocalizedUrls(["/about/", "/asset/"]);
221
+ expect(urls.map((url) => url.pathname).sort()).toEqual([
222
+ "/about",
223
+ "/asset/",
224
+ "/de/ueber",
225
+ ]);
226
+ });
181
227
  //# sourceMappingURL=generate-static-localized-urls.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate-static-localized-urls.test.js","sourceRoot":"","sources":["../../../src/compiler/runtime/generate-static-localized-urls.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\";\nimport \"@inlang/paraglide-js/urlpattern-polyfill\";\n\ntest(\"generates localized URLs using default URL pattern\", 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\tstrategy: [\"url\"],\n\t\t// undefined creates the default pattern\n\t\turlPatterns: undefined,\n\t});\n\n\tconst urls = runtime.generateStaticLocalizedUrls([\n\t\t\"http://example.com/about\",\n\t\t\"http://example.com/blog/post-1\",\n\t\t\"http://example.com/contact\",\n\t]);\n\tconst pathnames = urls.map((url) => url.pathname).sort();\n\n\texpect(pathnames).toEqual([\n\t\t\"/about\",\n\t\t\"/blog/post-1\",\n\t\t\"/contact\",\n\t\t\"/de/about\",\n\t\t\"/de/blog/post-1\",\n\t\t\"/de/contact\",\n\t\t\"/fr/about\",\n\t\t\"/fr/blog/post-1\",\n\t\t\"/fr/contact\",\n\t]);\n\n\t// All URLs should be URL objects\n\texpect(urls.every((url) => url instanceof URL)).toBe(true);\n});\n\ntest(\"generates localized URLs using custom URL patterns\", 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: \"/store/item/:id\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"en\", \"/store/item/:id\"],\n\t\t\t\t\t[\"de\", \"/laden/artikel/:id\"],\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"/blog/:slug\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"en\", \"/blog/:slug\"],\n\t\t\t\t\t[\"de\", \"/blog/:slug\"], // Same pattern for both locales\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\tconst urls = runtime.generateStaticLocalizedUrls([\n\t\t\"https://example.com/store/item/123\",\n\t\t\"https://example.com/store/item/456\",\n\t\t\"https://example.com/blog/my-post\",\n\t]);\n\n\tconst pathnames = urls.map((url) => url.pathname).sort();\n\n\texpect(pathnames).toEqual([\n\t\t\"/blog/my-post\",\n\t\t\"/laden/artikel/123\",\n\t\t\"/laden/artikel/456\",\n\t\t\"/store/item/123\",\n\t\t\"/store/item/456\",\n\t]);\n\n\t// All URLs should be URL objects\n\texpect(urls.every((url) => url instanceof URL)).toBe(true);\n});\n\ntest(\"handles paths that don't match any pattern by including them\", 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: \"https://example.com/store/:path*\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"en\", \"https://example.com/store/:path*\"],\n\t\t\t\t\t[\"de\", \"https://example.com/laden/:path*\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\tconst urls = runtime.generateStaticLocalizedUrls([\n\t\t\"https://example.com/store/item/123\", // Should match pattern\n\t\t\"https://example.com/about\", // Should not match pattern\n\t\t\"https://example.com/contact\", // Should not match pattern\n\t]);\n\n\tconst pathnames = urls.map((url) => url.pathname).sort();\n\n\texpect(pathnames).toEqual([\n\t\t\"/about\",\n\t\t\"/contact\",\n\t\t\"/laden/item/123\",\n\t\t\"/store/item/123\",\n\t]);\n\n\t// All URLs should be URL objects\n\texpect(urls.every((url) => url instanceof URL)).toBe(true);\n});\n\ntest(\"handles URL objects as input\", 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: \"/store/:path*\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"en\", \"/store/:path*\"],\n\t\t\t\t\t[\"de\", \"/laden/:path*\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\tconst urls = runtime.generateStaticLocalizedUrls([\n\t\tnew URL(\"https://example.com/store/item/123?color=blue#reviews\"),\n\t\tnew URL(\"https://example.com/store/cart\"),\n\t\tnew URL(\"https://example.com/about\"), // Should not match pattern\n\t]);\n\n\tconst pathnames = urls.map((url) => url.pathname).sort();\n\n\texpect(pathnames).toEqual([\n\t\t\"/about\",\n\t\t\"/laden/cart\",\n\t\t\"/laden/item/123\",\n\t\t\"/store/cart\",\n\t\t\"/store/item/123\",\n\t]);\n\n\t// Verify search params and hash fragments are preserve\n\t// All URLs should be URL objects\n\texpect(urls.every((url) => url instanceof URL)).toBe(true);\n});\n\ntest(\"generates localized URLs from paths\", 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\tstrategy: [\"url\"],\n\t\t// undefined creates the default pattern\n\t\turlPatterns: undefined,\n\t});\n\n\tconst urls = runtime.generateStaticLocalizedUrls([\n\t\t\"/about\",\n\t\t\"/blog/post-1\",\n\t\t\"/contact\",\n\t]);\n\n\tconst pathnames = urls.map((url) => url.pathname).sort();\n\n\texpect(pathnames).toEqual([\n\t\t\"/about\",\n\t\t\"/blog/post-1\",\n\t\t\"/contact\",\n\t\t\"/de/about\",\n\t\t\"/de/blog/post-1\",\n\t\t\"/de/contact\",\n\t\t\"/fr/about\",\n\t\t\"/fr/blog/post-1\",\n\t\t\"/fr/contact\",\n\t]);\n\n\t// All URLs should be URL objects\n\texpect(urls.every((url) => url instanceof URL)).toBe(true);\n});\n"]}
1
+ {"version":3,"file":"generate-static-localized-urls.test.js","sourceRoot":"","sources":["../../../src/compiler/runtime/generate-static-localized-urls.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\";\nimport \"@inlang/paraglide-js/urlpattern-polyfill\";\n\ntest(\"generates localized URLs using default URL pattern\", 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\tstrategy: [\"url\"],\n\t\t// undefined creates the default pattern\n\t\turlPatterns: undefined,\n\t});\n\n\tconst urls = runtime.generateStaticLocalizedUrls([\n\t\t\"http://example.com/about\",\n\t\t\"http://example.com/blog/post-1\",\n\t\t\"http://example.com/contact\",\n\t]);\n\tconst pathnames = urls.map((url) => url.pathname).sort();\n\n\texpect(pathnames).toEqual([\n\t\t\"/about\",\n\t\t\"/blog/post-1\",\n\t\t\"/contact\",\n\t\t\"/de/about\",\n\t\t\"/de/blog/post-1\",\n\t\t\"/de/contact\",\n\t\t\"/fr/about\",\n\t\t\"/fr/blog/post-1\",\n\t\t\"/fr/contact\",\n\t]);\n\n\t// All URLs should be URL objects\n\texpect(urls.every((url) => url instanceof URL)).toBe(true);\n});\n\ntest(\"generates localized URLs using custom URL patterns\", 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: \"/store/item/:id\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"en\", \"/store/item/:id\"],\n\t\t\t\t\t[\"de\", \"/laden/artikel/:id\"],\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tpattern: \"/blog/:slug\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"en\", \"/blog/:slug\"],\n\t\t\t\t\t[\"de\", \"/blog/:slug\"], // Same pattern for both locales\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\tconst urls = runtime.generateStaticLocalizedUrls([\n\t\t\"https://example.com/store/item/123\",\n\t\t\"https://example.com/store/item/456\",\n\t\t\"https://example.com/blog/my-post\",\n\t]);\n\n\tconst pathnames = urls.map((url) => url.pathname).sort();\n\n\texpect(pathnames).toEqual([\n\t\t\"/blog/my-post\",\n\t\t\"/laden/artikel/123\",\n\t\t\"/laden/artikel/456\",\n\t\t\"/store/item/123\",\n\t\t\"/store/item/456\",\n\t]);\n\n\t// All URLs should be URL objects\n\texpect(urls.every((url) => url instanceof URL)).toBe(true);\n});\n\ntest(\"handles paths that don't match any pattern by including them\", 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: \"https://example.com/store/:path*\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"en\", \"https://example.com/store/:path*\"],\n\t\t\t\t\t[\"de\", \"https://example.com/laden/:path*\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\tconst urls = runtime.generateStaticLocalizedUrls([\n\t\t\"https://example.com/store/item/123\", // Should match pattern\n\t\t\"https://example.com/about\", // Should not match pattern\n\t\t\"https://example.com/contact\", // Should not match pattern\n\t]);\n\n\tconst pathnames = urls.map((url) => url.pathname).sort();\n\n\texpect(pathnames).toEqual([\n\t\t\"/about\",\n\t\t\"/contact\",\n\t\t\"/laden/item/123\",\n\t\t\"/store/item/123\",\n\t]);\n\n\t// All URLs should be URL objects\n\texpect(urls.every((url) => url instanceof URL)).toBe(true);\n});\n\ntest(\"handles URL objects as input\", 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: \"/store/:path*\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"en\", \"/store/:path*\"],\n\t\t\t\t\t[\"de\", \"/laden/:path*\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\tconst urls = runtime.generateStaticLocalizedUrls([\n\t\tnew URL(\"https://example.com/store/item/123?color=blue#reviews\"),\n\t\tnew URL(\"https://example.com/store/cart\"),\n\t\tnew URL(\"https://example.com/about\"), // Should not match pattern\n\t]);\n\n\tconst pathnames = urls.map((url) => url.pathname).sort();\n\n\texpect(pathnames).toEqual([\n\t\t\"/about\",\n\t\t\"/laden/cart\",\n\t\t\"/laden/item/123\",\n\t\t\"/store/cart\",\n\t\t\"/store/item/123\",\n\t]);\n\n\t// Verify search params and hash fragments are preserve\n\t// All URLs should be URL objects\n\texpect(urls.every((url) => url instanceof URL)).toBe(true);\n});\n\ntest(\"generates localized URLs from paths\", 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\tstrategy: [\"url\"],\n\t\t// undefined creates the default pattern\n\t\turlPatterns: undefined,\n\t});\n\n\tconst urls = runtime.generateStaticLocalizedUrls([\n\t\t\"/about\",\n\t\t\"/blog/post-1\",\n\t\t\"/contact\",\n\t]);\n\n\tconst pathnames = urls.map((url) => url.pathname).sort();\n\n\texpect(pathnames).toEqual([\n\t\t\"/about\",\n\t\t\"/blog/post-1\",\n\t\t\"/contact\",\n\t\t\"/de/about\",\n\t\t\"/de/blog/post-1\",\n\t\t\"/de/contact\",\n\t\t\"/fr/about\",\n\t\t\"/fr/blog/post-1\",\n\t\t\"/fr/contact\",\n\t]);\n\n\t// All URLs should be URL objects\n\texpect(urls.every((url) => url instanceof URL)).toBe(true);\n});\n\ntest(\"applies trailing slash canonicalization to default pattern output\", 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\ttrailingSlash: \"never\",\n\t});\n\n\tconst urls = runtime.generateStaticLocalizedUrls([\"/\", \"/about/\"]);\n\texpect(urls.map((url) => url.pathname).sort()).toEqual([\n\t\t\"/\",\n\t\t\"/about\",\n\t\t\"/de\",\n\t\t\"/de/about\",\n\t]);\n});\n\ntest(\"canonicalizes custom pattern input but preserves unmatched URLs\", 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\ttrailingSlash: \"never\",\n\t\turlPatterns: [\n\t\t\t{\n\t\t\t\tpattern: \"/about\",\n\t\t\t\tlocalized: [\n\t\t\t\t\t[\"en\", \"/about\"],\n\t\t\t\t\t[\"de\", \"/de/ueber\"],\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t});\n\n\tconst urls = runtime.generateStaticLocalizedUrls([\"/about/\", \"/asset/\"]);\n\texpect(urls.map((url) => url.pathname).sort()).toEqual([\n\t\t\"/about\",\n\t\t\"/asset/\",\n\t\t\"/de/ueber\",\n\t]);\n});\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,77 @@
1
+ import { bench } from "vitest";
2
+ import { newProject } from "@inlang/sdk";
3
+ import { createParaglide } from "../create-paraglide.js";
4
+ const runtime = await createParaglide({
5
+ blob: await newProject({
6
+ settings: {
7
+ baseLocale: "en",
8
+ locales: ["en", "de", "fr", "es"],
9
+ },
10
+ }),
11
+ isServer: "false",
12
+ strategy: ["url"],
13
+ urlPatterns: [
14
+ {
15
+ pattern: "https://example.com/:path*",
16
+ localized: [
17
+ ["en", "https://example.com/:path*"],
18
+ ["de", "https://de.example.com/:path*"],
19
+ ["fr", "https://fr.example.com/:path*"],
20
+ ["es", "https://es.example.com/:path*"],
21
+ ],
22
+ },
23
+ {
24
+ pattern: "https://example.com/blog/:path*",
25
+ localized: [
26
+ ["en", "https://example.com/blog/:path*"],
27
+ ["de", "https://de.example.com/blog/:path*"],
28
+ ["fr", "https://fr.example.com/blog/:path*"],
29
+ ["es", "https://es.example.com/blog/:path*"],
30
+ ],
31
+ },
32
+ ],
33
+ });
34
+ const genericRuntime = await createParaglide({
35
+ blob: await newProject({
36
+ settings: {
37
+ baseLocale: "en",
38
+ locales: ["en", "de", "fr", "es"],
39
+ },
40
+ }),
41
+ isServer: "false",
42
+ strategy: ["url"],
43
+ urlPatterns: [
44
+ {
45
+ // A custom group name intentionally keeps this route on the generic
46
+ // URLPattern fallback while matching the same URLs.
47
+ pattern: "/:slug(.*)?",
48
+ localized: [
49
+ ["en", "/:slug(.*)?"],
50
+ ["de", "/de/:slug(.*)?"],
51
+ ["fr", "/fr/:slug(.*)?"],
52
+ ["es", "/es/:slug(.*)?"],
53
+ ],
54
+ },
55
+ ],
56
+ });
57
+ runtime.overwriteGetLocale(() => "en");
58
+ runtime.overwriteGetUrlOrigin(() => "https://example.com");
59
+ genericRuntime.overwriteGetLocale(() => "en");
60
+ genericRuntime.overwriteGetUrlOrigin(() => "https://example.com");
61
+ for (let i = 0; i < 1000; i++) {
62
+ runtime.localizeHref(`/page-${i % 100}.html`, { locale: "de" });
63
+ }
64
+ bench("localizeHref with repeated routes", () => {
65
+ for (let i = 0; i < 1000; i++) {
66
+ runtime.localizeHref(`/page-${i % 100}.html`, { locale: "de" });
67
+ }
68
+ }, { time: 1000 });
69
+ for (let i = 0; i < 1000; i++) {
70
+ genericRuntime.localizeHref(`/page-${i % 100}.html`, { locale: "de" });
71
+ }
72
+ bench("localizeHref with generic URLPattern fallback", () => {
73
+ for (let i = 0; i < 1000; i++) {
74
+ genericRuntime.localizeHref(`/page-${i % 100}.html`, { locale: "de" });
75
+ }
76
+ }, { time: 1000 });
77
+ //# sourceMappingURL=localize-href.bench.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localize-href.bench.js","sourceRoot":"","sources":["../../../src/compiler/runtime/localize-href.bench.ts"],"names":[],"mappings":"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","sourcesContent":["import { bench } from \"vitest\";\nimport { newProject } from \"@inlang/sdk\";\nimport { createParaglide } from \"../create-paraglide.js\";\n\nconst runtime = await createParaglide({\n\tblob: await newProject({\n\t\tsettings: {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\", \"de\", \"fr\", \"es\"],\n\t\t},\n\t}),\n\tisServer: \"false\",\n\tstrategy: [\"url\"],\n\turlPatterns: [\n\t\t{\n\t\t\tpattern: \"https://example.com/:path*\",\n\t\t\tlocalized: [\n\t\t\t\t[\"en\", \"https://example.com/:path*\"],\n\t\t\t\t[\"de\", \"https://de.example.com/:path*\"],\n\t\t\t\t[\"fr\", \"https://fr.example.com/:path*\"],\n\t\t\t\t[\"es\", \"https://es.example.com/:path*\"],\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tpattern: \"https://example.com/blog/:path*\",\n\t\t\tlocalized: [\n\t\t\t\t[\"en\", \"https://example.com/blog/:path*\"],\n\t\t\t\t[\"de\", \"https://de.example.com/blog/:path*\"],\n\t\t\t\t[\"fr\", \"https://fr.example.com/blog/:path*\"],\n\t\t\t\t[\"es\", \"https://es.example.com/blog/:path*\"],\n\t\t\t],\n\t\t},\n\t],\n});\n\nconst genericRuntime = await createParaglide({\n\tblob: await newProject({\n\t\tsettings: {\n\t\t\tbaseLocale: \"en\",\n\t\t\tlocales: [\"en\", \"de\", \"fr\", \"es\"],\n\t\t},\n\t}),\n\tisServer: \"false\",\n\tstrategy: [\"url\"],\n\turlPatterns: [\n\t\t{\n\t\t\t// A custom group name intentionally keeps this route on the generic\n\t\t\t// URLPattern fallback while matching the same URLs.\n\t\t\tpattern: \"/:slug(.*)?\",\n\t\t\tlocalized: [\n\t\t\t\t[\"en\", \"/:slug(.*)?\"],\n\t\t\t\t[\"de\", \"/de/:slug(.*)?\"],\n\t\t\t\t[\"fr\", \"/fr/:slug(.*)?\"],\n\t\t\t\t[\"es\", \"/es/:slug(.*)?\"],\n\t\t\t],\n\t\t},\n\t],\n});\n\nruntime.overwriteGetLocale(() => \"en\");\nruntime.overwriteGetUrlOrigin(() => \"https://example.com\");\ngenericRuntime.overwriteGetLocale(() => \"en\");\ngenericRuntime.overwriteGetUrlOrigin(() => \"https://example.com\");\n\nfor (let i = 0; i < 1000; i++) {\n\truntime.localizeHref(`/page-${i % 100}.html`, { locale: \"de\" });\n}\n\nbench(\n\t\"localizeHref with repeated routes\",\n\t() => {\n\t\tfor (let i = 0; i < 1000; i++) {\n\t\t\truntime.localizeHref(`/page-${i % 100}.html`, { locale: \"de\" });\n\t\t}\n\t},\n\t{ time: 1000 }\n);\n\nfor (let i = 0; i < 1000; i++) {\n\tgenericRuntime.localizeHref(`/page-${i % 100}.html`, { locale: \"de\" });\n}\n\nbench(\n\t\"localizeHref with generic URLPattern fallback\",\n\t() => {\n\t\tfor (let i = 0; i < 1000; i++) {\n\t\t\tgenericRuntime.localizeHref(`/page-${i % 100}.html`, { locale: \"de\" });\n\t\t}\n\t},\n\t{ time: 1000 }\n);\n"]}