fix(core): optimize i18n integration for site builds + improve inference of locale config (#11550)

This commit is contained in:
Sébastien Lorber 2025-11-14 13:13:05 +01:00 committed by GitHub
parent 6a38ccdfb0
commit 9c85f8689a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 116 additions and 37 deletions

View File

@ -38,7 +38,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node: ['20.0', '20', '22', '24', '25']
node: ['20.0', '20', '22', '24', '25.1']
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

View File

@ -27,7 +27,7 @@ jobs:
runs-on: windows-latest
strategy:
matrix:
node: ['20.0', '20', '22', '24', '25']
node: ['20.0', '20', '22', '24', '25.1']
steps:
- name: Support longpaths
run: git config --system core.longpaths true

View File

@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node: ['20.0', '20', '22', '24', '25']
node: ['20.0', '20', '22', '24', '25.1']
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

View File

@ -1,7 +1,7 @@
dist
node_modules
.yarn
build
**/build/**
coverage
.docusaurus
.idea
@ -11,6 +11,8 @@ coverage
jest/vendor
argos/test-results
packages/lqip-loader/lib/
packages/docusaurus/lib/
packages/docusaurus-*/lib/*

View File

@ -8,10 +8,10 @@
import fs from 'fs-extra';
import logger, {PerfLogger} from '@docusaurus/logger';
import {mapAsyncSequential} from '@docusaurus/utils';
import {loadContext, type LoadContextParams} from '../../server/site';
import {loadI18n} from '../../server/i18n';
import {type LoadContextParams} from '../../server/site';
import {loadI18nLocaleList} from '../../server/i18n';
import {buildLocale, type BuildLocaleParams} from './buildLocale';
import {isAutomaticBaseUrlLocalizationDisabled} from './buildUtils';
import {loadSiteConfig} from '../../server/config';
export type BuildCLIOptions = Pick<LoadContextParams, 'config' | 'outDir'> & {
locale?: [string, ...string[]];
@ -81,27 +81,21 @@ async function getLocalesToBuild({
siteDir: string;
cliOptions: BuildCLIOptions;
}): Promise<[string, ...string[]]> {
// TODO we shouldn't need to load all context + i18n just to get that list
// only loading siteConfig should be enough
const context = await loadContext({
const {siteConfig} = await loadSiteConfig({
siteDir,
outDir: cliOptions.outDir,
config: cliOptions.config,
automaticBaseUrlLocalizationDisabled: isAutomaticBaseUrlLocalizationDisabled(cliOptions),
customConfigFilePath: cliOptions.config,
});
const i18n = await loadI18n({
siteDir,
config: context.siteConfig,
currentLocale: context.siteConfig.i18n.defaultLocale, // Awkward but ok
automaticBaseUrlLocalizationDisabled: false,
});
const locales = cliOptions.locale ?? i18n.locales;
const locales =
cliOptions.locale ??
loadI18nLocaleList({
i18nConfig: siteConfig.i18n,
currentLocale: siteConfig.i18n.defaultLocale, // Awkward but ok
});
return orderLocales({
locales: locales as [string, ...string[]],
defaultLocale: i18n.defaultLocale,
defaultLocale: siteConfig.i18n.defaultLocale,
});
}

View File

@ -123,9 +123,11 @@ describe('defaultLocaleConfig', () => {
});
describe('loadI18n', () => {
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const consoleWarnSpy = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});
beforeEach(() => {
consoleSpy.mockClear();
consoleWarnSpy.mockClear();
});
it('loads I18n for default config', async () => {
@ -397,8 +399,67 @@ describe('loadI18n', () => {
},
currentLocale: 'it',
});
expect(consoleSpy.mock.calls[0]![0]).toMatch(
/The locale .*it.* was not found in your site configuration/,
expect(consoleWarnSpy.mock.calls[0]![0]).toMatch(
/The locale .*it.* was not found in your Docusaurus site configuration/,
);
});
it('throws when trying to load undeclared locale that is not a valid locale BCP47 name', async () => {
await expect(() =>
loadI18nTest({
i18nConfig: {
path: 'i18n',
defaultLocale: 'en',
locales: ['en', 'fr', 'de'],
localeConfigs: {},
},
currentLocale: 'x1',
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
"Docusaurus couldn't infer a default locale config for x1.
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."
`);
});
it('throws when trying to load declared locale that is not a valid locale BCP47 name', async () => {
await expect(() =>
loadI18nTest({
i18nConfig: {
path: 'i18n',
defaultLocale: 'fr',
locales: ['en', 'fr', 'de'],
localeConfigs: {x1: {}},
},
currentLocale: 'x1',
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
"Docusaurus couldn't infer a default locale config for x1.
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."
`);
});
it('loads i18n when trying to load declared locale with invalid BCP47 name but valid BCP47', async () => {
const result = await loadI18nTest({
i18nConfig: {
path: 'i18n',
defaultLocale: 'en',
locales: ['en', 'fr', 'x1'],
localeConfigs: {
x1: {htmlLang: 'en-US'},
},
},
currentLocale: 'x1',
});
expect(result.localeConfigs.x1).toEqual({
baseUrl: '/x1/',
calendar: 'gregory',
direction: 'ltr',
htmlLang: 'en-US',
label: 'American English',
path: 'en-US',
translate: false,
url: 'https://example.com',
});
expect(consoleWarnSpy).toHaveBeenCalledTimes(0);
});
});

View File

@ -10,7 +10,12 @@ import fs from 'fs-extra';
import logger from '@docusaurus/logger';
import combinePromises from 'combine-promises';
import {normalizeUrl} from '@docusaurus/utils';
import type {I18n, DocusaurusConfig, I18nLocaleConfig} from '@docusaurus/types';
import type {
I18n,
DocusaurusConfig,
I18nLocaleConfig,
I18nConfig,
} from '@docusaurus/types';
function inferLanguageDisplayName(locale: string) {
const tryLocale = (l: string) => {
@ -95,12 +100,33 @@ export function getDefaultLocaleConfig(
};
} catch (e) {
throw new Error(
`Docusaurus couldn't get default locale config for ${locale}`,
`Docusaurus couldn't infer a default locale config for ${logger.name(
locale,
)}.
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(
`siteConfig.i18n.localeConfig['${locale}'].htmlLang`,
)} attribute.`,
{cause: e},
);
}
}
export function loadI18nLocaleList({
i18nConfig,
currentLocale,
}: {
i18nConfig: I18nConfig;
currentLocale: string;
}): [string, ...string[]] {
if (!i18nConfig.locales.includes(currentLocale)) {
logger.warn`The locale name=${currentLocale} was not found in your Docusaurus site configuration.
We recommend adding the name=${currentLocale} to your site i18n config, but we will still try to run your site.
Declared site config locales are: ${i18nConfig.locales}`;
return i18nConfig.locales.concat(currentLocale) as [string, ...string[]];
}
return i18nConfig.locales;
}
export async function loadI18n({
siteDir,
config,
@ -114,14 +140,10 @@ export async function loadI18n({
}): Promise<I18n> {
const {i18n: i18nConfig} = config;
if (!i18nConfig.locales.includes(currentLocale)) {
logger.warn`The locale name=${currentLocale} was not found in your site configuration: Available locales are: ${i18nConfig.locales}
Note: Docusaurus only support running one locale at a time.`;
}
const locales = i18nConfig.locales.includes(currentLocale)
? i18nConfig.locales
: (i18nConfig.locales.concat(currentLocale) as [string, ...string[]]);
const locales = loadI18nLocaleList({
i18nConfig,
currentLocale,
});
async function getFullLocaleConfig(
locale: string,
@ -131,7 +153,7 @@ Note: Docusaurus only support running one locale at a time.`;
I18nLocaleConfig,
'translate' | 'url' | 'baseUrl'
> = {
...getDefaultLocaleConfig(locale),
...getDefaultLocaleConfig(localeConfigInput.htmlLang ?? locale),
...localeConfigInput,
};