0
Fork 0
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:
Emanuele Stoppa 2023-11-30 05:58:37 -05:00 committed by GitHub
parent 3f28336d9a
commit 27d3e86e4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 123 additions and 43 deletions

View 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,
+ }
}
}
})
```

View file

@ -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;

View file

@ -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,
});
}

View file

@ -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;
};

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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) => {

View file

@ -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,
});

View file

@ -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,
});

View file

@ -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,
};

View file

@ -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

View file

@ -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) {

View file

@ -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}`);

View file

@ -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,
};

View file

@ -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,
});
}

View file

@ -8,7 +8,9 @@ export default defineConfig({
locales: [
'en', 'pt', 'it'
],
routingStrategy: "prefix-always"
routing: {
prefixDefaultLocale: true
}
}
}
})

View file

@ -7,7 +7,9 @@ export default defineConfig({
locales: [
'en', 'pt', 'it'
],
routingStrategy: "prefix-always"
routing: {
prefixDefaultLocale: true
}
}
},
base: "/new-site"

View file

@ -7,7 +7,6 @@ export default defineConfig({
locales: [
'en', 'pt', 'it'
],
routingStrategy: "prefix-other-locales"
},
},

View file

@ -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,
},
},
},
});