@i18n-micro/route-strategy 1.1.30 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,228 +1,104 @@
1
1
  # @i18n-micro/route-strategy
2
2
 
3
- High‑performance route generation and localization strategies for **Nuxt I18n Micro**.
3
+ Build-time route generation for **Nuxt I18n Micro**: turns Nuxt pages (`NuxtPage[]`) into the localized vue-router routes of a strategy (`prefix_except_default`, `prefix`, `prefix_and_default`, `no_prefix`).
4
4
 
5
- This package is responsible for turning Nuxt page definitions (`NuxtPage[]`) into a fully localized route tree for all supported strategies:
5
+ The Nuxt module calls it in `pages:resolved`. It works on plain `NuxtPage` objects, without Nuxt or vue-router.
6
6
 
7
- - `no_prefix`
8
- - `prefix`
9
- - `prefix_except_default`
10
- - `prefix_and_default`
7
+ ---
11
8
 
12
- It is used by the main Nuxt module at build/extend‑pages time and is designed to be:
9
+ ## How it works
13
10
 
14
- - deterministic (stable snapshots),
15
- - fast (single pass over pages, no heavy allocations),
16
- - and testable (Jest snapshot suite under `tests/`).
11
+ Generation has two steps:
17
12
 
18
- ---
13
+ 1. **Localize.** For each page and locale, build the page tree as that locale serves it. Custom paths are applied, and unlocalized or disallowed pages are dropped.
14
+ 2. **Emit.** Turn the trees into vue-router records.
15
+ - Locales whose tree equals the page as written share one record, `/:locale(de|fr)/...`, named `localized-<name>`.
16
+ - Every other locale gets its own tree, named `localized-<name>-<locale>`. Under `no_prefix`, locales on one path share that record, named after the first.
17
+ - The default locale of `prefix_except_default` / `prefix_and_default`, and the base page of `no_prefix`, keep the page's own names, unprefixed.
19
18
 
20
- ## Overview
19
+ Every localized record carries `meta.i18n = { name, locales }`: the page's base name and the locales it serves. `@i18n-micro/path-strategy` reads that at runtime instead of re-deriving the rules, so the build is the single source of truth.
21
20
 
22
- At a high level, `@i18n-micro/route-strategy`:
21
+ | Strategy (locales `en` default, `de`, `fr`) | `/about`, `de: '/ueber-uns'` |
22
+ | ------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
23
+ | `prefix_except_default` | `about /about`, `localized-about /:locale(fr)/about`, `localized-about-de /:locale(de)/ueber-uns` |
24
+ | `prefix` | `localized-about /:locale(en\|fr)/about`, `localized-about-de /:locale(de)/ueber-uns` |
25
+ | `prefix_and_default` | `about /about`, `localized-about /:locale(en\|fr)/about`, `localized-about-de /:locale(de)/ueber-uns` |
26
+ | `no_prefix` | `about /about`, `localized-about-de /ueber-uns` |
23
27
 
24
- - takes the raw Nuxt `pages` array;
25
- - applies the selected i18n strategy;
26
- - expands each page into zero, one or many localized routes;
27
- - wires in:
28
- - `globalLocaleRoutes` (custom paths per locale),
29
- - `filesLocaleRoutes` (file‑level locale paths),
30
- - `routeLocales` (locale restrictions),
31
- - `noPrefixRedirect` flag;
32
- - keeps aliases, children and internal/excluded routes consistent.
28
+ ---
33
29
 
34
- All of this is done without any Nuxt runtime / Vue Router dependency – it operates purely on plain `NuxtPage` objects, so it is safe to run in Node during build and easy to test.
30
+ ## Rules
35
31
 
36
- ---
32
+ ### Keys
37
33
 
38
- ## Core Concepts
34
+ `globalLocaleRoutes` keys and `$defineI18nRoute` file keys are looked up in this order:
39
35
 
40
- ### Strategies
36
+ 1. The page's full path: `about/team`, `/about/team`, `blog/[slug]` or `blog/:slug()`.
37
+ 2. Its route name (`about-team`), only for keys written without a leading slash.
41
38
 
42
- The generator supports the same strategies as the runtime:
39
+ When both `about-team` and `/about-team` exist for a top-level page, the form without the slash wins.
43
40
 
44
- - **`no_prefix`**
45
- - URLs have **no locale prefix** (`/about`, `/kontakt`).
46
- - Locale is handled via cookies / runtime logic, not via path.
47
- - `globalLocaleRoutes` are used to generate per‑locale variants where appropriate, but the _visible_ URLs stay prefix‑less.
41
+ ### Values
48
42
 
49
- - **`prefix`**
50
- - All localized routes are prefixed: `/en/about`, `/de/ueber-uns`, etc.
51
- - There is **no unprefixed** default route; every locale uses its own prefix.
43
+ - A top-level value is the page's path (`about-us` = `/about-us`). File forms are accepted: `[id]`, `[[page]]`, `[...slug]`.
44
+ - A child's value is relative to its parent's localized path:
45
+ - `einstellungen` under `/konto` is `/konto/einstellungen`.
46
+ - An absolute value is a full URL: `/konto/bestellungen` under `/konto` is `bestellungen`.
47
+ - Any other absolute value keeps what follows the segments it shares with the parent.
48
+ - A value equal to the original path is no custom path.
49
+ - A value that starts with its own locale code (`fr: '/fr/article'`) warns: the prefix is added automatically.
50
+ - `false` means the page is not localized. `routeLocales` restricts a page (or a child) to some locales.
52
51
 
53
- - **`prefix_except_default`**
54
- - Default locale uses no prefix (`/about`).
55
- - Non‑default locales are prefixed (`/de/ueber-uns`).
56
- - This is the most complex strategy around children, aliases and custom paths.
52
+ ### Aliases and redirects
57
53
 
58
- - **`prefix_and_default`**
59
- - Default locale is available **both** as unprefixed and prefixed:
60
- - `/about` and `/en/about` can coexist.
61
- - Non‑default locales behave like in `prefix`.
54
+ - Declared aliases are localized like their record. Non-ASCII paths also get a percent-encoded alias.
55
+ - Under `prefix_except_default`, a page whose default locale has a custom path keeps its original URL as an alias. The redirect middleware sends it on (`/product` -> `/our-products`).
56
+ - Under `prefix`, the original URL of a static page with a custom default path is a redirect record. It carries `meta.i18n` so the runtime can find the page from it.
57
+ - `noPrefixRedirect` (`no_prefix`) redirects the base page to the default locale's custom path.
62
58
 
63
- ### Inputs and configuration
59
+ ---
64
60
 
65
- The main entry point is the `RouteGenerator` class:
61
+ ## API
66
62
 
67
63
  ```ts
68
64
  import type { NuxtPage } from '@nuxt/schema'
69
65
  import { RouteGenerator } from '@i18n-micro/route-strategy'
70
66
 
67
+ // what Nuxt's `pages:resolved` hook passes
68
+ const pages: NuxtPage[] = [{ path: '/about', name: 'about', file: 'pages/about.vue' }]
69
+
71
70
  const generator = new RouteGenerator({
72
- locales, // Array<{ code, iso, name, baseUrl?, baseDefault? }>
73
- defaultLocaleCode, // e.g. 'en'
74
- strategy, // 'no_prefix' | 'prefix' | 'prefix_except_default' | 'prefix_and_default'
75
- globalLocaleRoutes, // Optional: per‑path custom routes per locale
76
- filesLocaleRoutes, // Optional: per‑file routes extracted at build time
77
- routeLocales, // Optional: per‑route locale restrictions
78
- noPrefixRedirect, // Optional: behavior for redirect helpers in no_prefix
71
+ locales: [{ code: 'en' }, { code: 'de' }, { code: 'fr' }],
72
+ defaultLocaleCode: 'en',
73
+ strategy: 'prefix_except_default',
74
+ globalLocaleRoutes: { about: { de: '/ueber-uns' } }, // $defineI18nRoute localeRoutes merged in, config wins
75
+ routeLocales: {}, // $defineI18nRoute locales
76
+ noPrefixRedirect: false,
77
+ excludePatterns: [],
78
+ localizedRouteNamePrefix: 'localized-',
79
+ customRegexMatcher: undefined,
79
80
  })
80
81
 
81
- const pages: NuxtPage[] = [
82
- { path: '/about', name: 'about' },
83
- // ...
84
- ]
85
-
86
- generator.extendPages(pages)
87
- // `pages` is now mutated in‑place and contains localized routes
82
+ generator.extendPages(pages) // replaces `pages` in place
83
+ generator.resolveLocalizedPath('/about', 'de') // '/de/ueber-uns' (nitro routeRules / prerender)
88
84
  ```
89
85
 
90
- Key config fields:
91
-
92
- - **`globalLocaleRoutes`**:
93
- - Map from _canonical path_ or _route name_ to per‑locale paths:
94
- - Example:
95
- ```ts
96
- const globalLocaleRoutes = {
97
- '/about': {
98
- en: '/about',
99
- de: '/ueber-uns',
100
- ru: '/o-nas',
101
- },
102
- }
103
- ```
104
-
105
- - **`filesLocaleRoutes`**:
106
- - Map extracted from files (e.g. Vite/nuxt loader) that describes custom paths defined in code (`$defineI18nRoute`‑like APIs).
107
- - Used as a fallback when there is no explicit `globalLocaleRoutes` entry.
108
-
109
- - **`routeLocales`**:
110
- - Restricts which locales are allowed for a given page path:
111
- ```ts
112
- const routeLocales = {
113
- '/about': ['en', 'de'], // 'ru' will not get localized variants
114
- }
115
- ```
86
+ | Export | Purpose |
87
+ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
88
+ | `RouteGenerator` | `extendPages`, `resolveLocalizedPath`, `generateDataRoutes`, `ensureTranslationFilesExist`; `locales`, `defaultLocale`, `routeLocales` |
89
+ | `generateRoutes(pages, options)` | The generation itself, as a function |
90
+ | `Rules` | Normalized rules: `get(path, name)`, `allowedLocales(path, name)` |
91
+ | `isLocaleAllowedForUnlocalizedRoute` | Whether a locale may serve a path, given routeLocales |
92
+ | `normalizeRouteKey`, `normalizePath` | Path helpers the module shares |
93
+ | `RouteI18nMeta` | Type of `meta.i18n` |
116
94
 
117
95
  ---
118
96
 
119
- ## Internal Architecture (for Contributors)
120
-
121
- > This section is intended for developers working on `@i18n-micro/route-strategy` itself.
122
-
123
- The package is structured around **strategies** and a small **core**:
124
-
125
- - `src/route-generator.ts`
126
- - Thin facade around strategy selection and Nuxt `extendPages` hook contract.
127
- - `src/core/context.ts`
128
- - `GeneratorContext`:
129
- - holds locales, default locale, strategy;
130
- - provides helpers to query:
131
- - `getAllowedLocales` (applies `routeLocales`);
132
- - `getCustomPath` (combines `globalLocaleRoutes` + `filesLocaleRoutes`);
133
- - `localizedPaths` map (used to resolve children/aliases).
134
- - `src/core/localized-paths.ts`
135
- - Responsible for building a normalized map of `{ pathKey -> perLocalePath }`.
136
- - Used heavily by strategies to resolve nested/child routes and aliases.
137
- - `src/core/alias.ts`
138
- - `generateAliasRoutes`:
139
- - creates proper alias routes for each localized variant;
140
- - ensures that aliases inherit children so that `/company/team` works when `/about/team` exists.
141
- - `src/strategies/abstract.ts`
142
- - `BaseStrategy` and helpers:
143
- - common logic for recursion over children;
144
- - helpers for looking up custom paths;
145
- - ensures immutability and deterministic ordering.
146
- - `src/strategies/*.ts`
147
- - Concrete strategies:
148
- - `no-prefix.ts`
149
- - `prefix.ts`
150
- - `prefix-except-default.ts`
151
- - `prefix-and-default.ts`
152
- - Each strategy implements:
153
- - `processPage(page, context)` → `NuxtPage[]`:
154
- - decides how the original page should be localized (or left as‑is);
155
- - uses `localizeChildren` / `localizeChildrenAllLocales` from `BaseStrategy` for deep trees;
156
- - respects `internal`/excluded routes.
157
-
158
- The implementation is heavily covered by snapshot tests under `tests/`:
159
-
160
- - `basic.test.ts` – common/simple scenarios.
161
- - `paths-and-alias.test.ts` – aliases, internal paths, Cloudflare Pages, `filesLocaleRoutes`.
162
- - `locale-restrictions.test.ts` – `routeLocales` behavior.
163
- - `strategies.test.ts` – cross‑strategy matrix.
164
- - `deep-nesting.test.ts` / `advanced.test.ts` – complex trees.
165
- - `critical-scenarios.test.ts` – regression cases.
166
-
167
- When making changes, always run:
97
+ ## Tests
168
98
 
169
99
  ```bash
170
- pnpm --filter @i18n-micro/route-strategy test
100
+ pnpm test # generation rules
101
+ pnpm test:dist # built package
171
102
  ```
172
103
 
173
- Snapshots are considered part of the public contract for this package – avoid changing them unless you are intentionally changing behavior and understand the migration impact.
174
-
175
- ---
176
-
177
- ## Developer Guidelines
178
-
179
- - **Do not depend on Nuxt/Vue Router here**:
180
- - This package must stay framework‑agnostic and operate on `NuxtPage`‑like POJOs only.
181
- - All runtime behaviour (redirects, `$localeRoute`, `$localePath`, etc.) is handled by other packages (`@i18n-micro/core`, `@i18n-micro/path-strategy`, runtime plugins).
182
-
183
- - **Keep strategies deterministic**:
184
- - Given the same input pages and config, output must be stable.
185
- - Do not introduce randomization or time‑dependent behavior.
186
-
187
- - **Prefer small, composable helpers**:
188
- - Logic that is shared between multiple strategies should live in:
189
- - `BaseStrategy`,
190
- - or dedicated helpers in `core/`.
191
- - Avoid copying complex conditionals into each strategy.
192
-
193
- - **Respect performance**:
194
- - `extendPages` is called at build time, and can run on large apps.
195
- - Avoid unnecessary deep clones; reuse objects where safe.
196
- - Prefer simple loops over heavy abstractions.
197
-
198
- ---
199
-
200
- ## Testing & Contribution
201
-
202
- - Tests live in `packages/route-strategy/tests`.
203
- - Jest config: `packages/route-strategy/jest.config.cjs`.
204
- - To run only these tests:
205
-
206
- ```bash
207
- pnpm --filter @i18n-micro/route-strategy test
208
- ```
209
-
210
- If you contribute new behavior:
211
-
212
- 1. Add or update a dedicated test file.
213
- 2. Prefer asserting on the full `pages` array via snapshots to catch regressions.
214
- 3. Document non‑obvious behavior in this README or in inline comments near the relevant strategy/core function.
215
-
216
- ---
217
-
218
- ## Relationship to Other Packages
219
-
220
- - **`@i18n-micro/core`**:
221
- - Uses the routes generated here to implement runtime helpers (`$t`, `$localeRoute`, `$switchLocaleRoute`, etc.).
222
- - Assumes the naming and path conventions from `@i18n-micro/route-strategy` are stable.
223
-
224
- - **`@i18n-micro/path-strategy`**:
225
- - Implements strategy‑aware **frontend** path building for `$localeRoute` / `$localePath`.
226
- - Must remain consistent with how `@i18n-micro/route-strategy` names/structures routes.
227
-
228
- If you change route naming or generation rules here, you almost certainly need to review these packages as well.
104
+ The behaviour of both routing packages together lives in the repository's `test/routing-characterization/` golden files and the `test/routing-*.test.ts` matrices.
@@ -0,0 +1,33 @@
1
+ import { Strategies } from '@i18n-micro/types';
2
+ import { NuxtPage } from '@nuxt/schema';
3
+ import { Rules } from './rules.cjs';
4
+ export interface GenerateOptions {
5
+ strategy: Strategies;
6
+ locales: string[];
7
+ defaultLocale: string;
8
+ rules: Rules;
9
+ namePrefix: string;
10
+ customRegex?: string | RegExp;
11
+ excludePatterns?: (string | RegExp)[];
12
+ apiBaseUrl?: string;
13
+ noPrefixRedirect: boolean;
14
+ /** `false`: no redirect records either, a page's original URL under `prefix` is a 404 like any unprefixed one. */
15
+ redirects?: boolean;
16
+ /**
17
+ * Under prefix_except_default / prefix_and_default: locales that are the default of a domain in
18
+ * the same build. They get unprefixed records too, and prefix_except_default keeps every prefix
19
+ * (the default of one domain is a link target of the others, and the build default of an unknown host).
20
+ */
21
+ hostDefaults?: string[];
22
+ }
23
+ /** The name Nuxt's scanner would give the path: `/a/:id()` -> `a-id`. */
24
+ export declare function nameOfPath(fullPath: string): string;
25
+ /** Route names of the pages, nested ones included. */
26
+ export declare const pageNames: (pages: NuxtPage[]) => string[];
27
+ /** The pattern of a `customRegexMatcher`: `/re/` and `re` alike, and a `RegExp` loses its flags, as in the route and in `path-strategy`. */
28
+ export declare function localeMatcherSource(matcher: string | RegExp): string;
29
+ /** The codes `matcher` cannot route. The whole code must match, without flags: what the router and `path-strategy` will do. */
30
+ export declare function localesOutsideMatcher(codes: readonly string[], matcher: string | RegExp): string[];
31
+ /** Localized full paths (without prefix) by page key and locale, for nitro lookups. */
32
+ export type LocalizedPaths = Map<string, Record<string, string>>;
33
+ export declare function generateRoutes(pages: NuxtPage[], options: GenerateOptions, localizedPaths?: LocalizedPaths): NuxtPage[];
@@ -0,0 +1,33 @@
1
+ import { Strategies } from '@i18n-micro/types';
2
+ import { NuxtPage } from '@nuxt/schema';
3
+ import { Rules } from './rules.js';
4
+ export interface GenerateOptions {
5
+ strategy: Strategies;
6
+ locales: string[];
7
+ defaultLocale: string;
8
+ rules: Rules;
9
+ namePrefix: string;
10
+ customRegex?: string | RegExp;
11
+ excludePatterns?: (string | RegExp)[];
12
+ apiBaseUrl?: string;
13
+ noPrefixRedirect: boolean;
14
+ /** `false`: no redirect records either, a page's original URL under `prefix` is a 404 like any unprefixed one. */
15
+ redirects?: boolean;
16
+ /**
17
+ * Under prefix_except_default / prefix_and_default: locales that are the default of a domain in
18
+ * the same build. They get unprefixed records too, and prefix_except_default keeps every prefix
19
+ * (the default of one domain is a link target of the others, and the build default of an unknown host).
20
+ */
21
+ hostDefaults?: string[];
22
+ }
23
+ /** The name Nuxt's scanner would give the path: `/a/:id()` -> `a-id`. */
24
+ export declare function nameOfPath(fullPath: string): string;
25
+ /** Route names of the pages, nested ones included. */
26
+ export declare const pageNames: (pages: NuxtPage[]) => string[];
27
+ /** The pattern of a `customRegexMatcher`: `/re/` and `re` alike, and a `RegExp` loses its flags, as in the route and in `path-strategy`. */
28
+ export declare function localeMatcherSource(matcher: string | RegExp): string;
29
+ /** The codes `matcher` cannot route. The whole code must match, without flags: what the router and `path-strategy` will do. */
30
+ export declare function localesOutsideMatcher(codes: readonly string[], matcher: string | RegExp): string[];
31
+ /** Localized full paths (without prefix) by page key and locale, for nitro lookups. */
32
+ export type LocalizedPaths = Map<string, Record<string, string>>;
33
+ export declare function generateRoutes(pages: NuxtPage[], options: GenerateOptions, localizedPaths?: LocalizedPaths): NuxtPage[];
package/dist/index.cjs CHANGED
@@ -1,5 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("@i18n-micro/utils/app-path"),l=require("node:path");l=s(l,1);let u=require("node:fs"),d=require("@i18n-micro/core");var f=e=>e.map(e=>({...e})),p=e=>!(!e.redirect||e.file),m=(e,t,n,r=`localized-`)=>n?`${r}${e}-${t}`:`${r}${e}`,h=(e,t)=>e??(t??``).replace(/[^a-z0-9]/gi,`-`).replace(/^-+|-+$/g,``),g=(e,t,n)=>n&&e!==t.code,_=(e,t)=>(typeof e==`string`?e:e.code)===t.code;function v(e,t){let n=e.reduce((e,t)=>{let n=e.find(e=>e.code===t.code);return n?Object.assign(n,t):e.push(t),e},[]).filter(e=>!e.disabled);return{locales:n,defaultLocale:n.find(e=>e.code===t)??{code:t}}}function y(e){return e.split(`/`).map(e=>e.startsWith(`[...`)&&e.endsWith(`]`)?`:${e.substring(4,e.length-1)}(.*)*`:e.startsWith(`[`)&&e.endsWith(`]`)?`:${e.substring(1,e.length-1)}`:e.replace(/:([^()/]+)\(\)/g,`:$1`)).join(`/`)}var b=e=>{if(!e)return``;let t=l.default.posix.normalize(e).replace(/\/+$/,``);return t===`.`?``:t},x=e=>e.startsWith(`/`)?e.slice(1):e;function S(...e){return l.default.posix.join(...e)}function C(e){if(e!==void 0)return e.startsWith(`/`)&&e.endsWith(`/`)?e?.slice(1,-1):e}function w(e){return e}function T(e){return!e||!/[\u0080-\uFFFF]/.test(e)?e:e.split(`/`).map(e=>!e||e.startsWith(`:`)||!/[\u0080-\uFFFF]/.test(e)?e:encodeURI(e)).join(`/`)}function E(e){let t=T(e);return t===e?[]:[t]}function D(e,t,n){let r=C(n?.toString())||(Array.isArray(e)?e.join(`|`):e),i=w(t);return b(l.default.posix.join(`/`,`:locale(${r})`,i))}function O(e){return b(w(e))}var k=(e,t)=>e??(t??``).replace(/[^a-z0-9]/gi,`-`).replace(/^-+|-+$/g,``);function A(e){return x(e)||`/`}function j(e,t,n,r=``){let i={};return e.forEach(e=>{let a=k(e.name,e.path),o=b(S(r,e.path??``)),s=A(o),c=y(o),l=x(c)||`/`,u=t[s]||t[c]||t[l]||t[a];if(u)typeof u==`object`&&(i[s]=u);else{let e=n[a];e&&typeof e==`object`&&!Array.isArray(e)&&(i[s]=e)}if(e.children?.length){let a=b(S(r,e.path??``));Object.assign(i,j(e.children,t,n,a))}}),i}function M(e,t,n){let r=y(t),i=A(t);return e[n]===!1||e[r]===!1||e[i]===!1||e[x(r)]===!1}function N(e,t,n,r){if(!e||Object.keys(e).length===0)return!0;let i=y(n),a=x(i)||`/`,o=a===`/`?`/`:a,s=e[i]??e[n]??e[a]??e[o]??(a===`/`?void 0:e[`/${a}`]);if(!Array.isArray(s)||s.length===0)return!0;let c=t.map(e=>e.code),l=s.filter(e=>c.includes(e));return l.length===0||l.includes(r)}function P(e){let t={};if(!e)return t;for(let n in e){let r=y(n),i=e[n];if(typeof i==`object`){let e={};for(let t in i){let n=i[t];n&&(e[t]=y(n))}t[r]=e}else t[r]=i}return t}var F=class{constructor(e){this.locales=e.locales,this.defaultLocale=this.findLocaleByCode(e.locales,e.defaultLocaleCode)??{code:e.defaultLocaleCode},this.strategy=e.strategy,this.globalLocaleRoutes=P(e.globalLocaleRoutes??{}),this.filesLocaleRoutes=e.filesLocaleRoutes??{},this.routeLocales=e.routeLocales??{},this.localizedPaths=j(e.pages,this.globalLocaleRoutes,this.filesLocaleRoutes),this.activeLocaleCodes=this.locales.filter(e=>e.code!==this.defaultLocale.code||(0,d.isPrefixAndDefaultStrategy)(this.strategy)||(0,d.isPrefixStrategy)(this.strategy)).map(e=>e.code),this.excludePatterns=e.excludePatterns,this.customRegex=e.customRegex,this.noPrefixRedirect=e.noPrefixRedirect??!1,this.localizedRouteNamePrefix=e.localizedRouteNamePrefix??`localized-`,this.fallbackRedirectComponentPath=e.fallbackRedirectComponentPath,this.rawGlobalLocaleRoutes=e.rawGlobalLocaleRoutes}findLocaleByCode(e,t){return e.find(e=>e.code===t)}getAllowedLocales(e,t){let n=this.routeLocales[e]??this.routeLocales[t];return n?.length?n.filter(e=>this.locales.some(t=>t.code===e)):this.locales.map(e=>e.code)}getCustomPath(e,t){let n=A(e);return this.localizedPaths[n]?.[t]}getCustomPathsForPage(e,t){let n=A(e),r=this.localizedPaths[n]??this.localizedPaths[t];if(!r)return;let i={};for(let e in r){let t=r[e];i[e]=t.startsWith(`/`)?t:`/${t}`}return i}hasLocaleRestrictions(e,t){return!!(this.routeLocales[e]??this.routeLocales[t])}filterLocaleCodesWithoutCustomPaths(e){let t=A(e);return this.activeLocaleCodes.filter(e=>!this.localizedPaths[t]?.[e])}};function I(e,t){let n=(t??``).trim();return n.startsWith(`/`)?b(n):b(l.default.posix.join(e||``,n))}var L=e=>e.replace(/^\/?:locale\([^)]*\)/,``)||`/`;function R(e,t){if(!t.startsWith(`/`))return b(t).replace(/^\/+/,``);let n=b(`/${t.replace(/^\/+/,``)}`),r=b(`/${L(e).replace(/^\/+/,``)}`);if(r===`/`||r===``)return n.replace(/^\/+/,``);if(n===r)return``;if(n.startsWith(`${r}/`))return n.slice(r.length+1);let i=n.split(`/`).filter(Boolean),a=r.split(`/`).filter(Boolean),o=0;for(;o<i.length&&o<a.length&&i[o]===a[o];)o++;return i.slice(o).join(`/`)}function z(e,t){let{path:n,name:r,children:i,meta:a,alias:o}=t,s={...e,path:n};return r!==void 0&&(s.name=r),i!==void 0&&(s.children=i),a!==void 0&&(s.meta={...e.meta,...a}),o===void 0?`alias`in t&&(s.alias=void 0,s.meta={...s.meta,alias:void 0}):(s.alias=o,s.meta={...s.meta,alias:o}),s}var B=class{processPage(e,t){if(e.path===void 0)throw Error(`page.path is required`);if(p(e)||e.path&&(0,c.isInternalPath)(e.path,t.excludePatterns))return[e];let n=e.path??``,r=h(e.name,e.path);return M(t.globalLocaleRoutes,n,r)?[e]:this.generateVariants(e,t)}postProcess(e,t){return e}localizeChildren(e,t,n,r,i,a){return e.flatMap(e=>this.localizeChild(e,t,n,r,i,a))}localizeChildrenAllLocales(e,t,n,r,i){return e.flatMap(e=>this.localizeChildAllLocales(e,t,n,r,i))}localizeChildAllLocales(e,t,n,r,i,a=!0){let o=I(n,e.path??``),s=h(e.name,e.path);if(M(i.globalLocaleRoutes,o,s))return[e];let c=I(t,e.path??``),l=i.localizedPaths[A(o)];if(l&&a){let a=r.filter(e=>!l[e]),o=r.filter(e=>l[e]);return[...a.length?this.localizeChildAllLocales({...e},t,n,a,i,!1):[],...o.flatMap(r=>this.localizeChild(e,t,n,r,i,!0))]}let u=x(b(e.path??``)),d=this.localizeChildrenAllLocales(f(e.children??[]),c,o,r,i);return[z(e,{path:u,name:`${i.localizedRouteNamePrefix}${e.name??``}`,children:d})]}localizeChild(e,t,n,r,i,a){let o=I(n,e.path??``),s=h(e.name,e.path);if(M(i.globalLocaleRoutes,o,s))return[e];let c=i.getCustomPath(o,r),l=c?R(t,c):x(b(e.path??``)),u=I(t,l),d=this.localizeChildren(f(e.children??[]),u,o,r,i,a),p=h(e.name,e.path);return[z(e,{path:l,name:`${i.localizedRouteNamePrefix}${p}-${r}`,children:d})]}declaredAliases(e){let t=e.alias??e.meta?.alias;return typeof t==`string`?[t]:Array.isArray(t)?t.filter(e=>typeof e==`string`):[]}routeAliases(e,t,n,r){let i=new Set(E(t));for(let t of this.declaredAliases(e)){let e=n?D(n,t,r.customRegex):b(t);i.add(e);for(let t of E(e))i.add(t)}return[...i]}localizeChildPathsKeepNames(e,t,n,r,i){return e.map(e=>{let a=I(n,e.path??``);if(M(i.globalLocaleRoutes,a,h(e.name,e.path)))return e;let o=i.getCustomPath(a,r),s=o?R(t,o):e.path??``,c=e.children?.length?this.localizeChildPathsKeepNames(e.children,I(t,s),a,r,i):e.children;return o||c!==e.children?z(e,{path:s,children:c}):e})}buildRoutePathForLocales(e,t,n,r,i,a,o){if(r){let t=e.includes(o);return a||!t?D(e,n,i):b(n)}return D(e,t,i)}},V=class extends B{generateVariants(e,t){let n=e.path??``,r=h(e.name,e.path),i=t.getAllowedLocales(n,r),a=t.getCustomPathsForPage(n,r),o=f(e.children??[]),s=[],c=this.routeAliases(e,b(n),null,t);c.length&&c.join(`
2
- `)!==this.declaredAliases(e).join(`
3
- `)&&(e.alias=c,e.meta={...e.meta,alias:c}),s.push(e);let l=new Set;if(a)for(let c of i){let i=a[c];if(!i)continue;let u=b(i),d=b(n);if(u===d)continue;l.add(c);let f=O(i),p=m(r,c,!0),h=this.localizeChildrenForNoPrefix(o,u,n,c,t);s.push(z(e,{path:f,name:p,children:h,alias:[],meta:{alias:[]}})),t.noPrefixRedirect&&c===t.defaultLocale.code&&u!==d&&(e.redirect=u)}for(let a of i)l.has(a)||this.hasChangedChildPath(o,n,a,t)&&s.push(z(e,{path:O(n),name:m(r,a,!0),children:this.localizeChildrenForNoPrefix(o,b(n),n,a,t),alias:[],meta:{alias:[]}}));return s}hasChangedChildPath(e,t,n,r){return e.some(e=>{let i=I(t,e.path??``),a=r.getCustomPath(i,n);return a&&R(t,a)!==x(b(e.path??``))?!0:this.hasChangedChildPath(e.children??[],i,n,r)})}localizeChildrenForNoPrefix(e,t,n,r,i){return e.map(e=>{let a=I(n,e.path??``),o=i.getCustomPath(a,r),s=o?R(t,o):x(b(e.path??``)),c=I(t,s),l=this.localizeChildrenForNoPrefix(f(e.children??[]),c,a,r,i);return z(e,{path:s,name:m(h(e.name,e.path),r,!0),children:l})})}},H=class extends B{generateVariants(e,t){let n=e.path??``,r=h(e.name,e.path),i=t.getAllowedLocales(n,r),a=t.getCustomPathsForPage(n,r),o=f(e.children??[]),s=[];if(a){let c=i.filter(e=>!a[e]);if(c.length){let i=D(c,n,t.customRegex),a=this.routeAliases(e,i,c,t);s.push(z(e,{path:i,name:m(r,c[0],!1,t.localizedRouteNamePrefix),children:this.localizeChildrenAllLocales(o,n,n,c,t),alias:a.length?a:void 0,meta:{alias:a.length?a:[]}}))}for(let c of i){let i=a[c];if(!i)continue;let l=D(c,b(i),t.customRegex),u=m(r,c,!!i,t.localizedRouteNamePrefix),d=i??n,f=this.localizeChildren(o,d,n,c,t,!0),p=this.routeAliases(e,l,[c],t);s.push(z(e,{path:l,name:u,children:f,alias:p.length?p:void 0,meta:{alias:p.length?p:[]}}))}}else{let a=D(i,n,t.customRegex),c=m(r,i[0],!1,t.localizedRouteNamePrefix),l=this.localizeChildrenAllLocales(o,n,n,i,t),u=this.routeAliases(e,a,i,t);s.push(z(e,{path:a,name:c,children:l,alias:u,meta:{alias:u}}))}return s}postProcess(e,t){let n=e.filter(e=>{if(e.name===`index`&&e.path===`/`)return!1;let n=e.path??``;return!!(n&&(0,c.isInternalPath)(n,t.excludePatterns)||M(t.globalLocaleRoutes,n,e.name??``)||/^\/:locale/.test(n)||n===`/`)}),r=t.defaultLocale.code;for(let[e,i]of Object.entries(t.globalLocaleRoutes)){if(i===!1||typeof i!=`object`)continue;let t=i[r];if(!t)continue;let a=e.startsWith(`/`)?e:`/${e}`,o=`/${r}${t.startsWith(`/`)?t:`/${t}`}`;a!==o&&n.push({path:a,name:`redirect-${e}`,redirect:o})}return t.fallbackRedirectComponentPath&&n.push({path:`/:pathMatch(.*)*`,name:`custom-fallback-route`,file:t.fallbackRedirectComponentPath,meta:{globalLocaleRoutes:t.rawGlobalLocaleRoutes??t.globalLocaleRoutes}}),n}},U=class extends B{generateVariants(e,t){let n=e.path??``,r=h(e.name,e.path),i=t.getAllowedLocales(n,r),a=t.getCustomPathsForPage(n,r),o=f(e.children??[]),s=[],c=t.defaultLocale.code;if(i.includes(c)){let r=a?.[c],i=r?b(r):n,l=o.length?this.localizeChildPathsKeepNames(o,i,n,c,t):void 0,u=this.routeAliases(e,i,null,t),d=u.length?{alias:u,meta:{alias:u}}:{},f=u.join(`
4
- `)!==this.declaredAliases(e).join(`
5
- `);s.push(r||l||f?z(e,{path:i,children:l,...d}):e)}else s.push(e);let l=t.locales.filter(e=>i.includes(e.code));if(l.length>0){if(a){for(let r of l){let i=a[r.code];if(i){if(r.code===c){let a=this.createLocalizedRoute(e,[r.code],o,!0,i,t.customRegex,!0,r.code,n,t);a&&s.push(a)}else{let a=this.createLocalizedRoute(e,[r.code],o,!0,i,t.customRegex,!1,r.code,n,t);a&&s.push(a)}}}let r=l.filter(e=>!a[e.code]).map(e=>e.code);if(r.length){let i=this.createLocalizedRoute(e,r,o,!1,``,t.customRegex,!1,r.length!==1||r[0],n,t);i&&s.push(i)}}else{let r=l.map(e=>e.code),i=this.createLocalizedRoute(e,r,o,!1,``,t.customRegex,!1,!0,n,t);i&&s.push(i)}}return s}postProcess(e,t){let n=[],r=[];for(let i of e){let e=i.name??``;typeof e==`string`&&e.startsWith(t.localizedRouteNamePrefix)?r.push(i):n.push(i)}return[...n,...r]}createLocalizedRoute(e,t,n,r,i=``,a,o=!1,s=!1,c,l){let u=this.buildRoutePathForLocales(t,e.path??``,i,r,a,o,l.defaultLocale.code);if(!u||!(r&&i)&&u===e.path||t.length===0)return null;let d=t[0];if(!d)return null;let f=c??e.path??``,p=m(h(e.name??``,f),d,r,l.localizedRouteNamePrefix),g=o||d!==l.defaultLocale.code,_=t.length===1?this.localizeChildren(n,u,f,d,l,g):this.localizeChildrenAllLocales(n,u,f,t,l),v=this.routeAliases(e,u,t,l);return z(e,{path:u,name:p,children:_,alias:v,meta:{alias:v}})}},W=class extends B{generateVariants(e,t){let n=e.path??``,r=h(e.name,e.path),i=t.getAllowedLocales(n,r),a=t.getCustomPathsForPage(n,r),o=f(e.children??[]),s=[],c=t.defaultLocale.code;if(i.includes(c)){let r=a?.[c],i=r?b(r):n,l=r?{[c]:r}:{},u=this.createLocalizedChildren(o,n,[c],!1,!1,!1,l,t),d=this.routeAliases(e,i,null,t);s.push(z(e,{path:i,name:e.name,children:u,...d.length?{alias:d,meta:{alias:d}}:{}}))}let l=t.locales.filter(e=>i.includes(e.code)&&e.code!==c);if(l.length){if(a){for(let r of l){let i=a[r.code];if(!i)continue;let c=this.createLocalizedRoute(e,[r.code],o,!0,i,t.customRegex,!1,r.code,n,t);c&&s.push(c)}let r=l.filter(e=>!a[e.code]).map(e=>e.code);if(r.length){let i=this.createLocalizedRoute(e,r,o,!1,``,t.customRegex,!1,r.length!==1||r[0],n,t);i&&s.push(i)}}else{let r=l.map(e=>e.code),i=this.createLocalizedRoute(e,r,o,!1,``,t.customRegex,!1,!0,n,t);i&&s.push(i)}}return s}postProcess(e,t){let n=[],r=[];for(let i of e){let e=i.name??``;typeof e==`string`&&e.startsWith(t.localizedRouteNamePrefix)?r.push(i):n.push(i)}let i=[...n,...r];return t.fallbackRedirectComponentPath&&i.push({path:`/:pathMatch(.*)*`,name:`custom-fallback-route`,file:t.fallbackRedirectComponentPath,meta:{globalLocaleRoutes:t.rawGlobalLocaleRoutes??t.globalLocaleRoutes}}),i}createLocalizedChildren(e,t,n,r=!0,i=!1,a=!1,o={},s){return e.flatMap(e=>this.createLocalizedVariants(e,t,n,r,i,a,o,s))}createLocalizedVariants(e,t,n,r,i,a=!1,o,s){let c=b(e.path??``),l=A(I(t,e.path??``)),u=s.localizedPaths[l],d=!!u,p=[];if(!d){let l=x(c),u={};for(let e in o)u[e]=I(o[e],l);let d=this.createLocalizedChildren(f(e.children??[]),I(t,e.path??``),n,r,i,a,u,s),m=this.buildChildRouteName(e.name,a,s);return p.push(z(e,{path:l,name:m,children:d})),p}for(let a of n){let n=o?.[a]||t,c=u?.[a],l=c?R(n,c):b(e.path??``),d=x(g(a,s.defaultLocale,i)?D(a,l,s.customRegex):l),m=I(n,x(l)),_=this.createLocalizedChildren(f(e.children??[]),I(t,e.path??``),[a],r,i,a,{...o,[a]:m},s),v=this.buildLocalizedRouteName(h(e.name,e.path),a,r,!!u,s);p.push(z(e,{path:d,name:v,children:_}))}return p}buildChildRouteName(e,t,n){return t===!0?`${n.localizedRouteNamePrefix}${e}`:typeof t==`string`?`${n.localizedRouteNamePrefix}${e}-${t}`:e}createLocalizedRoute(e,t,n,r,i=``,a,o=!1,s=!1,c,l){let u=this.buildRoutePathForLocales(t,e.path??``,i,r,a,o,l.defaultLocale.code);if(!u||u===e.path||t.length===0)return null;let d=t[0];if(!d)return null;let f=c??e.path??``,p=m(h(e.name??``,f),d,r,l.localizedRouteNamePrefix),g=this.routeAliases(e,u,t,l);return z(e,{path:u,name:p,children:this.createLocalizedChildren(n,f,t,!0,!1,s,{[d]:i},l),alias:g,meta:{alias:g}})}buildLocalizedRouteName(e,t,n,r=!1,i){return n?r||t&&!_(t,i.defaultLocale)?`${i.localizedRouteNamePrefix}${e}-${t}`:`${i.localizedRouteNamePrefix}${e}`:e}};function G(e){switch(e){case`no_prefix`:return new V;case`prefix`:return new H;case`prefix_and_default`:return new U;case`prefix_except_default`:return new W;default:throw Error(`Unknown route strategy: ${e}`)}}var K=class{constructor(e){this.localizedPaths={};let{locales:t,defaultLocaleCode:n,strategy:r,globalLocaleRoutes:i,filesLocaleRoutes:a={},routeLocales:o={},noPrefixRedirect:s,excludePatterns:c,localizedRouteNamePrefix:l=`localized-`,customRegexMatcher:u,fallbackRedirectComponentPath:d}=e,f=v(t,n);this.locales=f.locales,this.defaultLocale=f.defaultLocale,this.strategy=r,this.noPrefixRedirect=s,this.excludePatterns=c,this.localizedRouteNamePrefix=l,this.customRegex=u,this.fallbackRedirectComponentPath=d,this.activeLocaleCodes=this.computeActiveLocaleCodes();let p={};for(let e in i){let t=y(e),n=i[e];if(typeof n==`object`){let e={};for(let t in n){let r=n[t];r&&(e[t]=y(r))}p[t]=e}else p[t]=n}this.globalLocaleRoutes=p,this.rawGlobalLocaleRoutes=i??{},this.filesLocaleRoutes=a??{},this.routeLocales=o??{}}computeActiveLocaleCodes(){return this.locales.filter(e=>e.code!==this.defaultLocale.code||(0,d.isPrefixAndDefaultStrategy)(this.strategy)||(0,d.isPrefixStrategy)(this.strategy)).map(e=>e.code)}extendPages(e){let t=new F({locales:this.locales,defaultLocaleCode:this.defaultLocale.code,strategy:this.strategy,globalLocaleRoutes:this.globalLocaleRoutes,filesLocaleRoutes:this.filesLocaleRoutes,routeLocales:this.routeLocales,pages:e,excludePatterns:this.excludePatterns,customRegex:this.customRegex,noPrefixRedirect:this.noPrefixRedirect,localizedRouteNamePrefix:this.localizedRouteNamePrefix,fallbackRedirectComponentPath:this.fallbackRedirectComponentPath,rawGlobalLocaleRoutes:this.rawGlobalLocaleRoutes});this.localizedPaths=t.localizedPaths;let n=G(this.strategy),r=[...e],i=[];for(let e of r)i.push(...n.processPage(e,t));let a=n.postProcess(i,t);e.length=0,e.push(...a)}extractLocalizedPaths(e,t=``){return j(e,this.globalLocaleRoutes,this.filesLocaleRoutes,t)}ensureTranslationFilesExist(e,t,n,r){this.locales.forEach(i=>{let a=l.default.join(n,t,`${i.code}.json`);this.ensureFileExists(a),r||e.forEach(e=>{let r=l.default.join(n,t,`pages`,`${e}/${i.code}.json`);this.ensureFileExists(r)})})}ensureFileExists(e){try{let t=l.default.dirname(e);(0,u.existsSync)(t)||(0,u.mkdirSync)(t,{recursive:!0}),(0,u.existsSync)(e)||(0,u.writeFileSync)(e,JSON.stringify({}),`utf-8`)}catch{}}resolveLocalizedPath(e,t){let n=y(e),r=x(n)||`/`,i,a=this.globalLocaleRoutes[r],o=this.globalLocaleRoutes[n];a&&typeof a==`object`&&!Array.isArray(a)?i=a[t]:o&&typeof o==`object`&&!Array.isArray(o)&&(i=o[t]);let s=!!i,c=i?b(i):b(n)||``,l=!1;if(this.strategy===`no_prefix`?l=!1:this.strategy===`prefix`||this.strategy===`prefix_and_default`?l=!0:this.strategy===`prefix_except_default`&&(l=t!==this.defaultLocale.code),t===this.defaultLocale.code&&!s&&(this.strategy===`prefix_except_default`||this.strategy===`no_prefix`)&&(l=!1),!l)return b(c)||`/`;let u=x(c);return u?b(`/${t}/${u}`):`/${t}`}generateDataRoutes(e,t,n){let r=[],i=this.collectPageNames(e);for(let e of this.locales)if(r.push(`/${t}/index/${e.code}/data.json`),!n)for(let n of i)r.push(`/${t}/${n}/${e.code}/data.json`);return r}collectPageNames(e){let t=new Set,n=e=>{for(let r of e)typeof r.name==`string`&&r.name.length>0&&t.add(r.name),r.children&&r.children.length>0&&n(r.children)};return n(e),[...t]}};exports.RouteGenerator=K,exports.buildEncodedPathAliases=E,exports.buildFullPath=D,exports.buildFullPathNoPrefix=O,exports.buildRouteName=m,exports.cloneArray=f,exports.extractLocalizedPaths=j,Object.defineProperty(exports,"isInternalPath",{enumerable:!0,get:function(){return c.isInternalPath}}),exports.isLocaleAllowedForUnlocalizedRoute=N,exports.isLocaleDefault=_,exports.isLocalizationDisabledForPage=M,exports.isPageRedirectOnly=p,exports.normalizePath=b,exports.normalizeRouteKey=y,exports.removeLeadingSlash=x,exports.shouldAddLocalePrefix=g;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));let c=require("@i18n-micro/utils/app-path"),l=require("node:path");l=s(l,1);let u=require("node:fs");function d(e){if(!e)return``;let t=(0,c.trimTrailingSlashes)(l.default.posix.normalize(e));return t===`.`?``:t}var f=e=>e.startsWith(`/`)?e.slice(1):e,p=e=>d(`/${f(e)}`)||`/`;function m(e,t){return t.startsWith(`/`)?p(t):p(l.default.posix.join(e,t))}function h(e){return e.split(`/`).map(e=>e.replace(/^\[\.\.\.(\w+)\]$/,`:$1(.*)*`).replace(/\[\[(\w+)\]\]/g,`:$1?`).replace(/\[(\w+)\]/g,`:$1`).replace(/:(\w+)\(\)/g,`:$1`)).join(`/`)}var g=new Map;function _(e){let t=g.get(e);return t===void 0&&g.set(e,t=f(d(h(e)))||`/`),t}function v(e,t){if(!t.startsWith(`/`))return f(d(t));let n=p(t),r=p(e);if(r===`/`)return f(n);if(n===r)return``;if(n.startsWith(`${r}/`))return n.slice(r.length+1);let i=n.split(`/`).filter(Boolean),a=r.split(`/`).filter(Boolean),o=0;for(;o<i.length&&i[o]===a[o];)o++;return i.slice(o).join(`/`)}function y(e){let t=p(e).split(`/`).slice(1);if(t[0]?.includes(`:`))return null;let n=t.findIndex(e=>e.endsWith(`*`)||e.endsWith(`+`)||e.endsWith(`?`));return`/${(n===-1?t:[...t.slice(0,n),`**`]).map(e=>e.replace(/^(:\w+)\(.*\)$/,`$1`)).join(`/`)}`}function b(e,t){if(!t.startsWith(`/`))return!0;let n=p(e),r=p(t);return n===`/`||r===n||r.startsWith(`${n}/`)}var x=/[\u0080-￿ "<>`]/;function S(e){if(!x.test(e))return;let t=e.split(`/`).map(e=>e.startsWith(`:`)||!x.test(e)?e:encodeURI(e)).join(`/`);return t===e?void 0:t}var C=e=>Array.from(e.matchAll(/:(\w+)/g),e=>e[1]).sort().join(`,`),w=e=>!e.name&&/(?:^|\/)component-stub(?:\.\w+)?$/.test(e.file??``);function T(e){let t=_(e);return t===`/`?`index`:t.replace(/\/|:|\(.*?\)|[?*+]/g,e=>e===`/`?`-`:``)}function E(e,t,n=``,r=!0){return e.map(e=>{let i=r?p(e.path??``):m(n,e.path??``),a=e.children&&E(e.children,t,i,!1),o=a?{...e,children:a}:e;if(e.name||!e.file||w(e)||e.children?.some(e=>!e.path))return o;let s=T(i);return t.has(s)?o:(t.add(s),{...o,name:s})})}function D(e,t){for(let n of e){let{name:e}=n;e!==void 0&&n.meta?.i18n?.name!==e&&n.meta?.i18n!==void 0&&t.has(e)&&(n.name=O(e,t)),n.name&&t.add(n.name),D(n.children??[],t)}}function O(e,t){let n=2;for(;t.has(`${e}-${n}`);)n++;return`${e}-${n}`}var k=e=>e.flatMap(e=>[...e.name?[e.name]:[],...k(e.children??[])]);function A(e){return e instanceof RegExp?e.source:e.length>1&&e[0]===`/`&&e.endsWith(`/`)?e.slice(1,-1):e}function j(e,t){let n=RegExp(`^(?:${A(t)})$`);return e.filter(e=>!n.test(e))}function M(e,t,n=new Map){let{strategy:r,locales:i,defaultLocale:a,rules:o,namePrefix:s,hostDefaults:l=[]}=t,u=t.customRegex===void 0?void 0:A(t.customRegex),h=(e,t)=>d(`/:locale(${u??e.join(`|`)})/${f(t)}`),g=new WeakMap;function y(e,t,n){let r=g.get(e);if(!r){let i=n?p(e.path??``):m(t,e.path??``);r={original:i,key:_(i),rule:o.get(i,e.name),allowed:o.allowedLocales(i,e.name),noMeta:o.disableMeta(i,e.name)},g.set(e,r)}return r}function x(e){let t=g.get(e)?.noMeta;return t===void 0?e:{...e,meta:{...e.meta,i18nNoMeta:t}}}let T=new Set;function O(e,t,n,r,i){T.has(`${e}\0${t}`)||(T.add(`${e}\0${t}`),console.warn(`[i18n] globalLocaleRoutes["${e}"].${t} = "${n}" is outside its parent "${r}": the page is served at "${i}".`))}function j(e,t,r,i,a,o){let s=e.path??``,{original:c,key:l,rule:u,allowed:d}=y(e,r,a);if(u===!1)return o?{page:e,path:s,children:[],raw:!0}:null;if(t&&d&&!d.includes(t))return null;let f=t&&u?u[t]:void 0,h=f===void 0?s:a?p(f):v(i,f),g=a?p(h):m(i,h);if(f!==void 0&&!a&&!b(i,f)&&O(l,t,f,i,g),t){let e=n.get(l);e?e[t]=g:n.set(l,{[t]:g})}let _=[];for(let n of e.children??[]){let e=j(n,t,c,g,!1,o);e&&_.push(e)}return{page:e,path:h,children:_}}let M=e=>e.raw?`!`:`${e.path}(${e.children.map(M).join(`,`)})`;function N(e,t,n,r,i,a,o=!0){if(e.raw)return x(e.page);let{page:c}=e,l=a?i?h(t,e.path):p(e.path):e.path,u={...c,path:l};if(c.name){u.name=r?`${s}${c.name}${n}`:c.name;let e=g.get(c)?.noMeta;u.meta={...c.meta,i18n:{name:c.name,locales:t,...e===void 0?{}:{noMeta:e}}}}if(c.children&&(u.children=e.children.map(e=>N(e,t,n,r,i,!1))),a){let e=P(o?c:{path:c.path},l,i?t:null);u.alias=e,u.meta={...u.meta,alias:e}}return u}function P(e,t,n){let r=e.alias??e.meta?.alias,i=(typeof r==`string`?[r]:Array.isArray(r)?r:[]).filter(e=>typeof e==`string`),a=new Set;for(let e of[t,...i.map(e=>n?h(n,e):p(e))]){e!==t&&a.add(e);let n=S(e);n&&a.add(n)}return[...a]}function F(e,t,n,r){let i=M(j(e,null,``,``,!0,L)),a=[],o,s=new Map;for(let e of t){let t=r.get(e);if(!t)continue;let c=M(t);if(c===i)a.push(e),o??=t;else{let t=n?e:c;s.set(t,[...s.get(t)??[],e])}}let c=[...s.values()].map(e=>N(r.get(e[0]),e,`-${e[0]}`,!0,n,!0,n));return{plain:a,records:o&&n?[N(o,a,``,!0,!0,!0),...c]:c}}function I(e){let t=new Map;for(let n of[a,...l]){let r=e.get(n);if(!r)continue;let i=M(r);t.set(i,[...t.get(i)??[],n])}return[...t.values()]}let L=r===`prefix`,R=[],z=[];for(let n of E(e,new Set(k(e)))){let e=n.path??``;if(n.redirect&&!n.file||(0,c.isInternalPath)(e,t.excludePatterns,t.apiBaseUrl)||y(n,``,!0).rule===!1){R.push(x(n));continue}if(w(n)){R.push({...n,meta:{...n.meta,i18nStub:!0}});continue}let o=y(n,``,!0).allowed??i,s=o.includes(a),u=new Map(o.map(e=>[e,j(n,e,``,``,!0,L)]));if(r===`no_prefix`){let{plain:e,records:r}=F(n,o,!1,u),i=N(j(n,null,``,``,!0,!0),e,``,!1,!1,!0),s=r.find(e=>(e.meta?.i18n)?.locales.includes(a));if(t.noPrefixRedirect&&s&&s.path!==i.path&&!s.path.includes(`:`)){let e=r.flatMap(e=>e.path.includes(`:`)?[]:e.meta.i18n.locales.map(t=>[t,e.path]));i.meta={...i.meta,i18n:{...i.meta.i18n,paths:Object.fromEntries(e)}}}R.push(i),z.push(...r);continue}if(r!==`prefix`)for(let t of I(u)){let i=t[0]===a,o=N(j(n,t[0],``,``,!0,!0),t,i?``:`-${t[0]}-default`,!i,!1,!0,i),s=p(e);i&&r===`prefix_except_default`&&o.path!==s&&C(o.path)===C(s)&&(o.alias=[...o.alias??[],s,...[S(s)??[]].flat()],o.meta={...o.meta,alias:o.alias,i18n:{...o.meta.i18n,original:s}}),R.push(o)}let d=F(n,r===`prefix_except_default`&&!l.length?o.filter(e=>e!==a):o,!0,u).records;if(z.push(...d),(r===`prefix`||r===`prefix_and_default`)&&s&&n.name&&!e.includes(`:`)){let i=p(u.get(a).path);if(i!==p(e)&&!i.includes(`:`)){if(r===`prefix`&&t.redirects!==!1)R.push({path:p(e),name:`redirect-${n.name}`,redirect:`/${a}${i===`/`?``:i}`,meta:{i18n:{name:n.name,locales:[]}}});else{let t=d.find(e=>e.meta.i18n.locales.includes(a));t.meta={...t.meta,i18n:{...t.meta.i18n,original:p(e)}}}}}}let B=[...R,...z];return D(B,new Set),B}function N(e){let t=e.trim().toLowerCase();return t.endsWith(`:80`)?t.slice(0,-3):t.endsWith(`:443`)?t.slice(0,-4):t}function P(e){if(!e.baseUrl)return null;try{return N(new URL(e.baseUrl).host)}catch{return null}}function F(e){let t=new Map;for(let n of e){let e=P(n);e&&t.set(e,[...t.get(e)??[],n])}return t}var I=(e,t)=>t.filter(t=>{let n=P(t);return!n||n===e||t.disabled});function L(e,t,n){let r=e.filter(e=>!e.disabled),i=r.find(e=>e.baseDefault);return i?i.code:t.some(e=>e.code===n&&!e.disabled)?n:(r[0]??t.find(e=>!e.disabled))?.code}function R(e,t){let n=new Set;for(let[r,i]of F(e)){let a=L(i,I(r,e),t);i.some(e=>e.code===a)&&n.add(a)}return e.map(e=>n.has(e.code)&&!e.baseDefault?Object.assign({},e,{baseDefault:!0}):e)}var z=class{constructor(e={},t={},n=[],r={}){this.rules=new Map,this.byName=new Map,this.restrictions=new Map,this.restrictionsByName=new Map,this.noMeta=new Map,this.noMetaByName=new Map;let i=new Set(n.map(e=>e.code)),a=B(this.rules,this.byName),o=B(this.restrictions,this.restrictionsByName),s=B(this.noMeta,this.noMetaByName);for(let[e,t]of Object.entries(r))s(e,t);for(let[t,n]of Object.entries(e??{})){if(n===!1){a(t,!1);continue}if(!n||typeof n!=`object`)continue;let e={};for(let[r,i]of Object.entries(n)){if(!i)continue;let n=d(h(i)),a=p(n);(a===`/${r}`||a.startsWith(`/${r}/`))&&console.warn(`[i18n] globalLocaleRoutes["${t}"].${r} = "${i}" starts with the locale code; the locale prefix is added automatically.`),e[r]=n}a(t,e)}for(let[e,n]of Object.entries(t)){let t=n.filter(e=>i.has(e));t.length&&o(e,t)}}get(e,t){return this.rules.get(_(e))??(t?this.byName.get(t):void 0)}allowedLocales(e,t){return this.restrictions.get(_(e))??(t?this.restrictionsByName.get(t):void 0)}disableMeta(e,t){return this.noMeta.get(_(e))??(t?this.noMetaByName.get(t):void 0)}customKeysUnder(e,t){let n=[];for(let[r,i]of this.rules)(r===e||r.startsWith(`${e}/`))&&i&&i[t]&&n.push(r);return n}customPath(e,t){let n=this.rules.get(_(e));return n?n[t]:void 0}};function B(e,t){let n=new Set;return(r,i)=>{let a=r.startsWith(`/`);a||t.set(r,i);let o=_(r);a&&e.has(o)&&!n.has(o)||(e.set(o,i),a?n.add(o):n.delete(o))}}function V(e,t,n,r){let i=new z({},e,t).allowedLocales(n,void 0);return!i||i.includes(r)}function H(e,t){let n=new Map;for(let t of e)n.set(t.code,{...t,...n.get(t.code)});let r=[...n.values()],i=r.filter(e=>!e.disabled);return{locales:i,disabledLocales:r.filter(e=>e.disabled),defaultLocale:i.find(e=>e.code===t)??{code:t}}}var U=class{constructor(e){this.localizedPaths=new Map;let t=H(e.locales,e.defaultLocaleCode);this.locales=R(t.locales,e.defaultLocaleCode),this.disabledLocales=t.disabledLocales,this.defaultLocale=t.defaultLocale,this.strategy=e.strategy,this.routeLocales=e.routeLocales??{},this.rules=new z(e.globalLocaleRoutes,this.routeLocales,this.locales,e.routeDisableMeta),this.options=e;let n=e.strategy===`prefix_except_default`||e.strategy===`prefix_and_default`;this.hostDefaults=n?this.locales.filter(e=>e.baseDefault&&e.code!==this.defaultLocale.code).map(e=>e.code):[],this.parentKeys=(e.parentPaths??[]).map(_)}extendPages(e){this.localizedPaths=new Map;let t=M(e,{strategy:this.strategy,locales:this.locales.map(e=>e.code),defaultLocale:this.defaultLocale.code,rules:this.rules,namePrefix:this.options.localizedRouteNamePrefix??`localized-`,customRegex:this.options.customRegexMatcher,excludePatterns:this.options.excludePatterns,apiBaseUrl:this.options.apiBaseUrl,noPrefixRedirect:this.options.noPrefixRedirect,redirects:this.options.redirects,hostDefaults:this.hostDefaults},this.localizedPaths);e.splice(0,e.length,...t)}resolveLocalizedPath(e,t){let n=_(e);return this.withPrefix(this.localizedPaths.get(n)?.[t]??this.rules.customPath(e,t)??e,t)}withPrefix(e,t){if(!(this.strategy===`prefix`||this.strategy===`prefix_and_default`||this.strategy===`prefix_except_default`&&t!==this.defaultLocale.code))return p(e);let n=f(p(e));return n?`/${t}/${n}`:`/${t}`}localizedKey(e,t){let n=this.rules.customPath(e,t),r=this.parentKeys.filter(t=>e.startsWith(`${t}/`)).sort((e,t)=>t.length-e.length)[0];if(!r)return p(n??e);let i=this.localizedKey(r,t);return m(i,n===void 0?e.slice(r.length+1):v(i,n))}resolveLocalizedRulePaths(e,t){let n=new Set([this.resolveLocalizedPath(e,t)]);if(e.endsWith(`/**`)){let r=_(e.slice(0,-3));for(let e of this.rules.customKeysUnder(r,t)){let i=y(this.localizedKey(e,t));i&&n.add(this.withPrefix(e===r?`${i}/**`:i,t))}}if(this.hostDefaults.includes(t))for(let e of[...n])n.add(`/${e.split(`/`).slice(2).join(`/`)}`);return[...n]}ensureTranslationFilesExist(e,t,n,r){for(let i of this.locales)if(W(l.default.join(n,t,`${i.code}.json`)),!r)for(let r of e)W(l.default.join(n,t,`pages`,`${r}/${i.code}.json`))}generateDataRoutes(e,t,n){let r=new Set(k(e)),i=[];for(let e of this.locales)if(i.push(`/${t}/index/${e.code}/data.json`),!n)for(let n of r)i.push(`/${t}/${n}/${e.code}/data.json`);return i}};function W(e){try{(0,u.mkdirSync)(l.default.dirname(e),{recursive:!0}),(0,u.existsSync)(e)||(0,u.writeFileSync)(e,`{}`,`utf-8`)}catch{}}exports.RouteGenerator=U,exports.Rules=z,exports.generateRoutes=M,exports.isLocaleAllowedForUnlocalizedRoute=V,exports.localeMatcherSource=A,exports.localesOutsideMatcher=j,exports.nameOfPath=T,exports.normalizePath=d,exports.normalizeRouteKey=h,exports.pageNames=k,exports.routeKey=_;
package/dist/index.d.cts CHANGED
@@ -1,6 +1,5 @@
1
- import { extractLocalizedPaths, LocalizedPathsMap } from './core/localized-paths';
2
- import { isLocalizationDisabledForPage } from './core/localization-disabled';
3
- import { isLocaleAllowedForUnlocalizedRoute } from './locale-route-access';
4
- import { RouteGenerator, RouteGeneratorOptions } from './route-generator';
5
- import { buildEncodedPathAliases, buildFullPath, buildFullPathNoPrefix, buildRouteName, cloneArray, isInternalPath, isLocaleDefault, isPageRedirectOnly, normalizePath, normalizeRouteKey, removeLeadingSlash, shouldAddLocalePrefix } from './utils';
6
- export { RouteGenerator, type RouteGeneratorOptions, isLocaleAllowedForUnlocalizedRoute, extractLocalizedPaths, type LocalizedPathsMap, isLocalizationDisabledForPage, isInternalPath, normalizeRouteKey, normalizePath, cloneArray, isPageRedirectOnly, removeLeadingSlash, buildRouteName, shouldAddLocalePrefix, isLocaleDefault, buildEncodedPathAliases, buildFullPath, buildFullPathNoPrefix, };
1
+ export type { RouteI18nMeta } from '@i18n-micro/types';
2
+ export { type GenerateOptions, generateRoutes, localeMatcherSource, localesOutsideMatcher, nameOfPath, pageNames } from './generate.cjs';
3
+ export { normalizePath, normalizeRouteKey, routeKey } from './paths.cjs';
4
+ export { RouteGenerator, type RouteGeneratorOptions } from './route-generator.cjs';
5
+ export { isLocaleAllowedForUnlocalizedRoute, Rules } from './rules.cjs';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { extractLocalizedPaths, LocalizedPathsMap } from './core/localized-paths';
2
- import { isLocalizationDisabledForPage } from './core/localization-disabled';
3
- import { isLocaleAllowedForUnlocalizedRoute } from './locale-route-access';
4
- import { RouteGenerator, RouteGeneratorOptions } from './route-generator';
5
- import { buildEncodedPathAliases, buildFullPath, buildFullPathNoPrefix, buildRouteName, cloneArray, isInternalPath, isLocaleDefault, isPageRedirectOnly, normalizePath, normalizeRouteKey, removeLeadingSlash, shouldAddLocalePrefix } from './utils';
6
- export { RouteGenerator, type RouteGeneratorOptions, isLocaleAllowedForUnlocalizedRoute, extractLocalizedPaths, type LocalizedPathsMap, isLocalizationDisabledForPage, isInternalPath, normalizeRouteKey, normalizePath, cloneArray, isPageRedirectOnly, removeLeadingSlash, buildRouteName, shouldAddLocalePrefix, isLocaleDefault, buildEncodedPathAliases, buildFullPath, buildFullPathNoPrefix, };
1
+ export type { RouteI18nMeta } from '@i18n-micro/types';
2
+ export { type GenerateOptions, generateRoutes, localeMatcherSource, localesOutsideMatcher, nameOfPath, pageNames } from './generate.js';
3
+ export { normalizePath, normalizeRouteKey, routeKey } from './paths.js';
4
+ export { RouteGenerator, type RouteGeneratorOptions } from './route-generator.js';
5
+ export { isLocaleAllowedForUnlocalizedRoute, Rules } from './rules.js';