Skip to content

Commit 1a9107a

Browse files
Properly compute currentLocale when path segment contains .html (#14229)
Co-authored-by: Chris Swithinbank <swithinbank@gmail.com>
1 parent eadc9dd commit 1a9107a

9 files changed

Lines changed: 173 additions & 3 deletions

File tree

.changeset/weak-maps-give.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+
Ensures `Astro.currentLocale` returns the correct locale during SSG for pages that use a locale param (such as `[locale].astro` or `[locale]/index.astro`, which produce `[locale].html`)

packages/astro/src/i18n/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ export function requestHasLocale(locales: Locales) {
1717

1818
// Checks if the pathname has any locale
1919
export function pathHasLocale(path: string, locales: Locales): boolean {
20-
const segments = path.split('/');
20+
// pages that use a locale param ([locale].astro or [locale]/index.astro)
21+
// and getStaticPaths make [locale].html the pathname during SSG
22+
// which will not match a configured locale without removing .html
23+
// as we do in normalizeThePath
24+
const segments = path.split('/').map(normalizeThePath);
2125
for (const segment of segments) {
2226
for (const locale of locales) {
2327
if (typeof locale === 'string') {
@@ -225,6 +229,15 @@ export function normalizeTheLocale(locale: string): string {
225229
return locale.replaceAll('_', '-').toLowerCase();
226230
}
227231

232+
/**
233+
*
234+
* Given a path or path segment, this function:
235+
* - removes the `.html` extension if it exists
236+
*/
237+
export function normalizeThePath(path: string): string {
238+
return path.endsWith('.html') ? path.slice(0, -5) : path;
239+
}
240+
228241
/**
229242
* Returns an array of only locales, by picking the `code`
230243
* @param locales

packages/astro/src/i18n/utils.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { SSRManifest } from '../core/app/types.js';
22
import type { AstroConfig, Locales } from '../types/public/config.js';
3-
import { getAllCodes, normalizeTheLocale } from './index.js';
3+
import { getAllCodes, normalizeTheLocale, normalizeThePath } from './index.js';
44

55
type BrowserLocale = {
66
locale: string;
@@ -149,7 +149,11 @@ export function computeCurrentLocale(
149149
locales: Locales,
150150
defaultLocale: string,
151151
): string | undefined {
152-
for (const segment of pathname.split('/')) {
152+
// pages that use a locale param ([locale].astro or [locale]/index.astro)
153+
// and getStaticPaths make [locale].html the pathname during SSG
154+
// which will not match a configured locale without removing .html
155+
// as we do in normalizeThePath
156+
for (const segment of pathname.split('/').map(normalizeThePath)) {
153157
for (const locale of locales) {
154158
if (typeof locale === 'string') {
155159
// we skip ta locale that isn't present in the current segment
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { defineConfig } from 'astro/config';
2+
3+
export default defineConfig({
4+
build: {
5+
format: 'file',
6+
},
7+
output: 'static',
8+
trailingSlash: 'never',
9+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "@test/i18n-locale-index-format-file",
3+
"version": "0.0.0",
4+
"private": true,
5+
"dependencies": {
6+
"astro": "workspace:*"
7+
}
8+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
export const getStaticPaths = async () => {
3+
return ['fr-fr'].map((locale) => ({
4+
params: {
5+
locale,
6+
},
7+
}))
8+
}
9+
10+
const currentLocale = Astro.currentLocale;
11+
---
12+
<html>
13+
<head>
14+
<title>Astro</title>
15+
</head>
16+
<body>
17+
currentLocale: {currentLocale}
18+
</body>
19+
</html>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
export const getStaticPaths = async () => {
3+
return ['en-us', 'es-mx'].map((locale) => ({
4+
params: {
5+
locale,
6+
},
7+
}))
8+
}
9+
10+
const currentLocale = Astro.currentLocale;
11+
---
12+
<html>
13+
<head>
14+
<title>Astro</title>
15+
</head>
16+
<body>
17+
currentLocale: {currentLocale}
18+
</body>
19+
</html>

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

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,6 +1268,93 @@ describe('[SSG] i18n routing', () => {
12681268
});
12691269
});
12701270

1271+
describe('when `build.format` is `file`, locales array contains objects, and locale indexes use getStaticPaths', () => {
1272+
/** @type {import('./test-utils').Fixture} */
1273+
let fixture;
1274+
1275+
before(async () => {
1276+
fixture = await loadFixture({
1277+
root: './fixtures/i18n-locale-index-format-file/',
1278+
i18n: {
1279+
defaultLocale: 'en-us',
1280+
locales: [
1281+
{
1282+
path: 'en-us',
1283+
codes: ['en-US'],
1284+
},
1285+
{
1286+
path: 'es-mx',
1287+
codes: ['es-MX'],
1288+
},
1289+
{
1290+
path: 'fr-fr',
1291+
codes: ['fr-FR'],
1292+
},
1293+
],
1294+
routing: {
1295+
prefixDefaultLocale: true,
1296+
redirectToDefaultLocale: false,
1297+
},
1298+
},
1299+
});
1300+
await fixture.build();
1301+
});
1302+
1303+
it('should return the locale code of the current URL (en-US)', async () => {
1304+
const html = await fixture.readFile('/en-us.html');
1305+
assert.equal(html.includes('currentLocale: en-US'), true);
1306+
});
1307+
1308+
it('should return the locale code of the current URL (es-MX)', async () => {
1309+
const html = await fixture.readFile('/es-mx.html');
1310+
assert.equal(html.includes('currentLocale: es-MX'), true);
1311+
});
1312+
1313+
it('should return the locale code of the current URL (fr-FR)', async () => {
1314+
const html = await fixture.readFile('/fr-fr.html');
1315+
assert.equal(html.includes('currentLocale: fr-FR'), true);
1316+
});
1317+
});
1318+
1319+
describe('when `build.format` is `file`, locales array contains strings, and locale indexes use getStaticPaths', () => {
1320+
/** @type {import('./test-utils').Fixture} */
1321+
let fixture;
1322+
1323+
before(async () => {
1324+
fixture = await loadFixture({
1325+
root: './fixtures/i18n-locale-index-format-file/',
1326+
i18n: {
1327+
defaultLocale: 'en-us',
1328+
locales: [
1329+
'en-us',
1330+
'es-mx',
1331+
'fr-fr',
1332+
],
1333+
routing: {
1334+
prefixDefaultLocale: true,
1335+
redirectToDefaultLocale: false,
1336+
},
1337+
}
1338+
});
1339+
await fixture.build();
1340+
});
1341+
1342+
it('should return the locale of the current URL (en-us)', async () => {
1343+
const html = await fixture.readFile('/en-us.html');
1344+
assert.equal(html.includes('currentLocale: en-us'), true);
1345+
});
1346+
1347+
it('should return the locale of the current URL (es-mx)', async () => {
1348+
const html = await fixture.readFile('/es-mx.html');
1349+
assert.equal(html.includes('currentLocale: es-mx'), true);
1350+
});
1351+
1352+
it('should return the locale of the current URL (fr-fr)', async () => {
1353+
const html = await fixture.readFile('/fr-fr.html');
1354+
assert.equal(html.includes('currentLocale: fr-fr'), true);
1355+
});
1356+
});
1357+
12711358
describe('with dynamic paths', async () => {
12721359
/** @type {import('./test-utils').Fixture} */
12731360
let fixture;

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)