mirror of
https://github.com/withastro/astro.git
synced 2025-03-17 23:11:29 -05:00
refactor(i18n): breakdown routing strategy (#9236)
* refactor(i18n): breakdown routing strategy * changelog * changeset * chore: fix rebase * fix changeset * chore: update test * Apply suggestions from code review Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> * Update packages/astro/test/i18n-routing.test.js Co-authored-by: Chris Swithinbank <swithinbank@gmail.com> --------- Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
This commit is contained in:
parent
3f28336d9a
commit
27d3e86e4c
19 changed files with 123 additions and 43 deletions
31
.changeset/young-trains-shout.md
Normal file
31
.changeset/young-trains-shout.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
The configuration `i18n.routingStrategy` has been replaced with an object called `routing`.
|
||||
|
||||
```diff
|
||||
export default defineConfig({
|
||||
experimental: {
|
||||
i18n: {
|
||||
- routingStrategy: "prefix-always",
|
||||
+ routing: {
|
||||
+ prefixDefaultLocale: true,
|
||||
+ }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```diff
|
||||
export default defineConfig({
|
||||
experimental: {
|
||||
i18n: {
|
||||
- routingStrategy: "prefix-other-locales",
|
||||
+ routing: {
|
||||
+ prefixDefaultLocale: false,
|
||||
+ }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
|
@ -1519,23 +1519,43 @@ export interface AstroUserConfig {
|
|||
/**
|
||||
* @docs
|
||||
* @kind h4
|
||||
* @name experimental.i18n.routingStrategy
|
||||
* @type {'prefix-always' | 'prefix-other-locales'}
|
||||
* @default 'prefix-other-locales'
|
||||
* @version 3.5.0
|
||||
* @name experimental.i18n.routing
|
||||
* @type {Routing}
|
||||
* @version 3.7.0
|
||||
* @description
|
||||
*
|
||||
* Controls the routing strategy to determine your site URLs. Set this based on your folder/URL path configuration for your default language:
|
||||
*
|
||||
* - `prefix-other-locales`(default): Only non-default languages will display a language prefix.
|
||||
* The `defaultLocale` will not show a language prefix and content files do not exist in a localized folder.
|
||||
* URLs will be of the form `example.com/[locale]/content/` for all non-default languages, but `example.com/content/` for the default locale.
|
||||
* - `prefix-always`: All URLs will display a language prefix.
|
||||
* URLs will be of the form `example.com/[locale]/content/` for every route, including the default language.
|
||||
* Localized folders are used for every language, including the default.
|
||||
*
|
||||
* Controls the routing strategy to determine your site URLs. Set this based on your folder/URL path configuration for your default language.
|
||||
*/
|
||||
routingStrategy?: 'prefix-always' | 'prefix-other-locales';
|
||||
routing?: {
|
||||
/**
|
||||
* @docs
|
||||
* @name experimental.i18n.routing.prefixDefaultLocale
|
||||
* @type {boolean}
|
||||
* @default `false`
|
||||
* @version 3.7.0
|
||||
* @description
|
||||
*
|
||||
* When `false`, only non-default languages will display a language prefix.
|
||||
* The `defaultLocale` will not show a language prefix and content files do not exist in a localized folder.
|
||||
* URLs will be of the form `example.com/[locale]/content/` for all non-default languages, but `example.com/content/` for the default locale.
|
||||
*
|
||||
* When `true`, all URLs will display a language prefix.
|
||||
* URLs will be of the form `example.com/[locale]/content/` for every route, including the default language.
|
||||
* Localized folders are used for every language, including the default.
|
||||
*/
|
||||
prefixDefaultLocale: boolean;
|
||||
|
||||
/**
|
||||
* @name experimental.i18n.routing.strategy
|
||||
* @type {"pathname"}
|
||||
* @default `"pathname"`
|
||||
* @version 3.7.0
|
||||
* @description
|
||||
*
|
||||
* - `"pathanme": The strategy is applied to the pathname of the URLs
|
||||
*/
|
||||
strategy: 'pathname';
|
||||
};
|
||||
};
|
||||
/**
|
||||
* @docs
|
||||
|
@ -2253,6 +2273,10 @@ export interface APIContext<
|
|||
currentLocale: string | undefined;
|
||||
}
|
||||
|
||||
type Routing = {
|
||||
prefixDefaultLocale: boolean;
|
||||
strategy: 'pathname';
|
||||
};
|
||||
export type EndpointOutput =
|
||||
| {
|
||||
body: Body;
|
||||
|
|
|
@ -243,7 +243,7 @@ export class App {
|
|||
env: this.#pipeline.env,
|
||||
mod: handler as any,
|
||||
locales: this.#manifest.i18n?.locales,
|
||||
routingStrategy: this.#manifest.i18n?.routingStrategy,
|
||||
routing: this.#manifest.i18n?.routing,
|
||||
defaultLocale: this.#manifest.i18n?.defaultLocale,
|
||||
});
|
||||
} else {
|
||||
|
@ -280,7 +280,7 @@ export class App {
|
|||
mod,
|
||||
env: this.#pipeline.env,
|
||||
locales: this.#manifest.i18n?.locales,
|
||||
routingStrategy: this.#manifest.i18n?.routingStrategy,
|
||||
routing: this.#manifest.i18n?.routing,
|
||||
defaultLocale: this.#manifest.i18n?.defaultLocale,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ export type SSRManifest = {
|
|||
|
||||
export type SSRManifestI18n = {
|
||||
fallback?: Record<string, string>;
|
||||
routingStrategy?: 'prefix-always' | 'prefix-other-locales';
|
||||
routing?: 'prefix-always' | 'prefix-other-locales';
|
||||
locales: string[];
|
||||
defaultLocale: string;
|
||||
};
|
||||
|
|
|
@ -579,7 +579,7 @@ async function generatePath(pathname: string, gopts: GeneratePathOptions, pipeli
|
|||
env: pipeline.getEnvironment(),
|
||||
mod,
|
||||
locales: i18n?.locales,
|
||||
routingStrategy: i18n?.routingStrategy,
|
||||
routing: i18n?.routing,
|
||||
defaultLocale: i18n?.defaultLocale,
|
||||
});
|
||||
|
||||
|
@ -655,7 +655,7 @@ export function createBuildManifest(
|
|||
if (settings.config.experimental.i18n) {
|
||||
i18nManifest = {
|
||||
fallback: settings.config.experimental.i18n.fallback,
|
||||
routingStrategy: settings.config.experimental.i18n.routingStrategy,
|
||||
routing: settings.config.experimental.i18n.routing,
|
||||
defaultLocale: settings.config.experimental.i18n.defaultLocale,
|
||||
locales: settings.config.experimental.i18n.locales,
|
||||
};
|
||||
|
|
|
@ -245,7 +245,7 @@ function buildManifest(
|
|||
if (settings.config.experimental.i18n) {
|
||||
i18nManifest = {
|
||||
fallback: settings.config.experimental.i18n.fallback,
|
||||
routingStrategy: settings.config.experimental.i18n.routingStrategy,
|
||||
routing: settings.config.experimental.i18n.routing,
|
||||
locales: settings.config.experimental.i18n.locales,
|
||||
defaultLocale: settings.config.experimental.i18n.defaultLocale,
|
||||
};
|
||||
|
|
|
@ -65,6 +65,8 @@ const ASTRO_CONFIG_DEFAULTS = {
|
|||
},
|
||||
} satisfies AstroUserConfig & { server: { open: boolean } };
|
||||
|
||||
type RoutingStrategies = 'prefix-always' | 'prefix-other-locales';
|
||||
|
||||
export const AstroConfigSchema = z.object({
|
||||
root: z
|
||||
.string()
|
||||
|
@ -346,11 +348,25 @@ export const AstroConfigSchema = z.object({
|
|||
defaultLocale: z.string(),
|
||||
locales: z.string().array(),
|
||||
fallback: z.record(z.string(), z.string()).optional(),
|
||||
// TODO: properly add default when the feature goes of experimental
|
||||
routingStrategy: z
|
||||
.enum(['prefix-always', 'prefix-other-locales'])
|
||||
.optional()
|
||||
.default('prefix-other-locales'),
|
||||
routing: z
|
||||
.object({
|
||||
prefixDefaultLocale: z.boolean().default(false),
|
||||
strategy: z.enum(['pathname']).default('pathname'),
|
||||
})
|
||||
.default({})
|
||||
.transform((routing) => {
|
||||
let strategy: RoutingStrategies;
|
||||
switch (routing.strategy) {
|
||||
case 'pathname': {
|
||||
if (routing.prefixDefaultLocale === true) {
|
||||
strategy = 'prefix-always';
|
||||
} else {
|
||||
strategy = 'prefix-other-locales';
|
||||
}
|
||||
}
|
||||
}
|
||||
return strategy;
|
||||
}),
|
||||
})
|
||||
.optional()
|
||||
.superRefine((i18n, ctx) => {
|
||||
|
|
|
@ -180,7 +180,7 @@ export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>
|
|||
props: ctx.props,
|
||||
site: env.site,
|
||||
adapterName: env.adapterName,
|
||||
routingStrategy: ctx.routingStrategy,
|
||||
routingStrategy: ctx.routing,
|
||||
defaultLocale: ctx.defaultLocale,
|
||||
locales: ctx.locales,
|
||||
});
|
||||
|
|
|
@ -128,7 +128,7 @@ export class Pipeline {
|
|||
site: env.site,
|
||||
adapterName: env.adapterName,
|
||||
locales: renderContext.locales,
|
||||
routingStrategy: renderContext.routingStrategy,
|
||||
routingStrategy: renderContext.routing,
|
||||
defaultLocale: renderContext.defaultLocale,
|
||||
});
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ export interface RenderContext {
|
|||
locals?: object;
|
||||
locales: string[] | undefined;
|
||||
defaultLocale: string | undefined;
|
||||
routingStrategy: 'prefix-always' | 'prefix-other-locales' | undefined;
|
||||
routing: 'prefix-always' | 'prefix-other-locales' | undefined;
|
||||
}
|
||||
|
||||
export type CreateRenderContextArgs = Partial<
|
||||
|
@ -62,7 +62,7 @@ export async function createRenderContext(
|
|||
params,
|
||||
props,
|
||||
locales: options.locales,
|
||||
routingStrategy: options.routingStrategy,
|
||||
routing: options.routing,
|
||||
defaultLocale: options.defaultLocale,
|
||||
};
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ export async function renderPage({ mod, renderContext, env, cookies }: RenderPag
|
|||
locals: renderContext.locals ?? {},
|
||||
locales: renderContext.locales,
|
||||
defaultLocale: renderContext.defaultLocale,
|
||||
routingStrategy: renderContext.routingStrategy,
|
||||
routingStrategy: renderContext.routing,
|
||||
});
|
||||
|
||||
// TODO: Remove in Astro 4.0
|
||||
|
|
|
@ -532,7 +532,7 @@ export function createRouteManifest(
|
|||
|
||||
// Work done, now we start creating "fallback" routes based on the configuration
|
||||
|
||||
if (i18n.routingStrategy === 'prefix-always') {
|
||||
if (i18n.routing === 'prefix-always') {
|
||||
// we attempt to retrieve the index page of the default locale
|
||||
const defaultLocaleRoutes = routesByLocale.get(i18n.defaultLocale);
|
||||
if (defaultLocaleRoutes) {
|
||||
|
|
|
@ -48,14 +48,14 @@ export function createI18nMiddleware(
|
|||
const separators = url.pathname.split('/');
|
||||
const pathnameContainsDefaultLocale = url.pathname.includes(`/${defaultLocale}`);
|
||||
const isLocaleFree = checkIsLocaleFree(url.pathname, i18n.locales);
|
||||
if (i18n.routingStrategy === 'prefix-other-locales' && pathnameContainsDefaultLocale) {
|
||||
if (i18n.routing === 'prefix-other-locales' && pathnameContainsDefaultLocale) {
|
||||
const newLocation = url.pathname.replace(`/${defaultLocale}`, '');
|
||||
response.headers.set('Location', newLocation);
|
||||
return new Response(null, {
|
||||
status: 404,
|
||||
headers: response.headers,
|
||||
});
|
||||
} else if (i18n.routingStrategy === 'prefix-always') {
|
||||
} else if (i18n.routing === 'prefix-always') {
|
||||
if (url.pathname === base + '/' || url.pathname === base) {
|
||||
if (trailingSlash === 'always') {
|
||||
return context.redirect(`${appendForwardSlash(joinPaths(base, i18n.defaultLocale))}`);
|
||||
|
@ -82,7 +82,7 @@ export function createI18nMiddleware(
|
|||
let newPathname: string;
|
||||
// If a locale falls back to the default locale, we want to **remove** the locale because
|
||||
// the default locale doesn't have a prefix, but _only_ if prefix-always is false
|
||||
if (fallbackLocale === defaultLocale && i18n.routingStrategy !== 'prefix-always') {
|
||||
if (fallbackLocale === defaultLocale && i18n.routing !== 'prefix-always') {
|
||||
newPathname = url.pathname.replace(`/${urlLocale}`, ``);
|
||||
} else {
|
||||
newPathname = url.pathname.replace(`/${urlLocale}`, `/${fallbackLocale}`);
|
||||
|
|
|
@ -90,7 +90,7 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
|
|||
if (settings.config.experimental.i18n) {
|
||||
i18nManifest = {
|
||||
fallback: settings.config.experimental.i18n.fallback,
|
||||
routingStrategy: settings.config.experimental.i18n.routingStrategy,
|
||||
routing: settings.config.experimental.i18n.routing,
|
||||
defaultLocale: settings.config.experimental.i18n.defaultLocale,
|
||||
locales: settings.config.experimental.i18n.locales,
|
||||
};
|
||||
|
|
|
@ -217,7 +217,7 @@ export async function handleRoute({
|
|||
mod,
|
||||
route,
|
||||
locales: manifest.i18n?.locales,
|
||||
routingStrategy: manifest.i18n?.routingStrategy,
|
||||
routing: manifest.i18n?.routing,
|
||||
defaultLocale: manifest.i18n?.defaultLocale,
|
||||
});
|
||||
} else {
|
||||
|
@ -276,7 +276,7 @@ export async function handleRoute({
|
|||
mod,
|
||||
env,
|
||||
locales: i18n?.locales,
|
||||
routingStrategy: i18n?.routingStrategy,
|
||||
routing: i18n?.routing,
|
||||
defaultLocale: i18n?.defaultLocale,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,7 +8,9 @@ export default defineConfig({
|
|||
locales: [
|
||||
'en', 'pt', 'it'
|
||||
],
|
||||
routingStrategy: "prefix-always"
|
||||
routing: {
|
||||
prefixDefaultLocale: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -7,7 +7,9 @@ export default defineConfig({
|
|||
locales: [
|
||||
'en', 'pt', 'it'
|
||||
],
|
||||
routingStrategy: "prefix-always"
|
||||
routing: {
|
||||
prefixDefaultLocale: true
|
||||
}
|
||||
}
|
||||
},
|
||||
base: "/new-site"
|
||||
|
|
|
@ -7,7 +7,6 @@ export default defineConfig({
|
|||
locales: [
|
||||
'en', 'pt', 'it'
|
||||
],
|
||||
routingStrategy: "prefix-other-locales"
|
||||
},
|
||||
|
||||
},
|
||||
|
|
|
@ -291,7 +291,9 @@ describe('[DEV] i18n routing', () => {
|
|||
fallback: {
|
||||
it: 'en',
|
||||
},
|
||||
routingStrategy: 'prefix-other-locales',
|
||||
routing: {
|
||||
prefixDefaultLocale: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -661,7 +663,9 @@ describe('[SSG] i18n routing', () => {
|
|||
fallback: {
|
||||
it: 'en',
|
||||
},
|
||||
routingStrategy: 'prefix-always',
|
||||
routing: {
|
||||
prefixDefaultLocale: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -975,7 +979,9 @@ describe('[SSR] i18n routing', () => {
|
|||
fallback: {
|
||||
it: 'en',
|
||||
},
|
||||
routingStrategy: 'prefix-always',
|
||||
routing: {
|
||||
prefixDefaultLocale: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue