Skip to content

Commit 9c85f86

Browse files
authored
fix(core): optimize i18n integration for site builds + improve inference of locale config (#11550)
1 parent 6a38ccd commit 9c85f86

File tree

7 files changed

+116
-37
lines changed

7 files changed

+116
-37
lines changed

.github/workflows/tests-e2e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
runs-on: ubuntu-latest
3939
strategy:
4040
matrix:
41-
node: ['20.0', '20', '22', '24', '25']
41+
node: ['20.0', '20', '22', '24', '25.1']
4242
steps:
4343
- name: Checkout
4444
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

.github/workflows/tests-windows.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
runs-on: windows-latest
2828
strategy:
2929
matrix:
30-
node: ['20.0', '20', '22', '24', '25']
30+
node: ['20.0', '20', '22', '24', '25.1']
3131
steps:
3232
- name: Support longpaths
3333
run: git config --system core.longpaths true

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
runs-on: ubuntu-latest
2828
strategy:
2929
matrix:
30-
node: ['20.0', '20', '22', '24', '25']
30+
node: ['20.0', '20', '22', '24', '25.1']
3131
steps:
3232
- name: Checkout
3333
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

.prettierignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
dist
22
node_modules
33
.yarn
4-
build
4+
**/build/**
55
coverage
66
.docusaurus
77
.idea
@@ -11,6 +11,8 @@ coverage
1111

1212
jest/vendor
1313

14+
argos/test-results
15+
1416
packages/lqip-loader/lib/
1517
packages/docusaurus/lib/
1618
packages/docusaurus-*/lib/*

packages/docusaurus/src/commands/build/build.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
import fs from 'fs-extra';
99
import logger, {PerfLogger} from '@docusaurus/logger';
1010
import {mapAsyncSequential} from '@docusaurus/utils';
11-
import {loadContext, type LoadContextParams} from '../../server/site';
12-
import {loadI18n} from '../../server/i18n';
11+
import {type LoadContextParams} from '../../server/site';
12+
import {loadI18nLocaleList} from '../../server/i18n';
1313
import {buildLocale, type BuildLocaleParams} from './buildLocale';
14-
import {isAutomaticBaseUrlLocalizationDisabled} from './buildUtils';
14+
import {loadSiteConfig} from '../../server/config';
1515

1616
export type BuildCLIOptions = Pick<LoadContextParams, 'config' | 'outDir'> & {
1717
locale?: [string, ...string[]];
@@ -81,27 +81,21 @@ async function getLocalesToBuild({
8181
siteDir: string;
8282
cliOptions: BuildCLIOptions;
8383
}): Promise<[string, ...string[]]> {
84-
// TODO we shouldn't need to load all context + i18n just to get that list
85-
// only loading siteConfig should be enough
86-
const context = await loadContext({
84+
const {siteConfig} = await loadSiteConfig({
8785
siteDir,
88-
outDir: cliOptions.outDir,
89-
config: cliOptions.config,
90-
automaticBaseUrlLocalizationDisabled: isAutomaticBaseUrlLocalizationDisabled(cliOptions),
86+
customConfigFilePath: cliOptions.config,
9187
});
9288

93-
const i18n = await loadI18n({
94-
siteDir,
95-
config: context.siteConfig,
96-
currentLocale: context.siteConfig.i18n.defaultLocale, // Awkward but ok
97-
automaticBaseUrlLocalizationDisabled: false,
98-
});
99-
100-
const locales = cliOptions.locale ?? i18n.locales;
89+
const locales =
90+
cliOptions.locale ??
91+
loadI18nLocaleList({
92+
i18nConfig: siteConfig.i18n,
93+
currentLocale: siteConfig.i18n.defaultLocale, // Awkward but ok
94+
});
10195

10296
return orderLocales({
10397
locales: locales as [string, ...string[]],
104-
defaultLocale: i18n.defaultLocale,
98+
defaultLocale: siteConfig.i18n.defaultLocale,
10599
});
106100
}
107101

packages/docusaurus/src/server/__tests__/i18n.test.ts

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,11 @@ describe('defaultLocaleConfig', () => {
123123
});
124124

125125
describe('loadI18n', () => {
126-
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
126+
const consoleWarnSpy = jest
127+
.spyOn(console, 'warn')
128+
.mockImplementation(() => {});
127129
beforeEach(() => {
128-
consoleSpy.mockClear();
130+
consoleWarnSpy.mockClear();
129131
});
130132

131133
it('loads I18n for default config', async () => {
@@ -397,8 +399,67 @@ describe('loadI18n', () => {
397399
},
398400
currentLocale: 'it',
399401
});
400-
expect(consoleSpy.mock.calls[0]![0]).toMatch(
401-
/The locale .*it.* was not found in your site configuration/,
402+
expect(consoleWarnSpy.mock.calls[0]![0]).toMatch(
403+
/The locale .*it.* was not found in your Docusaurus site configuration/,
402404
);
403405
});
406+
407+
it('throws when trying to load undeclared locale that is not a valid locale BCP47 name', async () => {
408+
await expect(() =>
409+
loadI18nTest({
410+
i18nConfig: {
411+
path: 'i18n',
412+
defaultLocale: 'en',
413+
locales: ['en', 'fr', 'de'],
414+
localeConfigs: {},
415+
},
416+
currentLocale: 'x1',
417+
}),
418+
).rejects.toThrowErrorMatchingInlineSnapshot(`
419+
"Docusaurus couldn't infer a default locale config for x1.
420+
Make sure it is a valid BCP 47 locale name (e.g. en, fr, fr-FR, etc.) and/or provide a valid BCP 47 \`siteConfig.i18n.localeConfig['x1'].htmlLang\` attribute."
421+
`);
422+
});
423+
424+
it('throws when trying to load declared locale that is not a valid locale BCP47 name', async () => {
425+
await expect(() =>
426+
loadI18nTest({
427+
i18nConfig: {
428+
path: 'i18n',
429+
defaultLocale: 'fr',
430+
locales: ['en', 'fr', 'de'],
431+
localeConfigs: {x1: {}},
432+
},
433+
currentLocale: 'x1',
434+
}),
435+
).rejects.toThrowErrorMatchingInlineSnapshot(`
436+
"Docusaurus couldn't infer a default locale config for x1.
437+
Make sure it is a valid BCP 47 locale name (e.g. en, fr, fr-FR, etc.) and/or provide a valid BCP 47 \`siteConfig.i18n.localeConfig['x1'].htmlLang\` attribute."
438+
`);
439+
});
440+
441+
it('loads i18n when trying to load declared locale with invalid BCP47 name but valid BCP47', async () => {
442+
const result = await loadI18nTest({
443+
i18nConfig: {
444+
path: 'i18n',
445+
defaultLocale: 'en',
446+
locales: ['en', 'fr', 'x1'],
447+
localeConfigs: {
448+
x1: {htmlLang: 'en-US'},
449+
},
450+
},
451+
currentLocale: 'x1',
452+
});
453+
expect(result.localeConfigs.x1).toEqual({
454+
baseUrl: '/x1/',
455+
calendar: 'gregory',
456+
direction: 'ltr',
457+
htmlLang: 'en-US',
458+
label: 'American English',
459+
path: 'en-US',
460+
translate: false,
461+
url: 'https://example.com',
462+
});
463+
expect(consoleWarnSpy).toHaveBeenCalledTimes(0);
464+
});
404465
});

packages/docusaurus/src/server/i18n.ts

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import fs from 'fs-extra';
1010
import logger from '@docusaurus/logger';
1111
import combinePromises from 'combine-promises';
1212
import {normalizeUrl} from '@docusaurus/utils';
13-
import type {I18n, DocusaurusConfig, I18nLocaleConfig} from '@docusaurus/types';
13+
import type {
14+
I18n,
15+
DocusaurusConfig,
16+
I18nLocaleConfig,
17+
I18nConfig,
18+
} from '@docusaurus/types';
1419

1520
function inferLanguageDisplayName(locale: string) {
1621
const tryLocale = (l: string) => {
@@ -95,12 +100,33 @@ export function getDefaultLocaleConfig(
95100
};
96101
} catch (e) {
97102
throw new Error(
98-
`Docusaurus couldn't get default locale config for ${locale}`,
103+
`Docusaurus couldn't infer a default locale config for ${logger.name(
104+
locale,
105+
)}.
106+
Make sure it is a valid BCP 47 locale name (e.g. en, fr, fr-FR, etc.) and/or provide a valid BCP 47 ${logger.code(
107+
`siteConfig.i18n.localeConfig['${locale}'].htmlLang`,
108+
)} attribute.`,
99109
{cause: e},
100110
);
101111
}
102112
}
103113

114+
export function loadI18nLocaleList({
115+
i18nConfig,
116+
currentLocale,
117+
}: {
118+
i18nConfig: I18nConfig;
119+
currentLocale: string;
120+
}): [string, ...string[]] {
121+
if (!i18nConfig.locales.includes(currentLocale)) {
122+
logger.warn`The locale name=${currentLocale} was not found in your Docusaurus site configuration.
123+
We recommend adding the name=${currentLocale} to your site i18n config, but we will still try to run your site.
124+
Declared site config locales are: ${i18nConfig.locales}`;
125+
return i18nConfig.locales.concat(currentLocale) as [string, ...string[]];
126+
}
127+
return i18nConfig.locales;
128+
}
129+
104130
export async function loadI18n({
105131
siteDir,
106132
config,
@@ -114,14 +140,10 @@ export async function loadI18n({
114140
}): Promise<I18n> {
115141
const {i18n: i18nConfig} = config;
116142

117-
if (!i18nConfig.locales.includes(currentLocale)) {
118-
logger.warn`The locale name=${currentLocale} was not found in your site configuration: Available locales are: ${i18nConfig.locales}
119-
Note: Docusaurus only support running one locale at a time.`;
120-
}
121-
122-
const locales = i18nConfig.locales.includes(currentLocale)
123-
? i18nConfig.locales
124-
: (i18nConfig.locales.concat(currentLocale) as [string, ...string[]]);
143+
const locales = loadI18nLocaleList({
144+
i18nConfig,
145+
currentLocale,
146+
});
125147

126148
async function getFullLocaleConfig(
127149
locale: string,
@@ -131,7 +153,7 @@ Note: Docusaurus only support running one locale at a time.`;
131153
I18nLocaleConfig,
132154
'translate' | 'url' | 'baseUrl'
133155
> = {
134-
...getDefaultLocaleConfig(locale),
156+
...getDefaultLocaleConfig(localeConfigInput.htmlLang ?? locale),
135157
...localeConfigInput,
136158
};
137159

0 commit comments

Comments
 (0)