From e3bfd9396969caf35b3b05135539e82aab560c92 Mon Sep 17 00:00:00 2001 From: mtwilliams Date: Thu, 12 Dec 2024 08:29:46 -0500 Subject: [PATCH] fix(i18n): parse params and props correctly with fallback (#12709) Co-authored-by: Emanuele Stoppa --- .changeset/selfish-paws-play.md | 5 ++ .../astro/src/core/render/params-and-props.ts | 9 ++- .../src/pages/blog/[id].astro | 16 +++- .../src/pages/pt/blog/[id].astro | 10 ++- packages/astro/test/i18n-routing.test.js | 74 ++++++++++++++++++- 5 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 .changeset/selfish-paws-play.md diff --git a/.changeset/selfish-paws-play.md b/.changeset/selfish-paws-play.md new file mode 100644 index 0000000000..6f0e3049b2 --- /dev/null +++ b/.changeset/selfish-paws-play.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a bug where Astro couldn't correctly parse `params` and `props` when receiving i18n fallback URLs diff --git a/packages/astro/src/core/render/params-and-props.ts b/packages/astro/src/core/render/params-and-props.ts index c1fe318ceb..f8799115b0 100644 --- a/packages/astro/src/core/render/params-and-props.ts +++ b/packages/astro/src/core/render/params-and-props.ts @@ -47,7 +47,8 @@ export async function getProps(opts: GetParamsAndPropsOptions): Promise { 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 // the ones expected from the users const params = getParams(route, decodeURI(pathname)); @@ -77,7 +78,11 @@ export function getParams(route: RouteData, pathname: string): Params { if (!route.params.length) return {}; // The RegExp pattern expects a decoded string, but the pathname is encoded // 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 {}; const params: Params = {}; route.params.forEach((key, i) => { diff --git a/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/blog/[id].astro b/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/blog/[id].astro index 97b41230d6..f277b93efe 100644 --- a/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/blog/[id].astro +++ b/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/blog/[id].astro @@ -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() { return [ {params: {id: '1'}, props: { content: "Hello world" }}, {params: {id: '2'}, props: { content: "Eat Something" }}, {params: {id: '3'}, props: { content: "How are you?" }}, - ]; + ] } -const { content } = Astro.props; +const { content } = Astro.props --- Astro -{content} +{content || ssrContent} diff --git a/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/pt/blog/[id].astro b/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/pt/blog/[id].astro index e37f83a302..ed4415fc5b 100644 --- a/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/pt/blog/[id].astro +++ b/packages/astro/test/fixtures/i18n-routing-fallback/src/pages/pt/blog/[id].astro @@ -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() { return [ {params: {id: '1'}, props: { content: "Hola mundo" }}, @@ -13,6 +21,6 @@ const { content } = Astro.props; Astro -{content} +{content || ssrContent} diff --git a/packages/astro/test/i18n-routing.test.js b/packages/astro/test/i18n-routing.test.js index 441823fc7a..28f4b05f80 100644 --- a/packages/astro/test/i18n-routing.test.js +++ b/packages/astro/test/i18n-routing.test.js @@ -1,6 +1,6 @@ +import * as cheerio from 'cheerio'; import * as assert from 'node:assert/strict'; import { after, afterEach, before, describe, it } from 'node:test'; -import * as cheerio from 'cheerio'; import testAdapter from './test-adapter.js'; import { loadFixture } from './test-utils.js'; @@ -2000,12 +2000,14 @@ describe('Fallback rewrite dev server', () => { root: './fixtures/i18n-routing-fallback/', i18n: { defaultLocale: 'en', - locales: ['en', 'fr'], + locales: ['en', 'fr', 'es', 'it', 'pt'], routing: { prefixDefaultLocale: false, }, fallback: { fr: 'en', + it: 'en', + es: 'pt', }, fallbackType: 'rewrite', }, @@ -2021,6 +2023,27 @@ describe('Fallback rewrite dev server', () => { assert.match(html, /Hello/); // 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', () => { @@ -2032,13 +2055,15 @@ describe('Fallback rewrite SSG', () => { root: './fixtures/i18n-routing-fallback/', i18n: { defaultLocale: 'en', - locales: ['en', 'fr'], + locales: ['en', 'fr', 'es', 'it', 'pt'], routing: { prefixDefaultLocale: false, fallbackType: 'rewrite', }, fallback: { fr: 'en', + it: 'en', + es: 'pt', }, }, }); @@ -2051,6 +2076,21 @@ describe('Fallback rewrite SSG', () => { assert.match(html, /Hello/); // 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', () => { @@ -2066,13 +2106,15 @@ describe('Fallback rewrite SSR', () => { adapter: testAdapter(), i18n: { defaultLocale: 'en', - locales: ['en', 'fr'], + locales: ['en', 'fr', 'es', 'it', 'pt'], routing: { prefixDefaultLocale: false, fallbackType: 'rewrite', }, fallback: { fr: 'en', + it: 'en', + es: 'pt', }, }, }); @@ -2087,4 +2129,28 @@ describe('Fallback rewrite SSR', () => { const html = await response.text(); 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/); + }); });