@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 +66 -190
- package/dist/generate.d.cts +33 -0
- package/dist/generate.d.ts +33 -0
- package/dist/index.cjs +1 -5
- package/dist/index.d.cts +5 -6
- package/dist/index.d.ts +5 -6
- package/dist/index.mjs +402 -558
- package/dist/paths.d.cts +32 -0
- package/dist/paths.d.ts +32 -0
- package/dist/route-generator.d.cts +58 -0
- package/dist/route-generator.d.ts +35 -36
- package/dist/rules.d.cts +35 -0
- package/dist/rules.d.ts +35 -0
- package/package.json +21 -21
- package/dist/core/builder.d.ts +0 -31
- package/dist/core/context.d.ts +0 -53
- package/dist/core/localization-disabled.d.ts +0 -3
- package/dist/core/localized-paths.d.ts +0 -20
- package/dist/locale-route-access.d.ts +0 -6
- package/dist/strategies/abstract.d.ts +0 -47
- package/dist/strategies/factory.d.ts +0 -3
- package/dist/strategies/no-prefix.d.ts +0 -13
- package/dist/strategies/prefix-and-default.d.ts +0 -8
- package/dist/strategies/prefix-except-default.d.ts +0 -12
- package/dist/strategies/prefix.d.ts +0 -7
- package/dist/strategies/types.d.ts +0 -17
- package/dist/utils/common.d.ts +0 -9
- package/dist/utils/index.d.cts +0 -3
- package/dist/utils/index.d.ts +0 -3
- package/dist/utils/locales.d.ts +0 -9
- package/dist/utils/path.d.ts +0 -8
package/README.md
CHANGED
|
@@ -1,228 +1,104 @@
|
|
|
1
1
|
# @i18n-micro/route-strategy
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
5
|
+
The Nuxt module calls it in `pages:resolved`. It works on plain `NuxtPage` objects, without Nuxt or vue-router.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- `prefix`
|
|
9
|
-
- `prefix_except_default`
|
|
10
|
-
- `prefix_and_default`
|
|
7
|
+
---
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
## How it works
|
|
13
10
|
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
+
## Rules
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
### Keys
|
|
37
33
|
|
|
38
|
-
|
|
34
|
+
`globalLocaleRoutes` keys and `$defineI18nRoute` file keys are looked up in this order:
|
|
39
35
|
|
|
40
|
-
|
|
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
|
-
|
|
39
|
+
When both `about-team` and `/about-team` exist for a top-level page, the form without the slash wins.
|
|
43
40
|
|
|
44
|
-
|
|
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
|
-
-
|
|
50
|
-
|
|
51
|
-
-
|
|
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
|
-
|
|
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
|
-
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
59
|
+
---
|
|
64
60
|
|
|
65
|
-
|
|
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
|
|
73
|
-
defaultLocaleCode
|
|
74
|
-
strategy
|
|
75
|
-
globalLocaleRoutes, //
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
##
|
|
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
|
|
100
|
+
pnpm test # generation rules
|
|
101
|
+
pnpm test:dist # built package
|
|
171
102
|
```
|
|
172
103
|
|
|
173
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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';
|