infer translate from presence of translation dir

This commit is contained in:
sebastien 2025-07-07 14:14:31 +02:00
parent 5cfe2c1dca
commit fe7dba95a8
7 changed files with 91 additions and 57 deletions

View file

@ -33,9 +33,8 @@ export type I18nLocaleConfig = {
*/ */
path: string; path: string;
/** /**
* Should we run the extra translation process for this locale? * Should we attempt to translate this locale?
* By default, we skip the translation process for the default locale, * By default, it will only be run if the `./i18n/<locale>` exists.
* while all the other locales run it.
*/ */
translate: boolean; translate: boolean;
}; };

View file

@ -91,7 +91,11 @@ async function getLocalesToBuild({
localizePath, localizePath,
}); });
const i18n = await loadI18n(context.siteConfig); const i18n = await loadI18n({
siteDir,
config: context.siteConfig,
currentLocale: context.siteConfig.i18n.defaultLocale // Awkward but ok
});
const locales = cliOptions.locale ?? i18n.locales; const locales = cliOptions.locale ?? i18n.locales;

View file

@ -0,0 +1 @@
Since i18n/de folder exists, de locale should infer to translate = true

View file

@ -0,0 +1 @@
Since i18n/fr folder exists, fr locale should infer to translate = true

View file

@ -6,17 +6,33 @@
*/ */
import {jest} from '@jest/globals'; import {jest} from '@jest/globals';
import path from 'path';
import {loadI18n, getDefaultLocaleConfig} from '../i18n'; import {loadI18n, getDefaultLocaleConfig} from '../i18n';
import {DEFAULT_I18N_CONFIG} from '../configValidation'; import {DEFAULT_I18N_CONFIG} from '../configValidation';
import type {DocusaurusConfig, I18nConfig} from '@docusaurus/types'; import type {DocusaurusConfig, I18nConfig} from '@docusaurus/types';
function loadI18nTest(i18nConfig: I18nConfig, locale?: string) { const loadI18nSiteDir = path.resolve(
return loadI18n( __dirname,
{ '__fixtures__',
'load-i18n-site',
);
function loadI18nTest({
siteDir = loadI18nSiteDir,
i18nConfig,
currentLocale,
}: {
siteDir?: string;
i18nConfig: I18nConfig;
currentLocale: string;
}) {
return loadI18n({
siteDir,
config: {
i18n: i18nConfig, i18n: i18nConfig,
} as DocusaurusConfig, } as DocusaurusConfig,
{locale}, currentLocale,
); });
} }
describe('defaultLocaleConfig', () => { describe('defaultLocaleConfig', () => {
@ -103,7 +119,12 @@ describe('loadI18n', () => {
}); });
it('loads I18n for default config', async () => { it('loads I18n for default config', async () => {
await expect(loadI18nTest(DEFAULT_I18N_CONFIG)).resolves.toEqual({ await expect(
loadI18nTest({
i18nConfig: DEFAULT_I18N_CONFIG,
currentLocale: 'en',
}),
).resolves.toEqual({
path: 'i18n', path: 'i18n',
defaultLocale: 'en', defaultLocale: 'en',
locales: ['en'], locales: ['en'],
@ -120,10 +141,13 @@ describe('loadI18n', () => {
it('loads I18n for multi-lang config', async () => { it('loads I18n for multi-lang config', async () => {
await expect( await expect(
loadI18nTest({ loadI18nTest({
i18nConfig: {
path: 'i18n', path: 'i18n',
defaultLocale: 'fr', defaultLocale: 'fr',
locales: ['en', 'fr', 'de'], locales: ['en', 'fr', 'de'],
localeConfigs: {}, localeConfigs: {},
},
currentLocale: 'fr',
}), }),
).resolves.toEqual({ ).resolves.toEqual({
defaultLocale: 'fr', defaultLocale: 'fr',
@ -133,11 +157,11 @@ describe('loadI18n', () => {
localeConfigs: { localeConfigs: {
en: { en: {
...getDefaultLocaleConfig('en'), ...getDefaultLocaleConfig('en'),
translate: true, translate: false,
}, },
fr: { fr: {
...getDefaultLocaleConfig('fr'), ...getDefaultLocaleConfig('fr'),
translate: false, translate: true,
}, },
de: { de: {
...getDefaultLocaleConfig('de'), ...getDefaultLocaleConfig('de'),
@ -149,15 +173,15 @@ describe('loadI18n', () => {
it('loads I18n for multi-locale config with specified locale', async () => { it('loads I18n for multi-locale config with specified locale', async () => {
await expect( await expect(
loadI18nTest( loadI18nTest({
{ i18nConfig: {
path: 'i18n', path: 'i18n',
defaultLocale: 'fr', defaultLocale: 'fr',
locales: ['en', 'fr', 'de'], locales: ['en', 'fr', 'de'],
localeConfigs: {}, localeConfigs: {},
}, },
'de', currentLocale: 'de',
), }),
).resolves.toEqual({ ).resolves.toEqual({
defaultLocale: 'fr', defaultLocale: 'fr',
path: 'i18n', path: 'i18n',
@ -166,11 +190,11 @@ describe('loadI18n', () => {
localeConfigs: { localeConfigs: {
en: { en: {
...getDefaultLocaleConfig('en'), ...getDefaultLocaleConfig('en'),
translate: true, translate: false,
}, },
fr: { fr: {
...getDefaultLocaleConfig('fr'), ...getDefaultLocaleConfig('fr'),
translate: false, translate: true,
}, },
de: { de: {
...getDefaultLocaleConfig('de'), ...getDefaultLocaleConfig('de'),
@ -182,19 +206,19 @@ describe('loadI18n', () => {
it('loads I18n for multi-locale config with some custom locale configs', async () => { it('loads I18n for multi-locale config with some custom locale configs', async () => {
await expect( await expect(
loadI18nTest( loadI18nTest({
{ i18nConfig: {
path: 'i18n', path: 'i18n',
defaultLocale: 'fr', defaultLocale: 'fr',
locales: ['en', 'fr', 'de'], locales: ['en', 'fr', 'de'],
localeConfigs: { localeConfigs: {
fr: {label: 'Français', translate: false}, fr: {label: 'Français', translate: false},
en: {}, en: {translate: true},
de: {translate: false}, de: {translate: false},
}, },
}, },
'de', currentLocale: 'de',
), }),
).resolves.toEqual({ ).resolves.toEqual({
defaultLocale: 'fr', defaultLocale: 'fr',
path: 'i18n', path: 'i18n',
@ -222,15 +246,15 @@ describe('loadI18n', () => {
}); });
it('warns when trying to load undeclared locale', async () => { it('warns when trying to load undeclared locale', async () => {
await loadI18nTest( await loadI18nTest({
{ i18nConfig: {
path: 'i18n', path: 'i18n',
defaultLocale: 'fr', defaultLocale: 'fr',
locales: ['en', 'fr', 'de'], locales: ['en', 'fr', 'de'],
localeConfigs: {}, localeConfigs: {},
}, },
'it', currentLocale: 'it',
); });
expect(consoleSpy.mock.calls[0]![0]).toMatch( expect(consoleSpy.mock.calls[0]![0]).toMatch(
/The locale .*it.* was not found in your site configuration/, /The locale .*it.* was not found in your site configuration/,
); );

View file

@ -5,10 +5,11 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import path from 'path';
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 type {I18n, DocusaurusConfig, I18nLocaleConfig} from '@docusaurus/types'; import type {I18n, DocusaurusConfig, I18nLocaleConfig} from '@docusaurus/types';
import type {LoadContextParams} from './site';
function inferLanguageDisplayName(locale: string) { function inferLanguageDisplayName(locale: string) {
const tryLocale = (l: string) => { const tryLocale = (l: string) => {
@ -98,27 +99,17 @@ export function getDefaultLocaleConfig(
} }
} }
async function shouldTranslateLocale({ export async function loadI18n({
locale, siteDir,
defaultLocale, config,
localeConfigInput, currentLocale,
}: { }: {
locale: string; siteDir: string;
defaultLocale: string; config: DocusaurusConfig;
localeConfigInput: Partial<I18nLocaleConfig>; currentLocale: string;
}): Promise<boolean> { }): Promise<I18n> {
// Maybe in the future we want to check if `./i18n/en` exists?
return localeConfigInput.translate ?? locale !== defaultLocale;
}
export async function loadI18n(
config: DocusaurusConfig,
options?: Pick<LoadContextParams, 'locale'>,
): Promise<I18n> {
const {i18n: i18nConfig} = config; const {i18n: i18nConfig} = config;
const currentLocale = options?.locale ?? i18nConfig.defaultLocale;
if (!i18nConfig.locales.includes(currentLocale)) { if (!i18nConfig.locales.includes(currentLocale)) {
logger.warn`The locale name=${currentLocale} was not found in your site configuration: Available locales are: ${i18nConfig.locales} 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.`; Note: Docusaurus only support running one locale at a time.`;
@ -132,14 +123,24 @@ Note: Docusaurus only support running one locale at a time.`;
locale: string, locale: string,
): Promise<I18nLocaleConfig> { ): Promise<I18nLocaleConfig> {
const localeConfigInput = i18nConfig.localeConfigs[locale] ?? {}; const localeConfigInput = i18nConfig.localeConfigs[locale] ?? {};
const translate = await shouldTranslateLocale({ const localeConfig: Omit<I18nLocaleConfig, 'translate'> = {
locale,
defaultLocale: i18nConfig.defaultLocale,
localeConfigInput,
});
return {
...getDefaultLocaleConfig(locale), ...getDefaultLocaleConfig(locale),
...localeConfigInput, ...localeConfigInput,
};
// By default, translations will be enabled if i18n/<locale> dir exists
async function inferTranslate() {
const localizationDir = path.resolve(
siteDir,
i18nConfig.path,
localeConfig.path,
);
return fs.pathExists(localizationDir);
}
const translate = localeConfigInput.translate ?? (await inferTranslate());
return {
...localeConfig,
translate, translate,
}; };
} }

View file

@ -97,7 +97,11 @@ export async function loadContext(
siteConfig: initialSiteConfig, siteConfig: initialSiteConfig,
}); });
const i18n = await loadI18n(initialSiteConfig, {locale}); const i18n = await loadI18n({
siteDir,
config: initialSiteConfig,
currentLocale: locale ?? initialSiteConfig.i18n.defaultLocale,
});
const baseUrl = localizePath({ const baseUrl = localizePath({
path: initialSiteConfig.baseUrl, path: initialSiteConfig.baseUrl,