@i18n-micro/astro 1.3.24 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +8 -64
- package/dist/auto-middleware.d.ts +1 -0
- package/dist/client/core.d.cts +86 -0
- package/dist/client/core.d.ts +71 -8
- package/dist/client/index.d.cts +2 -6
- package/dist/client/index.d.ts +2 -6
- package/dist/client/index.js +1 -6
- package/dist/client/preact.d.ts +12 -23
- package/dist/client/preact.js +13 -37
- package/dist/client/react.d.ts +14 -21
- package/dist/client/react.js +12 -37
- package/dist/client/svelte.d.ts +14 -14
- package/dist/client/svelte.js +17 -47
- package/dist/client/vue.d.ts +15 -15
- package/dist/client/vue.js +37 -2
- package/dist/composer.d.cts +16 -0
- package/dist/composer.d.ts +10 -30
- package/dist/core-CgrgdmGy.cjs +1 -0
- package/dist/core-DTAcn2yF.js +72 -0
- package/dist/env.d.ts +8 -2
- package/dist/index.cjs +11 -3
- package/dist/index.d.cts +10 -13
- package/dist/index.d.ts +10 -13
- package/dist/index.mjs +171 -482
- package/dist/integration.d.cts +26 -0
- package/dist/integration.d.ts +14 -15
- package/dist/middleware-BT3OA5YL.cjs +1 -0
- package/dist/middleware-Ch8E0iV_.js +257 -0
- package/dist/middleware.d.cts +34 -0
- package/dist/middleware.d.ts +17 -8
- package/dist/middleware.js +23 -0
- package/dist/node.d.ts +26 -0
- package/dist/node.js +22 -0
- package/dist/request.d.ts +69 -0
- package/dist/router/adapter.d.cts +9 -0
- package/dist/router/adapter.d.ts +5 -4
- package/dist/router/types.d.cts +66 -0
- package/dist/router/types.d.ts +3 -2
- package/dist/utils.d.cts +112 -0
- package/dist/utils.d.ts +39 -35
- package/dist/virtual.d.ts +21 -0
- package/package.json +28 -15
- package/src/auto-middleware.ts +26 -0
- package/src/client/core.ts +124 -59
- package/src/client/index.ts +2 -11
- package/src/client/preact.tsx +12 -127
- package/src/client/react.tsx +11 -123
- package/src/client/svelte.ts +6 -69
- package/src/client/vue.ts +22 -77
- package/src/components/i18n-link.astro +2 -59
- package/src/components/i18n-switcher.astro +46 -149
- package/src/components/i18n-t.astro +6 -54
- package/src/composer.ts +25 -115
- package/src/env.d.ts +7 -0
- package/src/index.ts +1 -6
- package/src/integration.ts +113 -52
- package/src/middleware.ts +174 -120
- package/src/node.ts +51 -0
- package/src/request.ts +177 -0
- package/src/router/adapter.ts +47 -149
- package/src/router/types.ts +3 -2
- package/src/utils.ts +149 -323
- package/src/virtual.d.ts +21 -0
- package/dist/components/index.d.cts +0 -0
- package/dist/components/index.d.ts +0 -0
- package/dist/core-DwkpOV-H.cjs +0 -1
- package/dist/core-VSnkGRWR.js +0 -28
- package/dist/load-translations.d.ts +0 -43
- package/dist/routing-context.d.ts +0 -4
- package/dist/vue-BNiEwZr2.cjs +0 -5
- package/dist/vue-CWHjIOiS.js +0 -1724
- package/src/components/index.ts +0 -11
- package/src/load-translations.ts +0 -106
- package/src/routing-context.ts +0 -23
package/src/request.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { externalLinkHref, pathEnd, withoutDots } from '@i18n-micro/core'
|
|
2
|
+
import { trimTrailingSlashes, withoutAppBaseURL } from '@i18n-micro/utils/app-path'
|
|
3
|
+
import { localeFromPath } from '@i18n-micro/utils/resolve-locale'
|
|
4
|
+
import type { I18nRoutingStrategy } from './router/types'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The one request model. Everything below works on the page's path without Astro's `base`:
|
|
8
|
+
* the middleware strips it once, every URL that goes out gets it back from {@link withBase}.
|
|
9
|
+
*/
|
|
10
|
+
export interface I18nRouting {
|
|
11
|
+
defaultLocale: string
|
|
12
|
+
/** Codes of the locales in service: a disabled one is neither a URL's locale nor a redirect target. */
|
|
13
|
+
locales: string[]
|
|
14
|
+
/** `''` or `/docs`, no trailing slash. */
|
|
15
|
+
base: string
|
|
16
|
+
strategy: I18nRoutingStrategy
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The parts of an Astro context a request is read from: also a mock in tests. */
|
|
20
|
+
export interface RequestContext {
|
|
21
|
+
url: URL
|
|
22
|
+
params?: Record<string, string | undefined>
|
|
23
|
+
routePattern?: string
|
|
24
|
+
isPrerendered?: boolean
|
|
25
|
+
request: { headers: Headers; method?: string }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface I18nRequest {
|
|
29
|
+
/** The page this request is for: another URL than `context.url` for a server island or action. */
|
|
30
|
+
url: URL
|
|
31
|
+
/** A server island or action: it belongs to the page in `url`, and is no page view itself. */
|
|
32
|
+
partOfPage: boolean
|
|
33
|
+
/** `url.pathname` without `base`. */
|
|
34
|
+
path: string
|
|
35
|
+
/** The locale `url` names, `null` for an unprefixed URL. */
|
|
36
|
+
urlLocale: string | null
|
|
37
|
+
/** The URL's locale, else the default one: an unprefixed URL is the default locale's page. */
|
|
38
|
+
locale: string
|
|
39
|
+
route: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const normalizeBase = (base: string | undefined): string => trimTrailingSlashes(base ?? '')
|
|
43
|
+
|
|
44
|
+
/** `path`, which has no `base`, as a URL of the site. */
|
|
45
|
+
export function withBase(path: string, base: string): string {
|
|
46
|
+
if (!base) return path
|
|
47
|
+
return path === '/' ? base : `${base}${path}`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A request header, or null where there is none (static prerendering). */
|
|
51
|
+
export function header(context: Pick<RequestContext, 'request'>, name: string): string | null {
|
|
52
|
+
try {
|
|
53
|
+
return context.request.headers.get(name)
|
|
54
|
+
} catch {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The URL of the page this request comes from. A malformed header is no page, and prerendering has no visitor. */
|
|
60
|
+
function refererUrl(context: RequestContext): URL | null {
|
|
61
|
+
const referer = context.isPrerendered ? null : header(context, 'referer')
|
|
62
|
+
if (!referer) return null
|
|
63
|
+
try {
|
|
64
|
+
return new URL(referer)
|
|
65
|
+
} catch {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Whether `from` is this site. The browser's word first: behind a TLS proxy `context.url` may be the
|
|
72
|
+
* internal URL. Without that header the host decides, and a proxy needs `security.allowedDomains`.
|
|
73
|
+
*/
|
|
74
|
+
function isSameSite(context: RequestContext, from: URL): boolean {
|
|
75
|
+
const site = header(context, 'sec-fetch-site')
|
|
76
|
+
return site ? site === 'same-origin' : from.host === context.url.host
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A server island or action has no locale in its URL: it is a part of the page that asked for it.
|
|
81
|
+
* Any origin counts here: a forged `Referer` changes only the forger's own answer, and the internal
|
|
82
|
+
* URL behind a proxy matches no public one. The page's path, on this server's origin.
|
|
83
|
+
* ponytail: without a `Referer` (a `no-referrer` policy) it renders in the default locale, and on a
|
|
84
|
+
* dynamic page its route name comes from the URL; route patterns in the config would settle that.
|
|
85
|
+
*/
|
|
86
|
+
function pageOfPart(context: RequestContext): URL | null {
|
|
87
|
+
const from = refererUrl(context)
|
|
88
|
+
return from && new URL(from.pathname + from.search, context.url)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const isPartOfPage = (pattern: string | undefined): boolean =>
|
|
92
|
+
pattern?.startsWith('/_server-islands/') === true || pattern?.startsWith('/_actions/') === true
|
|
93
|
+
|
|
94
|
+
/** The route's pattern, a `[locale]` param replaced by its value so the locale drops out of the name. */
|
|
95
|
+
function pagePattern(context: RequestContext, locales: string[]): string | undefined {
|
|
96
|
+
const { routePattern: pattern, params } = context
|
|
97
|
+
if (!pattern) return undefined
|
|
98
|
+
const first = /^\/\[([^\].]+)\]/.exec(pattern)
|
|
99
|
+
const value = first ? params?.[first[1]!] : undefined
|
|
100
|
+
if (!value) return pattern
|
|
101
|
+
const rest = pattern.slice(first![0].length)
|
|
102
|
+
if (locales.includes(value)) return `/${value}${rest}`
|
|
103
|
+
// a disabled locale still matches `[locale]`: it is no part of the page's name
|
|
104
|
+
return first![1] === 'locale' ? rest || '/' : pattern
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Locale a path names: its prefix, or what the strategy reads from it when that is not the default
|
|
109
|
+
* (a strategy's `getLocaleFromPath` answers the default for a URL without a locale).
|
|
110
|
+
*/
|
|
111
|
+
export function localeOfPath(path: string, { defaultLocale, locales, strategy }: Omit<I18nRouting, 'base'>): string | null {
|
|
112
|
+
const prefix = localeFromPath(path, locales)
|
|
113
|
+
if (prefix) return prefix
|
|
114
|
+
const read = strategy.getLocaleFromPath?.(path, defaultLocale, locales) ?? null
|
|
115
|
+
return read !== defaultLocale && read !== null && locales.includes(read) ? read : null
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function resolveRequest(context: RequestContext, routing: I18nRouting): I18nRequest {
|
|
119
|
+
const { strategy, locales, defaultLocale, base } = routing
|
|
120
|
+
const partOfPage = isPartOfPage(context.routePattern)
|
|
121
|
+
const url = (partOfPage && pageOfPart(context)) || context.url
|
|
122
|
+
const path = withoutAppBaseURL(url.pathname, base)
|
|
123
|
+
const urlLocale = localeOfPath(path, routing)
|
|
124
|
+
const pattern = partOfPage ? undefined : pagePattern(context, locales)
|
|
125
|
+
return { url, partOfPage, path, urlLocale, locale: urlLocale ?? defaultLocale, route: strategy.getRouteName!(path, locales, pattern) }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Whether the visitor comes from a page of this site in another locale. */
|
|
129
|
+
export function isSwitch(context: RequestContext, request: I18nRequest, routing: I18nRouting): boolean {
|
|
130
|
+
const from = refererUrl(context)
|
|
131
|
+
if (!from || !isSameSite(context, from)) return false
|
|
132
|
+
const locale = localeOfPath(withoutAppBaseURL(from.pathname, routing.base), routing) ?? routing.defaultLocale
|
|
133
|
+
return locale !== request.locale
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** A document may be asked for by this request: an island, an action or a form post is no landing. */
|
|
137
|
+
export function asksForPage(context: RequestContext, request: I18nRequest): boolean {
|
|
138
|
+
const method = context.request.method ?? 'GET'
|
|
139
|
+
return !request.partOfPage && !context.isPrerendered && (method === 'GET' || method === 'HEAD')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The visitor did see this response as a page: no endpoint, image, 404 or redirect, and not a
|
|
144
|
+
* prefetch of a link they may never follow.
|
|
145
|
+
* ponytail: a `fetch` of an HTML fragment from a page of another locale still counts; `<ClientRouter>`
|
|
146
|
+
* navigates by `fetch` too, and the two look the same from here.
|
|
147
|
+
*/
|
|
148
|
+
export function isPageView(context: RequestContext, response: Response): boolean {
|
|
149
|
+
if (!response.ok || !response.headers.get('content-type')?.startsWith('text/html')) return false
|
|
150
|
+
return !`${header(context, 'sec-purpose')} ${header(context, 'purpose')}`.includes('prefetch')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** A scheme this router has no business with: `sms:`, `ftp:`, `javascript:`. `externalLinkHref` handles the common ones. */
|
|
154
|
+
const SCHEME = /^[a-z][a-z\d+.-]*:/i
|
|
155
|
+
|
|
156
|
+
export interface HrefContext extends Pick<I18nRouting, 'base' | 'defaultLocale' | 'strategy'> {
|
|
157
|
+
codes: string[]
|
|
158
|
+
/** What a relative `href` resolves against: the page's path. */
|
|
159
|
+
from?: string
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* `href` as a link to `locale`. What leaves the app, and a bare `#hash` or `?query`, stays as
|
|
164
|
+
* written; a route is localized and keeps its query, hash and trailing slash.
|
|
165
|
+
*/
|
|
166
|
+
export function localizeHref(href: string, locale: string, context: HrefContext): string {
|
|
167
|
+
if (href === '' || href[0] === '#' || href[0] === '?') return href
|
|
168
|
+
const external = externalLinkHref(href) ?? (SCHEME.test(href) ? href : undefined)
|
|
169
|
+
if (external) return external
|
|
170
|
+
const { strategy, codes, defaultLocale, base, from = '/' } = context
|
|
171
|
+
const cut = pathEnd(href)
|
|
172
|
+
const raw = cut === -1 ? href : href.slice(0, cut)
|
|
173
|
+
const path = withoutDots(raw[0] === '/' ? withoutAppBaseURL(raw, base) : `${trimTrailingSlashes(from)}/${raw}`)
|
|
174
|
+
const localized = strategy.localizePath!(path, locale, codes, defaultLocale)
|
|
175
|
+
const slash = raw.length > 1 && raw.endsWith('/') && !localized.endsWith('/') ? '/' : ''
|
|
176
|
+
return `${withBase(localized + slash, base)}${cut === -1 ? '' : href.slice(cut)}`
|
|
177
|
+
}
|
package/src/router/adapter.ts
CHANGED
|
@@ -1,166 +1,64 @@
|
|
|
1
|
+
import { pathSegments, urlPath } from '@i18n-micro/core'
|
|
1
2
|
import type { Locale } from '@i18n-micro/types'
|
|
3
|
+
import { trimLeadingSlashes } from '@i18n-micro/utils/app-path'
|
|
4
|
+
import { localeFromPath } from '@i18n-micro/utils/resolve-locale'
|
|
2
5
|
import type { I18nRoutingStrategy } from './types'
|
|
3
6
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
* Implements routing utilities for Astro file-based routing
|
|
7
|
-
* Uses standard Astro APIs: Astro.url, context.url
|
|
8
|
-
*/
|
|
9
|
-
export function createAstroRouterAdapter(locales: Locale[], defaultLocale: string, getCurrentUrl?: () => URL): I18nRoutingStrategy {
|
|
10
|
-
const localeCodes = locales.map((loc) => loc.code)
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Get route name from Astro path
|
|
14
|
-
* Extracts route name from path (e.g., /en/about -> about)
|
|
15
|
-
*/
|
|
16
|
-
const getRouteName = (path: string, locales: string[] = []): string => {
|
|
17
|
-
const cleanPath = path.replace(/^\//, '').replace(/\/$/, '')
|
|
18
|
-
|
|
19
|
-
if (!cleanPath) {
|
|
20
|
-
return 'index'
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const segments = cleanPath.split('/').filter(Boolean)
|
|
24
|
-
|
|
25
|
-
// Remove locale from path if present
|
|
26
|
-
const firstSegment = segments[0]
|
|
27
|
-
if (firstSegment && locales.includes(firstSegment)) {
|
|
28
|
-
segments.shift()
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (segments.length === 0) {
|
|
32
|
-
return 'index'
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return segments.join('-')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get locale from path
|
|
40
|
-
* Checks if first segment is a locale code
|
|
41
|
-
*/
|
|
42
|
-
const getLocaleFromPath = (path: string, defaultLocale: string = 'en', locales: string[] = []): string => {
|
|
43
|
-
const segments = path.split('/').filter(Boolean)
|
|
44
|
-
const firstSegment = segments[0]
|
|
45
|
-
if (firstSegment && locales.includes(firstSegment)) {
|
|
46
|
-
return firstSegment
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return defaultLocale
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Switch locale in path
|
|
54
|
-
* Replaces or adds locale prefix to path
|
|
55
|
-
*/
|
|
56
|
-
const switchLocalePath = (path: string, newLocale: string, locales: string[] = [], defaultLocale?: string): string => {
|
|
57
|
-
const segments = path.split('/').filter(Boolean)
|
|
7
|
+
/** A dynamic segment by its name: `[slug]` and `[...slug]` are `slug`, as a page dictionary's folder names them. */
|
|
8
|
+
const PARAM = /\[(?:\.\.\.)?([^\]]+)\]/g
|
|
58
9
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// Add new locale if not default or if default should be included
|
|
66
|
-
if (newLocale !== defaultLocale || defaultLocale === undefined) {
|
|
67
|
-
segments.unshift(newLocale)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return `/${segments.join('/')}`
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Localize path with locale prefix
|
|
75
|
-
*/
|
|
76
|
-
const localizePath = (path: string, locale: string, locales: string[] = [], defaultLocale?: string): string => {
|
|
77
|
-
const cleanPath = path.replace(/^\//, '').replace(/\/$/, '') || ''
|
|
78
|
-
const segments = cleanPath.split('/').filter(Boolean)
|
|
10
|
+
function segmentsOf(path: string, locales: readonly string[]): string[] {
|
|
11
|
+
const segments = pathSegments(urlPath(path))
|
|
12
|
+
if (segments[0] !== undefined && locales.includes(segments[0])) segments.shift()
|
|
13
|
+
return segments
|
|
14
|
+
}
|
|
79
15
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (firstSegment && locales.includes(firstSegment)) {
|
|
83
|
-
segments.shift()
|
|
84
|
-
}
|
|
16
|
+
/** The query and the hash of `path`, as they are. */
|
|
17
|
+
const extrasOf = (path: string): string => path.slice(urlPath(path).length)
|
|
85
18
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
19
|
+
/** `/en/blog/post` → `blog-post`, `/blog/[slug]` → `blog-slug`, `/` → `index`. */
|
|
20
|
+
export function routeNameFromPath(path: string, locales: readonly string[] = []): string {
|
|
21
|
+
const segments = segmentsOf(path, locales)
|
|
22
|
+
if (segments.length === 0) return 'index'
|
|
23
|
+
const name = segments.join('-')
|
|
24
|
+
return name.includes('[') ? name.replace(PARAM, '$1') : name
|
|
25
|
+
}
|
|
90
26
|
|
|
91
|
-
|
|
92
|
-
}
|
|
27
|
+
const removeLocale = (path: string, locales: readonly string[] = []): string => `/${segmentsOf(path, locales).join('/')}${extrasOf(path)}`
|
|
93
28
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (firstSegment && locales.includes(firstSegment)) {
|
|
101
|
-
segments.shift()
|
|
102
|
-
}
|
|
103
|
-
return `/${segments.join('/')}`
|
|
104
|
-
}
|
|
29
|
+
/** `path` in `locale`: no prefix for the default locale, or for every locale when no default is given and `locale` is empty. */
|
|
30
|
+
function addLocale(path: string, locale: string, locales: readonly string[] = [], defaultLocale?: string): string {
|
|
31
|
+
const segments = segmentsOf(path, locales)
|
|
32
|
+
if (locale !== defaultLocale) segments.unshift(locale)
|
|
33
|
+
return `/${segments.join('/')}${extrasOf(path)}`
|
|
34
|
+
}
|
|
105
35
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Prefix-except-default routing over plain paths. Paths here never carry Astro's `base`: the
|
|
38
|
+
* middleware strips it from what comes in and adds it to what goes out.
|
|
39
|
+
*/
|
|
40
|
+
export function createAstroRouterAdapter(locales: Locale[], defaultLocale: string, getCurrentUrl?: () => URL): I18nRoutingStrategy {
|
|
41
|
+
const localeCodes = locales.map((locale) => locale.code)
|
|
42
|
+
const currentUrl = () => getCurrentUrl?.() ?? (typeof window !== 'undefined' ? new URL(window.location.href) : null)
|
|
113
43
|
|
|
114
44
|
return {
|
|
115
|
-
getCurrentPath: () =>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
return '/'
|
|
125
|
-
},
|
|
126
|
-
getRouteName,
|
|
127
|
-
getLocaleFromPath,
|
|
128
|
-
switchLocalePath,
|
|
129
|
-
localizePath,
|
|
130
|
-
removeLocaleFromPath,
|
|
131
|
-
resolvePath,
|
|
45
|
+
getCurrentPath: () => currentUrl()?.pathname ?? '/',
|
|
46
|
+
// the pattern names one route for every URL it matches: `/blog/hello` is `blog-slug`
|
|
47
|
+
getRouteName: (path, codes = [], pattern) => routeNameFromPath(pattern ?? path, codes),
|
|
48
|
+
getLocaleFromPath: (path, fallback = 'en', codes = []) => localeFromPath(`/${trimLeadingSlashes(path)}`, codes) ?? fallback,
|
|
49
|
+
switchLocalePath: addLocale,
|
|
50
|
+
localizePath: addLocale,
|
|
51
|
+
removeLocaleFromPath: removeLocale,
|
|
52
|
+
resolvePath: (to, locale) => addLocale(typeof to === 'string' ? to : to.path || '/', locale, localeCodes, defaultLocale),
|
|
132
53
|
getRoute: () => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const url = getCurrentUrl()
|
|
136
|
-
return {
|
|
137
|
-
fullPath: url.pathname + url.search,
|
|
138
|
-
query: Object.fromEntries(url.searchParams),
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
// Fallback for client-side islands
|
|
142
|
-
if (typeof window !== 'undefined') {
|
|
143
|
-
const url = new URL(window.location.href)
|
|
144
|
-
return {
|
|
145
|
-
fullPath: url.pathname + url.search,
|
|
146
|
-
query: Object.fromEntries(url.searchParams),
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return {
|
|
150
|
-
fullPath: '/',
|
|
151
|
-
query: {},
|
|
152
|
-
}
|
|
54
|
+
const url = currentUrl()
|
|
55
|
+
return url ? { fullPath: url.pathname + url.search, query: Object.fromEntries(url.searchParams) } : { fullPath: '/', query: {} }
|
|
153
56
|
},
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (typeof window !== 'undefined') {
|
|
157
|
-
window.location.href = target.path
|
|
158
|
-
}
|
|
57
|
+
push: (target) => {
|
|
58
|
+
if (typeof window !== 'undefined') window.location.href = target.path
|
|
159
59
|
},
|
|
160
|
-
replace: (target
|
|
161
|
-
if (typeof window !== 'undefined')
|
|
162
|
-
window.location.replace(target.path)
|
|
163
|
-
}
|
|
60
|
+
replace: (target) => {
|
|
61
|
+
if (typeof window !== 'undefined') window.location.replace(target.path)
|
|
164
62
|
},
|
|
165
63
|
}
|
|
166
64
|
}
|
package/src/router/types.ts
CHANGED
|
@@ -18,9 +18,10 @@ export interface I18nRoutingStrategy {
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Get route name from path (e.g., /en/about -> about)
|
|
21
|
-
* Used in middleware to set route name
|
|
21
|
+
* Used in middleware to set route name. `routePattern` is the page's pattern (`/blog/[slug]`), with
|
|
22
|
+
* a `[locale]` param already replaced by its value: use it to give every URL of a page one name.
|
|
22
23
|
*/
|
|
23
|
-
getRouteName?: (path: string, locales: string[]) => string
|
|
24
|
+
getRouteName?: (path: string, locales: string[], routePattern?: string) => string
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Get locale from path
|