mirror of
https://github.com/facebook/docusaurus.git
synced 2025-12-25 17:22:50 +00:00
fix(core): optimize i18n integration for site builds + improve inference of locale config (#11550)
This commit is contained in:
parent
6a38ccdfb0
commit
9c85f8689a
|
|
@ -38,7 +38,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: ['20.0', '20', '22', '24', '25']
|
node: ['20.0', '20', '22', '24', '25.1']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ jobs:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: ['20.0', '20', '22', '24', '25']
|
node: ['20.0', '20', '22', '24', '25.1']
|
||||||
steps:
|
steps:
|
||||||
- name: Support longpaths
|
- name: Support longpaths
|
||||||
run: git config --system core.longpaths true
|
run: git config --system core.longpaths true
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: ['20.0', '20', '22', '24', '25']
|
node: ['20.0', '20', '22', '24', '25.1']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
dist
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
.yarn
|
.yarn
|
||||||
build
|
**/build/**
|
||||||
coverage
|
coverage
|
||||||
.docusaurus
|
.docusaurus
|
||||||
.idea
|
.idea
|
||||||
|
|
@ -11,6 +11,8 @@ coverage
|
||||||
|
|
||||||
jest/vendor
|
jest/vendor
|
||||||
|
|
||||||
|
argos/test-results
|
||||||
|
|
||||||
packages/lqip-loader/lib/
|
packages/lqip-loader/lib/
|
||||||
packages/docusaurus/lib/
|
packages/docusaurus/lib/
|
||||||
packages/docusaurus-*/lib/*
|
packages/docusaurus-*/lib/*
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import logger, {PerfLogger} from '@docusaurus/logger';
|
import logger, {PerfLogger} from '@docusaurus/logger';
|
||||||
import {mapAsyncSequential} from '@docusaurus/utils';
|
import {mapAsyncSequential} from '@docusaurus/utils';
|
||||||
import {loadContext, type LoadContextParams} from '../../server/site';
|
import {type LoadContextParams} from '../../server/site';
|
||||||
import {loadI18n} from '../../server/i18n';
|
import {loadI18nLocaleList} from '../../server/i18n';
|
||||||
import {buildLocale, type BuildLocaleParams} from './buildLocale';
|
import {buildLocale, type BuildLocaleParams} from './buildLocale';
|
||||||
import {isAutomaticBaseUrlLocalizationDisabled} from './buildUtils';
|
import {loadSiteConfig} from '../../server/config';
|
||||||
|
|
||||||
export type BuildCLIOptions = Pick<LoadContextParams, 'config' | 'outDir'> & {
|
export type BuildCLIOptions = Pick<LoadContextParams, 'config' | 'outDir'> & {
|
||||||
locale?: [string, ...string[]];
|
locale?: [string, ...string[]];
|
||||||
|
|
@ -81,27 +81,21 @@ async function getLocalesToBuild({
|
||||||
siteDir: string;
|
siteDir: string;
|
||||||
cliOptions: BuildCLIOptions;
|
cliOptions: BuildCLIOptions;
|
||||||
}): Promise<[string, ...string[]]> {
|
}): Promise<[string, ...string[]]> {
|
||||||
// TODO we shouldn't need to load all context + i18n just to get that list
|
const {siteConfig} = await loadSiteConfig({
|
||||||
// only loading siteConfig should be enough
|
|
||||||
const context = await loadContext({
|
|
||||||
siteDir,
|
siteDir,
|
||||||
outDir: cliOptions.outDir,
|
customConfigFilePath: cliOptions.config,
|
||||||
config: cliOptions.config,
|
|
||||||
automaticBaseUrlLocalizationDisabled: isAutomaticBaseUrlLocalizationDisabled(cliOptions),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const i18n = await loadI18n({
|
const locales =
|
||||||
siteDir,
|
cliOptions.locale ??
|
||||||
config: context.siteConfig,
|
loadI18nLocaleList({
|
||||||
currentLocale: context.siteConfig.i18n.defaultLocale, // Awkward but ok
|
i18nConfig: siteConfig.i18n,
|
||||||
automaticBaseUrlLocalizationDisabled: false,
|
currentLocale: siteConfig.i18n.defaultLocale, // Awkward but ok
|
||||||
});
|
});
|
||||||
|
|
||||||
const locales = cliOptions.locale ?? i18n.locales;
|
|
||||||
|
|
||||||
return orderLocales({
|
return orderLocales({
|
||||||
locales: locales as [string, ...string[]],
|
locales: locales as [string, ...string[]],
|
||||||
defaultLocale: i18n.defaultLocale,
|
defaultLocale: siteConfig.i18n.defaultLocale,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -123,9 +123,11 @@ describe('defaultLocaleConfig', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loadI18n', () => {
|
describe('loadI18n', () => {
|
||||||
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
const consoleWarnSpy = jest
|
||||||
|
.spyOn(console, 'warn')
|
||||||
|
.mockImplementation(() => {});
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
consoleSpy.mockClear();
|
consoleWarnSpy.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads I18n for default config', async () => {
|
it('loads I18n for default config', async () => {
|
||||||
|
|
@ -397,8 +399,67 @@ describe('loadI18n', () => {
|
||||||
},
|
},
|
||||||
currentLocale: 'it',
|
currentLocale: 'it',
|
||||||
});
|
});
|
||||||
expect(consoleSpy.mock.calls[0]![0]).toMatch(
|
expect(consoleWarnSpy.mock.calls[0]![0]).toMatch(
|
||||||
/The locale .*it.* was not found in your site configuration/,
|
/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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,12 @@ import fs from 'fs-extra';
|
||||||
import logger from '@docusaurus/logger';
|
import logger from '@docusaurus/logger';
|
||||||
import combinePromises from 'combine-promises';
|
import combinePromises from 'combine-promises';
|
||||||
import {normalizeUrl} from '@docusaurus/utils';
|
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) {
|
function inferLanguageDisplayName(locale: string) {
|
||||||
const tryLocale = (l: string) => {
|
const tryLocale = (l: string) => {
|
||||||
|
|
@ -95,12 +100,33 @@ export function getDefaultLocaleConfig(
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
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},
|
{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({
|
export async function loadI18n({
|
||||||
siteDir,
|
siteDir,
|
||||||
config,
|
config,
|
||||||
|
|
@ -114,14 +140,10 @@ export async function loadI18n({
|
||||||
}): Promise<I18n> {
|
}): Promise<I18n> {
|
||||||
const {i18n: i18nConfig} = config;
|
const {i18n: i18nConfig} = config;
|
||||||
|
|
||||||
if (!i18nConfig.locales.includes(currentLocale)) {
|
const locales = loadI18nLocaleList({
|
||||||
logger.warn`The locale name=${currentLocale} was not found in your site configuration: Available locales are: ${i18nConfig.locales}
|
i18nConfig,
|
||||||
Note: Docusaurus only support running one locale at a time.`;
|
currentLocale,
|
||||||
}
|
});
|
||||||
|
|
||||||
const locales = i18nConfig.locales.includes(currentLocale)
|
|
||||||
? i18nConfig.locales
|
|
||||||
: (i18nConfig.locales.concat(currentLocale) as [string, ...string[]]);
|
|
||||||
|
|
||||||
async function getFullLocaleConfig(
|
async function getFullLocaleConfig(
|
||||||
locale: string,
|
locale: string,
|
||||||
|
|
@ -131,7 +153,7 @@ Note: Docusaurus only support running one locale at a time.`;
|
||||||
I18nLocaleConfig,
|
I18nLocaleConfig,
|
||||||
'translate' | 'url' | 'baseUrl'
|
'translate' | 'url' | 'baseUrl'
|
||||||
> = {
|
> = {
|
||||||
...getDefaultLocaleConfig(locale),
|
...getDefaultLocaleConfig(localeConfigInput.htmlLang ?? locale),
|
||||||
...localeConfigInput,
|
...localeConfigInput,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue