Skip to content

Commit 0fa5c82

Browse files
ematipicoascorbic
andauthored
fix(i18n): server island request (#13112)
Co-authored-by: ascorbic <213306+ascorbic@users.noreply.github.com>
1 parent 3a26e45 commit 0fa5c82

File tree

13 files changed

+126
-11
lines changed

13 files changed

+126
-11
lines changed

.changeset/silent-worms-whisper.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes a bug where the i18n middleware was blocking a server island request when the `prefixDefaultLocale` option is set to `true`

packages/astro/src/core/render-context.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@ import { callMiddleware } from './middleware/callMiddleware.js';
2929
import { sequence } from './middleware/index.js';
3030
import { renderRedirect } from './redirects/render.js';
3131
import { type Pipeline, Slots, getParams, getProps } from './render/index.js';
32-
import { isRoute404or500 } from './routing/match.js';
32+
import { isRoute404or500, isRouteServerIsland } from './routing/match.js';
3333
import { copyRequest, getOriginPathname, setOriginPathname } from './routing/rewrite.js';
34-
import { SERVER_ISLAND_COMPONENT } from './server-islands/endpoint.js';
3534
import { AstroSession } from './session.js';
3635

3736
export const apiContextRoutesSymbol = Symbol.for('context.routes');
@@ -596,7 +595,7 @@ export class RenderContext {
596595
}
597596

598597
let computedLocale;
599-
if (routeData.component === SERVER_ISLAND_COMPONENT) {
598+
if (isRouteServerIsland(routeData)) {
600599
let referer = this.request.headers.get('referer');
601600
if (referer) {
602601
if (URL.canParse(referer)) {

packages/astro/src/core/routing/match.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { RoutesList } from '../../types/astro.js';
22
import type { RouteData } from '../../types/public/internal.js';
3+
import { SERVER_ISLAND_BASE_PREFIX, SERVER_ISLAND_COMPONENT } from '../server-islands/endpoint.js';
34

45
/** Find matching route from pathname */
56
export function matchRoute(pathname: string, manifest: RoutesList): RouteData | undefined {
@@ -37,3 +38,41 @@ export function isRoute500(route: string) {
3738
export function isRoute404or500(route: RouteData): boolean {
3839
return isRoute404(route.route) || isRoute500(route.route);
3940
}
41+
42+
/**
43+
* Determines if a given route is associated with the server island component.
44+
*
45+
* @param {RouteData} route - The route data object to evaluate.
46+
* @return {boolean} Returns true if the route's component is the server island component, otherwise false.
47+
*/
48+
export function isRouteServerIsland(route: RouteData): boolean {
49+
return route.component === SERVER_ISLAND_COMPONENT;
50+
}
51+
52+
/**
53+
* Determines whether the given `Request` is targeted to a "server island" based on its URL.
54+
*
55+
* @param {Request} request - The request object to be evaluated.
56+
* @param {string} [base=''] - The base path provided via configuration.
57+
* @return {boolean} - Returns `true` if the request is for a server island, otherwise `false`.
58+
*/
59+
export function isRequestServerIsland(request: Request, base = ''): boolean {
60+
const url = new URL(request.url);
61+
const pathname = url.pathname.slice(base.length);
62+
63+
return pathname.startsWith(SERVER_ISLAND_BASE_PREFIX);
64+
}
65+
66+
/**
67+
* Checks if the given request corresponds to a 404 or 500 route based on the specified base path.
68+
*
69+
* @param {Request} request - The HTTP request object to be checked.
70+
* @param {string} [base=''] - The base path to trim from the request's URL before checking the route. Default is an empty string.
71+
* @return {boolean} Returns true if the request matches a 404 or 500 route; otherwise, returns false.
72+
*/
73+
export function requestIs404Or500(request: Request, base = '') {
74+
const url = new URL(request.url);
75+
const pathname = url.pathname.slice(base.length);
76+
77+
return isRoute404(pathname) || isRoute500(pathname);
78+
}

packages/astro/src/core/server-islands/endpoint.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { getPattern } from '../routing/manifest/pattern.js';
1313

1414
export const SERVER_ISLAND_ROUTE = '/_server-islands/[name]';
1515
export const SERVER_ISLAND_COMPONENT = '_server-islands.astro';
16+
export const SERVER_ISLAND_BASE_PREFIX = '_server-islands';
1617

1718
type ConfigFields = Pick<SSRManifest, 'base' | 'trailingSlash'>;
1819

packages/astro/src/i18n/index.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ export function requestHasLocale(locales: Locales) {
1616
};
1717
}
1818

19-
export function requestIs404Or500(request: Request, base = '') {
20-
const url = new URL(request.url);
21-
const pathname = url.pathname.slice(base.length);
22-
23-
return isRoute404(pathname) || isRoute500(pathname);
24-
}
25-
2619
// Checks if the pathname has any locale
2720
export function pathHasLocale(path: string, locales: Locales): boolean {
2821
const segments = path.split('/');

packages/astro/src/i18n/middleware.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import {
99
redirectToDefaultLocale,
1010
redirectToFallback,
1111
requestHasLocale,
12-
requestIs404Or500,
1312
} from './index.js';
13+
import { isRequestServerIsland, requestIs404Or500 } from '../core/routing/match.js';
1414

1515
export function createI18nMiddleware(
1616
i18n: SSRManifest['i18n'],
@@ -82,6 +82,12 @@ export function createI18nMiddleware(
8282
return response;
8383
}
8484

85+
// This is a case where the rendering phase belongs to a server island. Server island are
86+
// special routes, and should be exhempt from i18n routing
87+
if (isRequestServerIsland(context.request, base)) {
88+
return response;
89+
}
90+
8591
const { currentLocale } = context;
8692
switch (i18n.strategy) {
8793
// NOTE: theoretically, we should never hit this code path
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { defineConfig } from "astro/config";
2+
3+
export default defineConfig({
4+
i18n: {
5+
defaultLocale: 'en',
6+
locales: [
7+
'en', 'pt', 'it', {
8+
path: "spanish",
9+
codes: ["es", "es-ar"]
10+
}
11+
],
12+
routing: {
13+
prefixDefaultLocale: true
14+
}
15+
},
16+
base: "/new-site"
17+
})
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "@test/i18n-server-island",
3+
"version": "0.0.0",
4+
"private": true,
5+
"dependencies": {
6+
"astro": "workspace:*"
7+
}
8+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>I am a server island</p>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
import Island from "../../components/Island.astro"
3+
---
4+
5+
<Island server:defer />

0 commit comments

Comments
 (0)