0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00

fix(i18n): parse params and props correctly with fallback (#12709)

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
This commit is contained in:
mtwilliams 2024-12-12 08:29:46 -05:00 committed by GitHub
parent 358eae83b7
commit e3bfd93969
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 104 additions and 10 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes a bug where Astro couldn't correctly parse `params` and `props` when receiving i18n fallback URLs

View file

@ -47,7 +47,8 @@ export async function getProps(opts: GetParamsAndPropsOptions): Promise<Props> {
base, base,
}); });
// The pathname used here comes from the server, which already encored. if (!staticPaths.length) return {};
// The pathname used here comes from the server, which already encoded.
// Since we decided to not mess up with encoding anymore, we need to decode them back so the parameters can match // Since we decided to not mess up with encoding anymore, we need to decode them back so the parameters can match
// the ones expected from the users // the ones expected from the users
const params = getParams(route, decodeURI(pathname)); const params = getParams(route, decodeURI(pathname));
@ -77,7 +78,11 @@ export function getParams(route: RouteData, pathname: string): Params {
if (!route.params.length) return {}; if (!route.params.length) return {};
// The RegExp pattern expects a decoded string, but the pathname is encoded // The RegExp pattern expects a decoded string, but the pathname is encoded
// when the URL contains non-English characters. // when the URL contains non-English characters.
const paramsMatch = route.pattern.exec(pathname); const paramsMatch =
route.pattern.exec(pathname) ||
route.fallbackRoutes
.map((fallbackRoute) => fallbackRoute.pattern.exec(pathname))
.find((x) => x);
if (!paramsMatch) return {}; if (!paramsMatch) return {};
const params: Params = {}; const params: Params = {};
route.params.forEach((key, i) => { route.params.forEach((key, i) => {

View file

@ -1,18 +1,28 @@
--- ---
// for SSR
const blogs = {
1: { content: "Hello world" },
2: { content: "Eat Something" },
3: { content: "How are you?" },
}
const id = Astro.params?.id;
const ssrContent = id && blogs[id]?.content;
// for SSG
export function getStaticPaths() { export function getStaticPaths() {
return [ return [
{params: {id: '1'}, props: { content: "Hello world" }}, {params: {id: '1'}, props: { content: "Hello world" }},
{params: {id: '2'}, props: { content: "Eat Something" }}, {params: {id: '2'}, props: { content: "Eat Something" }},
{params: {id: '3'}, props: { content: "How are you?" }}, {params: {id: '3'}, props: { content: "How are you?" }},
]; ]
} }
const { content } = Astro.props; const { content } = Astro.props
--- ---
<html> <html>
<head> <head>
<title>Astro</title> <title>Astro</title>
</head> </head>
<body> <body>
{content} {content || ssrContent}
</body> </body>
</html> </html>

View file

@ -1,4 +1,12 @@
--- ---
const blogs = {
1: { content: "Hola mundo" },
2: { content: "Eat Something" },
3: { content: "How are you?" },
}
const id = Astro.params?.id;
const ssrContent = id && blogs[id]?.content;
export function getStaticPaths() { export function getStaticPaths() {
return [ return [
{params: {id: '1'}, props: { content: "Hola mundo" }}, {params: {id: '1'}, props: { content: "Hola mundo" }},
@ -13,6 +21,6 @@ const { content } = Astro.props;
<title>Astro</title> <title>Astro</title>
</head> </head>
<body> <body>
{content} {content || ssrContent}
</body> </body>
</html> </html>

View file

@ -1,6 +1,6 @@
import * as cheerio from 'cheerio';
import * as assert from 'node:assert/strict'; import * as assert from 'node:assert/strict';
import { after, afterEach, before, describe, it } from 'node:test'; import { after, afterEach, before, describe, it } from 'node:test';
import * as cheerio from 'cheerio';
import testAdapter from './test-adapter.js'; import testAdapter from './test-adapter.js';
import { loadFixture } from './test-utils.js'; import { loadFixture } from './test-utils.js';
@ -2000,12 +2000,14 @@ describe('Fallback rewrite dev server', () => {
root: './fixtures/i18n-routing-fallback/', root: './fixtures/i18n-routing-fallback/',
i18n: { i18n: {
defaultLocale: 'en', defaultLocale: 'en',
locales: ['en', 'fr'], locales: ['en', 'fr', 'es', 'it', 'pt'],
routing: { routing: {
prefixDefaultLocale: false, prefixDefaultLocale: false,
}, },
fallback: { fallback: {
fr: 'en', fr: 'en',
it: 'en',
es: 'pt',
}, },
fallbackType: 'rewrite', fallbackType: 'rewrite',
}, },
@ -2021,6 +2023,27 @@ describe('Fallback rewrite dev server', () => {
assert.match(html, /Hello/); assert.match(html, /Hello/);
// assert.fail() // assert.fail()
}); });
it('should render fallback locale paths with path parameters correctly (fr)', async () => {
let response = await fixture.fetch('/fr/blog/1');
assert.equal(response.status, 200);
const text = await response.text();
assert.match(text, /Hello world/);
});
it('should render fallback locale paths with path parameters correctly (es)', async () => {
let response = await fixture.fetch('/es/blog/1');
assert.equal(response.status, 200);
const text = await response.text();
assert.match(text, /Hola mundo/);
});
it('should render fallback locale paths with query parameters correctly (it)', async () => {
let response = await fixture.fetch('/it/blog/1');
assert.equal(response.status, 200);
const text = await response.text();
assert.match(text, /Hello world/);
});
}); });
describe('Fallback rewrite SSG', () => { describe('Fallback rewrite SSG', () => {
@ -2032,13 +2055,15 @@ describe('Fallback rewrite SSG', () => {
root: './fixtures/i18n-routing-fallback/', root: './fixtures/i18n-routing-fallback/',
i18n: { i18n: {
defaultLocale: 'en', defaultLocale: 'en',
locales: ['en', 'fr'], locales: ['en', 'fr', 'es', 'it', 'pt'],
routing: { routing: {
prefixDefaultLocale: false, prefixDefaultLocale: false,
fallbackType: 'rewrite', fallbackType: 'rewrite',
}, },
fallback: { fallback: {
fr: 'en', fr: 'en',
it: 'en',
es: 'pt',
}, },
}, },
}); });
@ -2051,6 +2076,21 @@ describe('Fallback rewrite SSG', () => {
assert.match(html, /Hello/); assert.match(html, /Hello/);
// assert.fail() // assert.fail()
}); });
it('should render fallback locale paths with path parameters correctly (fr)', async () => {
const html = await fixture.readFile('/fr/blog/1/index.html');
assert.match(html, /Hello world/);
});
it('should render fallback locale paths with path parameters correctly (es)', async () => {
const html = await fixture.readFile('/es/blog/1/index.html');
assert.match(html, /Hola mundo/);
});
it('should render fallback locale paths with query parameters correctly (it)', async () => {
const html = await fixture.readFile('/it/blog/1/index.html');
assert.match(html, /Hello world/);
});
}); });
describe('Fallback rewrite SSR', () => { describe('Fallback rewrite SSR', () => {
@ -2066,13 +2106,15 @@ describe('Fallback rewrite SSR', () => {
adapter: testAdapter(), adapter: testAdapter(),
i18n: { i18n: {
defaultLocale: 'en', defaultLocale: 'en',
locales: ['en', 'fr'], locales: ['en', 'fr', 'es', 'it', 'pt'],
routing: { routing: {
prefixDefaultLocale: false, prefixDefaultLocale: false,
fallbackType: 'rewrite', fallbackType: 'rewrite',
}, },
fallback: { fallback: {
fr: 'en', fr: 'en',
it: 'en',
es: 'pt',
}, },
}, },
}); });
@ -2087,4 +2129,28 @@ describe('Fallback rewrite SSR', () => {
const html = await response.text(); const html = await response.text();
assert.match(html, /Hello/); assert.match(html, /Hello/);
}); });
it('should render fallback locale paths with path parameters correctly (fr)', async () => {
let request = new Request('http://example.com/new-site/fr/blog/1');
let response = await app.render(request);
assert.equal(response.status, 200);
const text = await response.text();
assert.match(text, /Hello world/);
});
it('should render fallback locale paths with path parameters correctly (es)', async () => {
let request = new Request('http://example.com/new-site/es/blog/1');
let response = await app.render(request);
assert.equal(response.status, 200);
const text = await response.text();
assert.match(text, /Hola mundo/);
});
it('should render fallback locale paths with query parameters correctly (it)', async () => {
let request = new Request('http://example.com/new-site/it/blog/1');
let response = await app.render(request);
assert.equal(response.status, 200);
const text = await response.text();
assert.match(text, /Hello world/);
});
}); });