Skip to content

Commit 7260367

Browse files
razonyangematipicoflorian-lefebvre
authored
fix(i18n): ensure the redirect pathname is non-empty (#13730)
Co-authored-by: Emanuele Stoppa <[email protected]> Co-authored-by: Florian Lefebvre <[email protected]>
1 parent d1b3409 commit 7260367

File tree

3 files changed

+54
-0
lines changed

3 files changed

+54
-0
lines changed

.changeset/olive-papers-hide.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 in i18n, where Astro caused an infinite loop when a locale that doesn't have an index, and Astro falls back to the index of the default locale.

packages/astro/src/i18n/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,11 @@ export function redirectToFallback({
399399
if (pathFallbackLocale === defaultLocale && strategy === 'pathname-prefix-other-locales') {
400400
if (context.url.pathname.includes(`${base}`)) {
401401
newPathname = context.url.pathname.replace(`/${urlLocale}`, ``);
402+
// Ensure the pathname are non-empty. Redirects like "/fr" => "" may create infinite loops,
403+
// as the "Location" response header is empty.
404+
if (newPathname === '') {
405+
newPathname = '/';
406+
}
402407
} else {
403408
newPathname = context.url.pathname.replace(`/${urlLocale}`, `/`);
404409
}

packages/astro/test/i18n-routing.test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,50 @@ describe('[SSG] i18n routing', () => {
774774
});
775775
});
776776

777+
describe('i18n routing with routing strategy [prefix-other-locales] with root base', () => {
778+
/** @type {import('./test-utils').Fixture} */
779+
let fixture;
780+
/** @type {import('./test-utils').DevServer} */
781+
let devServer;
782+
783+
before(async () => {
784+
fixture = await loadFixture({
785+
root: './fixtures/i18n-routing-prefix-other-locales/',
786+
output: 'server',
787+
adapter: testAdapter(),
788+
base: '/',
789+
i18n: {
790+
defaultLocale: 'en',
791+
locales: ['en', 'pt', 'fr'],
792+
fallback: {
793+
fr: 'en',
794+
},
795+
routing: {
796+
prefixDefaultLocale: false,
797+
redirectToDefaultLocale: true,
798+
fallbackType: 'redirect',
799+
},
800+
},
801+
});
802+
await fixture.build();
803+
devServer = await fixture.startDevServer();
804+
});
805+
806+
afterEach(async () => {
807+
devServer.stop();
808+
});
809+
810+
it('should redirect to English page', async () => {
811+
const response = await fixture.fetch('/fr', { redirect: 'manual' });
812+
assert.equal(response.headers.get('Location'), '/');
813+
assert.equal(response.status, 302);
814+
815+
const followRedirectResponse = await fixture.fetch('/fr');
816+
assert.equal(followRedirectResponse.status, 200);
817+
assert.equal((await followRedirectResponse.text()).includes('Hello'), true);
818+
});
819+
});
820+
777821
describe('i18n routing with routing strategy [pathname-prefix-always-no-redirect]', () => {
778822
/** @type {import('./test-utils').Fixture} */
779823
let fixture;

0 commit comments

Comments
 (0)