mirror of
https://github.com/withastro/astro.git
synced 2025-03-24 23:21:57 -05:00
feat(i18n): expand fallback system (#11677)
* feat(i18n): expand fallback system * rebase * apply feedback * better changeset * update tests too * apply feedback * Update .changeset/blue-pens-divide.md Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com> * update docs * nitpick * Apply suggestions from code review Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update .changeset/blue-pens-divide.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Apply suggestions from code review Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update .changeset/blue-pens-divide.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * fix regression --------- Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com> Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
parent
1c54e63327
commit
cb356a5db6
22 changed files with 297 additions and 51 deletions
.changeset
packages/astro
src
@types
container
core
i18n
virtual-modules
vite-plugin-astro-server
test
22
.changeset/blue-pens-divide.md
Normal file
22
.changeset/blue-pens-divide.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fixes a bug in the logic of `Astro.rewrite()` which led to the value for `base`, if configured, being automatically prepended to the rewrite URL passed. This was unintended behavior and has been corrected, and Astro now processes the URLs exactly as passed.
|
||||
|
||||
If you use the `rewrite()` function on a project that has `base` configured, you must now prepend the base to your existing rewrite URL:
|
||||
|
||||
```js
|
||||
// astro.config.mjs
|
||||
export default defineConfig({
|
||||
base: '/blog'
|
||||
})
|
||||
```
|
||||
|
||||
```diff
|
||||
// src/middleware.js
|
||||
export function onRequest(ctx, next) {
|
||||
- return ctx.rewrite("/about")
|
||||
+ return ctx.rewrite("/blog/about")
|
||||
}
|
||||
```
|
32
.changeset/lazy-feet-join.md
Normal file
32
.changeset/lazy-feet-join.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
'astro': minor
|
||||
---
|
||||
|
||||
Adds a new option `fallbackType` to `i18n.routing` configuration that allows you to control how fallback pages are handled.
|
||||
|
||||
When `i18n.fallback` is configured, this new routing option controls whether to [redirect](https://docs.astro.build/en/guides/routing/#redirects) to the fallback page, or to [rewrite](https://docs.astro.build/en/guides/routing/#rewrites) the fallback page's content in place.
|
||||
|
||||
The `"redirect"` option is the default value and matches the current behavior of the existing fallback system.
|
||||
|
||||
The option `"rewrite"` uses the new [rewriting system](https://docs.astro.build/en/guides/routing/#rewrites) to create fallback pages that render content on the original, requested URL without a browser refresh.
|
||||
|
||||
For example, the following configuration will generate a page `/fr/index.html` that will contain the same HTML rendered by the page `/en/index.html` when `src/pages/fr/index.astro` does not exist.
|
||||
|
||||
```js
|
||||
// astro.config.mjs
|
||||
export default defineConfig({
|
||||
i18n: {
|
||||
locals: ["en", "fr"],
|
||||
defaultLocale: "en",
|
||||
routing: {
|
||||
prefixDefaultLocale: true,
|
||||
fallbackType: "rewrite"
|
||||
},
|
||||
fallback: {
|
||||
fr: "en"
|
||||
},
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -1668,6 +1668,43 @@ export interface AstroUserConfig {
|
|||
* */
|
||||
redirectToDefaultLocale?: boolean;
|
||||
|
||||
/**
|
||||
* @docs
|
||||
* @name i18n.routing.fallbackType
|
||||
* @kind h4
|
||||
* @type {"redirect" | "rewrite"}
|
||||
* @default `"redirect"`
|
||||
* @version 4.15.0
|
||||
* @description
|
||||
*
|
||||
* When [`i18n.fallback`](#i18nfallback) is configured to avoid showing a 404 page for missing page routes, this option controls whether to [redirect](https://docs.astro.build/en/guides/routing/#redirects) to the fallback page, or to [rewrite](https://docs.astro.build/en/guides/routing/#rewrites) the fallback page's content in place.
|
||||
*
|
||||
* By default, Astro's i18n routing creates pages that redirect your visitors to a new destination based on your fallback configuration. The browser will refresh and show the destination address in the URL bar.
|
||||
*
|
||||
* When `i18n.routing.fallback: "rewrite"` is configured, Astro will create pages that render the contents of the fallback page on the original, requested URL.
|
||||
*
|
||||
* With the following configuration, if you have the file `src/pages/en/about.astro` but not `src/pages/fr/about.astro`, the `astro build` command will generate `dist/fr/about.html` with the same content as the `dist/en/index.html` page.
|
||||
* Your site visitor will see the English version of the page at `https://example.com/fr/about/` and will not be redirected.
|
||||
*
|
||||
* ```js
|
||||
* //astro.config.mjs
|
||||
* export default defineConfig({
|
||||
* i18n: {
|
||||
* defaultLocale: "en",
|
||||
* locales: ["en", "fr"],
|
||||
* routing: {
|
||||
* prefixDefaultLocale: false,
|
||||
* fallbackType: "rewrite",
|
||||
* },
|
||||
* fallback: {
|
||||
* fr: "en",
|
||||
* }
|
||||
* },
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
fallbackType: "redirect" | "rewrite"
|
||||
|
||||
/**
|
||||
* @name i18n.routing.strategy
|
||||
* @type {"pathname"}
|
||||
|
|
|
@ -5,7 +5,7 @@ import type {
|
|||
SSRElement,
|
||||
SSRResult,
|
||||
} from '../@types/astro.js';
|
||||
import { type HeadElements, Pipeline } from '../core/base-pipeline.js';
|
||||
import {type HeadElements, Pipeline, type TryRewriteResult} from '../core/base-pipeline.js';
|
||||
import type { SinglePageBuiltModule } from '../core/build/types.js';
|
||||
import {
|
||||
createModuleScriptElement,
|
||||
|
@ -71,8 +71,8 @@ export class ContainerPipeline extends Pipeline {
|
|||
async tryRewrite(
|
||||
payload: RewritePayload,
|
||||
request: Request,
|
||||
): Promise<[RouteData, ComponentInstance, URL]> {
|
||||
const [foundRoute, finalUrl] = findRouteToRewrite({
|
||||
): Promise<TryRewriteResult> {
|
||||
const {newUrl,pathname,routeData} = findRouteToRewrite({
|
||||
payload,
|
||||
request,
|
||||
routes: this.manifest?.routes.map((r) => r.routeData),
|
||||
|
@ -81,8 +81,8 @@ export class ContainerPipeline extends Pipeline {
|
|||
base: this.manifest.base,
|
||||
});
|
||||
|
||||
const componentInstance = await this.getComponentByRoute(foundRoute);
|
||||
return [foundRoute, componentInstance, finalUrl];
|
||||
const componentInstance = await this.getComponentByRoute(routeData);
|
||||
return {componentInstance, routeData, newUrl, pathname};
|
||||
}
|
||||
|
||||
insertRoute(route: RouteData, componentInstance: ComponentInstance): void {
|
||||
|
|
|
@ -6,7 +6,7 @@ import type {
|
|||
SSRElement,
|
||||
SSRResult,
|
||||
} from '../../@types/astro.js';
|
||||
import { Pipeline } from '../base-pipeline.js';
|
||||
import {Pipeline, type TryRewriteResult} from '../base-pipeline.js';
|
||||
import type { SinglePageBuiltModule } from '../build/types.js';
|
||||
import { RedirectSinglePageBuiltModule } from '../redirects/component.js';
|
||||
import { createModuleScriptElement, createStylesheetElementSet } from '../render/ssr-element.js';
|
||||
|
@ -94,8 +94,8 @@ export class AppPipeline extends Pipeline {
|
|||
payload: RewritePayload,
|
||||
request: Request,
|
||||
_sourceRoute: RouteData,
|
||||
): Promise<[RouteData, ComponentInstance, URL]> {
|
||||
const [foundRoute, finalUrl] = findRouteToRewrite({
|
||||
): Promise<TryRewriteResult> {
|
||||
const { newUrl,pathname,routeData} = findRouteToRewrite({
|
||||
payload,
|
||||
request,
|
||||
routes: this.manifest?.routes.map((r) => r.routeData),
|
||||
|
@ -104,8 +104,8 @@ export class AppPipeline extends Pipeline {
|
|||
base: this.manifest.base,
|
||||
});
|
||||
|
||||
const componentInstance = await this.getComponentByRoute(foundRoute);
|
||||
return [foundRoute, componentInstance, finalUrl];
|
||||
const componentInstance = await this.getComponentByRoute(routeData);
|
||||
return {newUrl, pathname, componentInstance, routeData};
|
||||
}
|
||||
|
||||
async getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule> {
|
||||
|
|
|
@ -76,6 +76,7 @@ export type SSRManifest = {
|
|||
|
||||
export type SSRManifestI18n = {
|
||||
fallback: Record<string, string> | undefined;
|
||||
fallbackType: "redirect" | "rewrite";
|
||||
strategy: RoutingStrategies;
|
||||
locales: Locales;
|
||||
defaultLocale: string;
|
||||
|
|
|
@ -95,7 +95,7 @@ export abstract class Pipeline {
|
|||
rewritePayload: RewritePayload,
|
||||
request: Request,
|
||||
sourceRoute: RouteData,
|
||||
): Promise<[RouteData, ComponentInstance, URL]>;
|
||||
): Promise<TryRewriteResult>;
|
||||
|
||||
/**
|
||||
* Tells the pipeline how to retrieve a component give a `RouteData`
|
||||
|
@ -106,3 +106,10 @@ export abstract class Pipeline {
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface HeadElements extends Pick<SSRResult, 'scripts' | 'styles' | 'links'> {}
|
||||
|
||||
export interface TryRewriteResult {
|
||||
routeData: RouteData,
|
||||
componentInstance: ComponentInstance,
|
||||
newUrl: URL,
|
||||
pathname: string
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import {
|
|||
removeLeadingForwardSlash,
|
||||
removeTrailingForwardSlash,
|
||||
} from '../../core/path.js';
|
||||
import { toRoutingStrategy } from '../../i18n/utils.js';
|
||||
import {toFallbackType, toRoutingStrategy} from '../../i18n/utils.js';
|
||||
import { runHookBuildGenerated } from '../../integrations/hooks.js';
|
||||
import { getOutputDirectory } from '../../prerender/utils.js';
|
||||
import type { SSRManifestI18n } from '../app/types.js';
|
||||
|
@ -528,6 +528,7 @@ function createBuildManifest(
|
|||
if (settings.config.i18n) {
|
||||
i18nManifest = {
|
||||
fallback: settings.config.i18n.fallback,
|
||||
fallbackType: toFallbackType(settings.config.i18n.routing),
|
||||
strategy: toRoutingStrategy(settings.config.i18n.routing, settings.config.i18n.domains),
|
||||
defaultLocale: settings.config.i18n.defaultLocale,
|
||||
locales: settings.config.i18n.locales,
|
||||
|
|
|
@ -26,6 +26,7 @@ import { RESOLVED_SPLIT_MODULE_ID } from './plugins/plugin-ssr.js';
|
|||
import { getPagesFromVirtualModulePageName, getVirtualModulePageName } from './plugins/util.js';
|
||||
import type { PageBuildData, SinglePageBuiltModule, StaticBuildOptions } from './types.js';
|
||||
import { i18nHasFallback } from './util.js';
|
||||
import type {TryRewriteResult} from "../base-pipeline.js";
|
||||
|
||||
/**
|
||||
* The build pipeline is responsible to gather the files emitted by the SSR build and generate the pages by executing these files.
|
||||
|
@ -289,8 +290,8 @@ export class BuildPipeline extends Pipeline {
|
|||
payload: RewritePayload,
|
||||
request: Request,
|
||||
_sourceRoute: RouteData,
|
||||
): Promise<[RouteData, ComponentInstance, URL]> {
|
||||
const [foundRoute, finalUrl] = findRouteToRewrite({
|
||||
): Promise<TryRewriteResult> {
|
||||
const { routeData, pathname, newUrl} = findRouteToRewrite({
|
||||
payload,
|
||||
request,
|
||||
routes: this.options.manifest.routes,
|
||||
|
@ -299,8 +300,8 @@ export class BuildPipeline extends Pipeline {
|
|||
base: this.config.base,
|
||||
});
|
||||
|
||||
const componentInstance = await this.getComponentByRoute(foundRoute);
|
||||
return [foundRoute, componentInstance, finalUrl];
|
||||
const componentInstance = await this.getComponentByRoute(routeData);
|
||||
return { routeData, componentInstance, newUrl, pathname };
|
||||
}
|
||||
|
||||
async retrieveSsrEntry(route: RouteData, filePath: string): Promise<SinglePageBuiltModule> {
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { OutputChunk } from 'rollup';
|
|||
import type { Plugin as VitePlugin } from 'vite';
|
||||
import { getAssetsPrefix } from '../../../assets/utils/getAssetsPrefix.js';
|
||||
import { normalizeTheLocale } from '../../../i18n/index.js';
|
||||
import { toRoutingStrategy } from '../../../i18n/utils.js';
|
||||
import {toFallbackType, toRoutingStrategy} from '../../../i18n/utils.js';
|
||||
import { runHookBuildSsr } from '../../../integrations/hooks.js';
|
||||
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
|
||||
import type {
|
||||
|
@ -254,6 +254,7 @@ function buildManifest(
|
|||
if (settings.config.i18n) {
|
||||
i18nManifest = {
|
||||
fallback: settings.config.i18n.fallback,
|
||||
fallbackType: toFallbackType(settings.config.i18n.routing),
|
||||
strategy: toRoutingStrategy(settings.config.i18n.routing, settings.config.i18n.domains),
|
||||
locales: settings.config.i18n.locales,
|
||||
defaultLocale: settings.config.i18n.defaultLocale,
|
||||
|
|
|
@ -403,6 +403,7 @@ export const AstroConfigSchema = z.object({
|
|||
.object({
|
||||
prefixDefaultLocale: z.boolean().optional().default(false),
|
||||
redirectToDefaultLocale: z.boolean().optional().default(true),
|
||||
fallbackType: z.enum(["redirect", "rewrite"]).optional().default("redirect"),
|
||||
})
|
||||
.refine(
|
||||
({ prefixDefaultLocale, redirectToDefaultLocale }) => {
|
||||
|
|
|
@ -142,13 +142,13 @@ export class RenderContext {
|
|||
if (payload) {
|
||||
pipeline.logger.debug('router', 'Called rewriting to:', payload);
|
||||
// we intentionally let the error bubble up
|
||||
const [routeData, component] = await pipeline.tryRewrite(
|
||||
const { routeData, componentInstance: newComponent} = await pipeline.tryRewrite(
|
||||
payload,
|
||||
this.request,
|
||||
this.originalRoute,
|
||||
);
|
||||
this.routeData = routeData;
|
||||
componentInstance = component;
|
||||
componentInstance = newComponent;
|
||||
this.isRewriting = true;
|
||||
this.status = 200;
|
||||
}
|
||||
|
@ -234,7 +234,7 @@ export class RenderContext {
|
|||
|
||||
async #executeRewrite(reroutePayload: RewritePayload) {
|
||||
this.pipeline.logger.debug('router', 'Calling rewrite: ', reroutePayload);
|
||||
const [routeData, component, newURL] = await this.pipeline.tryRewrite(
|
||||
const { routeData, componentInstance, newUrl, pathname } = await this.pipeline.tryRewrite(
|
||||
reroutePayload,
|
||||
this.request,
|
||||
this.originalRoute,
|
||||
|
@ -243,16 +243,16 @@ export class RenderContext {
|
|||
if (reroutePayload instanceof Request) {
|
||||
this.request = reroutePayload;
|
||||
} else {
|
||||
this.request = this.#copyRequest(newURL, this.request);
|
||||
this.request = this.#copyRequest(newUrl, this.request);
|
||||
}
|
||||
this.url = new URL(this.request.url);
|
||||
this.cookies = new AstroCookies(this.request);
|
||||
this.params = getParams(routeData, this.url.pathname);
|
||||
this.pathname = this.url.pathname;
|
||||
this.params = getParams(routeData, pathname);
|
||||
this.pathname = pathname;
|
||||
this.isRewriting = true;
|
||||
// we found a route and a component, we can change the status code to 200
|
||||
this.status = 200;
|
||||
return await this.render(component);
|
||||
return await this.render(componentInstance);
|
||||
}
|
||||
|
||||
createActionAPIContext(): ActionAPIContext {
|
||||
|
|
|
@ -12,6 +12,17 @@ export type FindRouteToRewrite = {
|
|||
base: AstroConfig['base'];
|
||||
};
|
||||
|
||||
export interface FindRouteToRewriteResult {
|
||||
routeData: RouteData;
|
||||
newUrl: URL;
|
||||
pathname: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared logic to retrieve the rewritten route. It returns a tuple that represents:
|
||||
* 1. The new `Request` object. It contains `base`
|
||||
* 2.
|
||||
*/
|
||||
export function findRouteToRewrite({
|
||||
payload,
|
||||
routes,
|
||||
|
@ -19,23 +30,25 @@ export function findRouteToRewrite({
|
|||
trailingSlash,
|
||||
buildFormat,
|
||||
base,
|
||||
}: FindRouteToRewrite): [RouteData, URL] {
|
||||
let finalUrl: URL | undefined = undefined;
|
||||
}: FindRouteToRewrite): FindRouteToRewriteResult {
|
||||
let newUrl: URL | undefined = undefined;
|
||||
if (payload instanceof URL) {
|
||||
finalUrl = payload;
|
||||
newUrl = payload;
|
||||
} else if (payload instanceof Request) {
|
||||
finalUrl = new URL(payload.url);
|
||||
newUrl = new URL(payload.url);
|
||||
} else {
|
||||
finalUrl = new URL(payload, new URL(request.url).origin);
|
||||
newUrl = new URL(payload, new URL(request.url).origin);
|
||||
}
|
||||
let pathname = newUrl.pathname;
|
||||
if (base !== '/' && newUrl.pathname.startsWith(base)) {
|
||||
pathname = shouldAppendForwardSlash(trailingSlash, buildFormat)
|
||||
? appendForwardSlash(newUrl.pathname)
|
||||
: removeTrailingForwardSlash(newUrl.pathname);
|
||||
pathname = pathname.slice(base.length);
|
||||
}
|
||||
|
||||
let foundRoute;
|
||||
for (const route of routes) {
|
||||
const pathname = shouldAppendForwardSlash(trailingSlash, buildFormat)
|
||||
? appendForwardSlash(finalUrl.pathname)
|
||||
: base !== '/'
|
||||
? removeTrailingForwardSlash(finalUrl.pathname)
|
||||
: finalUrl.pathname;
|
||||
if (route.pattern.test(decodeURI(pathname))) {
|
||||
foundRoute = route;
|
||||
break;
|
||||
|
@ -43,13 +56,17 @@ export function findRouteToRewrite({
|
|||
}
|
||||
|
||||
if (foundRoute) {
|
||||
return [foundRoute, finalUrl];
|
||||
return {
|
||||
routeData: foundRoute,
|
||||
newUrl,
|
||||
pathname
|
||||
};
|
||||
} else {
|
||||
const custom404 = routes.find((route) => route.route === '/404');
|
||||
if (custom404) {
|
||||
return [custom404, finalUrl];
|
||||
return { routeData: custom404, newUrl, pathname };
|
||||
} else {
|
||||
return [DEFAULT_404_ROUTE, finalUrl];
|
||||
return { routeData: DEFAULT_404_ROUTE, newUrl, pathname };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -282,6 +282,7 @@ export type MiddlewarePayload = {
|
|||
defaultLocale: string;
|
||||
domains: Record<string, string> | undefined;
|
||||
fallback: Record<string, string> | undefined;
|
||||
fallbackType: "redirect" | "rewrite";
|
||||
};
|
||||
|
||||
// NOTE: public function exported to the users via `astro:i18n` module
|
||||
|
@ -332,7 +333,7 @@ export function notFound({ base, locales }: MiddlewarePayload) {
|
|||
}
|
||||
|
||||
// NOTE: public function exported to the users via `astro:i18n` module
|
||||
export type RedirectToFallback = (context: APIContext, response: Response) => Response;
|
||||
export type RedirectToFallback = (context: APIContext, response: Response) => Promise<Response>;
|
||||
|
||||
export function redirectToFallback({
|
||||
fallback,
|
||||
|
@ -340,8 +341,9 @@ export function redirectToFallback({
|
|||
defaultLocale,
|
||||
strategy,
|
||||
base,
|
||||
fallbackType
|
||||
}: MiddlewarePayload) {
|
||||
return function (context: APIContext, response: Response): Response {
|
||||
return async function (context: APIContext, response: Response): Promise<Response> {
|
||||
if (response.status >= 300 && fallback) {
|
||||
const fallbackKeys = fallback ? Object.keys(fallback) : [];
|
||||
// we split the URL using the `/`, and then check in the returned array we have the locale
|
||||
|
@ -375,7 +377,12 @@ export function redirectToFallback({
|
|||
} else {
|
||||
newPathname = context.url.pathname.replace(`/${urlLocale}`, `/${pathFallbackLocale}`);
|
||||
}
|
||||
return context.redirect(newPathname);
|
||||
|
||||
if (fallbackType === "rewrite") {
|
||||
return await context.rewrite(newPathname);
|
||||
} else {
|
||||
return context.redirect(newPathname);
|
||||
}
|
||||
}
|
||||
}
|
||||
return response;
|
||||
|
|
|
@ -215,3 +215,11 @@ export function toRoutingStrategy(
|
|||
|
||||
return strategy;
|
||||
}
|
||||
|
||||
export function toFallbackType(routing: NonNullable<AstroConfig['i18n']>['routing']): "redirect" | "rewrite" {
|
||||
if (routing === 'manual') {
|
||||
return 'rewrite';
|
||||
}
|
||||
return routing.fallbackType;
|
||||
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { IncorrectStrategyForI18n } from '../core/errors/errors-data.js';
|
|||
import { AstroError } from '../core/errors/index.js';
|
||||
import * as I18nInternals from '../i18n/index.js';
|
||||
import type { RedirectToFallback } from '../i18n/index.js';
|
||||
import { toRoutingStrategy } from '../i18n/utils.js';
|
||||
import {toFallbackType, toRoutingStrategy} from '../i18n/utils.js';
|
||||
import type { I18nInternalConfig } from '../i18n/vite-plugin-i18n.js';
|
||||
|
||||
export { normalizeTheLocale, toCodes, toPaths } from '../i18n/index.js';
|
||||
|
@ -21,6 +21,7 @@ const { defaultLocale, locales, domains, fallback, routing } = i18n!;
|
|||
const base = import.meta.env.BASE_URL;
|
||||
|
||||
let strategy = toRoutingStrategy(routing, domains);
|
||||
let fallbackType = toFallbackType(routing);
|
||||
|
||||
export type GetLocaleOptions = I18nInternals.GetLocaleOptions;
|
||||
|
||||
|
@ -267,6 +268,7 @@ if (i18n?.routing === 'manual') {
|
|||
strategy,
|
||||
domains,
|
||||
fallback,
|
||||
fallbackType
|
||||
});
|
||||
} else {
|
||||
redirectToDefaultLocale = noop('redirectToDefaultLocale');
|
||||
|
@ -295,6 +297,7 @@ if (i18n?.routing === 'manual') {
|
|||
strategy,
|
||||
domains,
|
||||
fallback,
|
||||
fallbackType
|
||||
});
|
||||
} else {
|
||||
notFound = noop('notFound');
|
||||
|
@ -317,7 +320,7 @@ if (i18n?.routing === 'manual') {
|
|||
* Allows to use the build-in fallback system of Astro
|
||||
*
|
||||
* @param {APIContext} context The context passed to the middleware
|
||||
* @param {Response} response An optional `Response` in case you're handling a `Response` coming from the `next` function.
|
||||
* @param {Promise<Response>} response An optional `Response` in case you're handling a `Response` coming from the `next` function.
|
||||
*/
|
||||
export let redirectToFallback: RedirectToFallback;
|
||||
|
||||
|
@ -331,6 +334,7 @@ if (i18n?.routing === 'manual') {
|
|||
strategy,
|
||||
domains,
|
||||
fallback,
|
||||
fallbackType
|
||||
});
|
||||
} else {
|
||||
redirectToFallback = noop('useFallback');
|
||||
|
@ -374,11 +378,13 @@ export let middleware: (customOptions: NewAstroRoutingConfigWithoutManual) => Mi
|
|||
if (i18n?.routing === 'manual') {
|
||||
middleware = (customOptions: NewAstroRoutingConfigWithoutManual) => {
|
||||
strategy = toRoutingStrategy(customOptions, {});
|
||||
fallbackType = toFallbackType(customOptions);
|
||||
const manifest: SSRManifest['i18n'] = {
|
||||
...i18n,
|
||||
fallback: undefined,
|
||||
strategy,
|
||||
domainLookupTable: {},
|
||||
fallbackType
|
||||
};
|
||||
return I18nInternals.createMiddleware(manifest, base, trailingSlash, format);
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ import type {
|
|||
SSRManifest,
|
||||
} from '../@types/astro.js';
|
||||
import { getInfoOutput } from '../cli/info/index.js';
|
||||
import type { HeadElements } from '../core/base-pipeline.js';
|
||||
import type { HeadElements, TryRewriteResult } from '../core/base-pipeline.js';
|
||||
import { ASTRO_VERSION } from '../core/constants.js';
|
||||
import { enhanceViteSSRError } from '../core/errors/dev/index.js';
|
||||
import { AggregateError, CSSError, MarkdownError } from '../core/errors/index.js';
|
||||
|
@ -203,11 +203,11 @@ export class DevPipeline extends Pipeline {
|
|||
payload: RewritePayload,
|
||||
request: Request,
|
||||
_sourceRoute: RouteData,
|
||||
): Promise<[RouteData, ComponentInstance, URL]> {
|
||||
): Promise<TryRewriteResult> {
|
||||
if (!this.manifestData) {
|
||||
throw new Error('Missing manifest data. This is an internal error, please file an issue.');
|
||||
}
|
||||
const [foundRoute, finalUrl] = findRouteToRewrite({
|
||||
const { routeData, pathname, newUrl } = findRouteToRewrite({
|
||||
payload,
|
||||
request,
|
||||
routes: this.manifestData?.routes,
|
||||
|
@ -216,8 +216,8 @@ export class DevPipeline extends Pipeline {
|
|||
base: this.config.base,
|
||||
});
|
||||
|
||||
const componentInstance = await this.getComponentByRoute(foundRoute);
|
||||
return [foundRoute, componentInstance, finalUrl];
|
||||
const componentInstance = await this.getComponentByRoute(routeData);
|
||||
return { newUrl, pathname, componentInstance, routeData };
|
||||
}
|
||||
|
||||
setManifestData(manifestData: ManifestData) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import type { Logger } from '../core/logger/core.js';
|
|||
import { createViteLoader } from '../core/module-loader/index.js';
|
||||
import { injectDefaultRoutes } from '../core/routing/default.js';
|
||||
import { createRouteManifest } from '../core/routing/index.js';
|
||||
import { toRoutingStrategy } from '../i18n/utils.js';
|
||||
import {toFallbackType, toRoutingStrategy} from '../i18n/utils.js';
|
||||
import { baseMiddleware } from './base.js';
|
||||
import { createController } from './controller.js';
|
||||
import { recordServerError } from './error.js';
|
||||
|
@ -128,6 +128,7 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
|
|||
defaultLocale: settings.config.i18n.defaultLocale,
|
||||
locales: settings.config.i18n.locales,
|
||||
domainLookupTable: {},
|
||||
fallbackType: toFallbackType(settings.config.i18n.routing),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
---
|
||||
const { slug } = Astro.params;
|
||||
console.log("is it here???", Astro.params)
|
||||
export const prerender = false;
|
||||
---
|
||||
<html>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
return Astro.rewrite("/")
|
||||
return Astro.rewrite("/base")
|
||||
---
|
||||
|
|
|
@ -1929,3 +1929,108 @@ describe('SSR fallback from missing locale index to default locale index', () =>
|
|||
assert.equal(response.headers.get('location'), '/');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('Fallback rewrite dev server', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
let devServer;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/i18n-routing-fallback/',
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'fr'],
|
||||
routing: {
|
||||
prefixDefaultLocale: false,
|
||||
},
|
||||
fallback: {
|
||||
fr: 'en',
|
||||
},
|
||||
fallbackType: "rewrite"
|
||||
},
|
||||
});
|
||||
devServer = await fixture.startDevServer();
|
||||
});
|
||||
after(async () => {
|
||||
devServer.stop()
|
||||
})
|
||||
|
||||
it('should correctly rewrite to en', async () => {
|
||||
const html = await fixture.fetch('/fr').then((res) => res.text());
|
||||
assert.match(html, /Hello/);
|
||||
// assert.fail()
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fallback rewrite SSG', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/i18n-routing-fallback/',
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'fr'],
|
||||
routing: {
|
||||
prefixDefaultLocale: false,
|
||||
fallbackType: "rewrite"
|
||||
},
|
||||
fallback: {
|
||||
fr: 'en',
|
||||
},
|
||||
},
|
||||
});
|
||||
await fixture.build();
|
||||
// app = await fixture.loadTestAdapterApp();
|
||||
});
|
||||
|
||||
it('should correctly rewrite to en', async () => {
|
||||
const html = await fixture.readFile('/fr/index.html');
|
||||
assert.match(html, /Hello/);
|
||||
// assert.fail()
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fallback rewrite SSR', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
let app;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/i18n-routing-fallback/',
|
||||
output: 'server',
|
||||
outDir: './dist/i18n-routing-fallback',
|
||||
build: {
|
||||
client: './dist/i18n-routing-fallback/client',
|
||||
server: './dist/i18n-routing-fallback/server',
|
||||
},
|
||||
adapter: testAdapter(),
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'fr'],
|
||||
routing: {
|
||||
prefixDefaultLocale: false,
|
||||
fallbackType: "rewrite"
|
||||
},
|
||||
fallback: {
|
||||
fr: 'en',
|
||||
},
|
||||
},
|
||||
});
|
||||
await fixture.build();
|
||||
app = await fixture.loadTestAdapterApp();
|
||||
});
|
||||
|
||||
it('should correctly rewrite to en', async () => {
|
||||
const request = new Request('http://example.com/fr');
|
||||
const response = await app.render(request);
|
||||
assert.equal(response.status, 200);
|
||||
const html = await response.text();
|
||||
assert.match(html, /Hello/);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -104,7 +104,7 @@ describe('Dev rewrite, trailing slash -> never, with base', () => {
|
|||
});
|
||||
|
||||
it('should rewrite to the homepage', async () => {
|
||||
const html = await fixture.fetch('/foo').then((res) => res.text());
|
||||
const html = await fixture.fetch('/base/foo').then((res) => res.text());
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
assert.equal($('h1').text(), 'Index');
|
||||
|
|
Loading…
Add table
Reference in a new issue