From fe7dba95a889256329070f67d01c7466525650d1 Mon Sep 17 00:00:00 2001 From: sebastien Date: Mon, 7 Jul 2025 14:14:31 +0200 Subject: [PATCH] infer translate from presence of translation dir --- packages/docusaurus-types/src/i18n.d.ts | 5 +- .../docusaurus/src/commands/build/build.ts | 6 +- .../load-i18n-site/i18n/de/README.md | 1 + .../load-i18n-site/i18n/fr/README.md | 1 + .../src/server/__tests__/i18n.test.ts | 78 ++++++++++++------- packages/docusaurus/src/server/i18n.ts | 51 ++++++------ packages/docusaurus/src/server/site.ts | 6 +- 7 files changed, 91 insertions(+), 57 deletions(-) create mode 100644 packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/de/README.md create mode 100644 packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/fr/README.md diff --git a/packages/docusaurus-types/src/i18n.d.ts b/packages/docusaurus-types/src/i18n.d.ts index 5d9c242ca3..ea834788e5 100644 --- a/packages/docusaurus-types/src/i18n.d.ts +++ b/packages/docusaurus-types/src/i18n.d.ts @@ -33,9 +33,8 @@ export type I18nLocaleConfig = { */ path: string; /** - * Should we run the extra translation process for this locale? - * By default, we skip the translation process for the default locale, - * while all the other locales run it. + * Should we attempt to translate this locale? + * By default, it will only be run if the `./i18n/` exists. */ translate: boolean; }; diff --git a/packages/docusaurus/src/commands/build/build.ts b/packages/docusaurus/src/commands/build/build.ts index 7efaa0ca1d..4bfea69201 100644 --- a/packages/docusaurus/src/commands/build/build.ts +++ b/packages/docusaurus/src/commands/build/build.ts @@ -91,7 +91,11 @@ async function getLocalesToBuild({ 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; diff --git a/packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/de/README.md b/packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/de/README.md new file mode 100644 index 0000000000..0d58ac5abf --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/de/README.md @@ -0,0 +1 @@ +Since i18n/de folder exists, de locale should infer to translate = true diff --git a/packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/fr/README.md b/packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/fr/README.md new file mode 100644 index 0000000000..8b313dec94 --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__fixtures__/load-i18n-site/i18n/fr/README.md @@ -0,0 +1 @@ +Since i18n/fr folder exists, fr locale should infer to translate = true diff --git a/packages/docusaurus/src/server/__tests__/i18n.test.ts b/packages/docusaurus/src/server/__tests__/i18n.test.ts index 7950ea5b04..fb075d8b2b 100644 --- a/packages/docusaurus/src/server/__tests__/i18n.test.ts +++ b/packages/docusaurus/src/server/__tests__/i18n.test.ts @@ -6,17 +6,33 @@ */ import {jest} from '@jest/globals'; +import path from 'path'; import {loadI18n, getDefaultLocaleConfig} from '../i18n'; import {DEFAULT_I18N_CONFIG} from '../configValidation'; import type {DocusaurusConfig, I18nConfig} from '@docusaurus/types'; -function loadI18nTest(i18nConfig: I18nConfig, locale?: string) { - return loadI18n( - { +const loadI18nSiteDir = path.resolve( + __dirname, + '__fixtures__', + 'load-i18n-site', +); + +function loadI18nTest({ + siteDir = loadI18nSiteDir, + i18nConfig, + currentLocale, +}: { + siteDir?: string; + i18nConfig: I18nConfig; + currentLocale: string; +}) { + return loadI18n({ + siteDir, + config: { i18n: i18nConfig, } as DocusaurusConfig, - {locale}, - ); + currentLocale, + }); } describe('defaultLocaleConfig', () => { @@ -103,7 +119,12 @@ describe('loadI18n', () => { }); 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', defaultLocale: 'en', locales: ['en'], @@ -120,10 +141,13 @@ describe('loadI18n', () => { it('loads I18n for multi-lang config', async () => { await expect( loadI18nTest({ - path: 'i18n', - defaultLocale: 'fr', - locales: ['en', 'fr', 'de'], - localeConfigs: {}, + i18nConfig: { + path: 'i18n', + defaultLocale: 'fr', + locales: ['en', 'fr', 'de'], + localeConfigs: {}, + }, + currentLocale: 'fr', }), ).resolves.toEqual({ defaultLocale: 'fr', @@ -133,11 +157,11 @@ describe('loadI18n', () => { localeConfigs: { en: { ...getDefaultLocaleConfig('en'), - translate: true, + translate: false, }, fr: { ...getDefaultLocaleConfig('fr'), - translate: false, + translate: true, }, de: { ...getDefaultLocaleConfig('de'), @@ -149,15 +173,15 @@ describe('loadI18n', () => { it('loads I18n for multi-locale config with specified locale', async () => { await expect( - loadI18nTest( - { + loadI18nTest({ + i18nConfig: { path: 'i18n', defaultLocale: 'fr', locales: ['en', 'fr', 'de'], localeConfigs: {}, }, - 'de', - ), + currentLocale: 'de', + }), ).resolves.toEqual({ defaultLocale: 'fr', path: 'i18n', @@ -166,11 +190,11 @@ describe('loadI18n', () => { localeConfigs: { en: { ...getDefaultLocaleConfig('en'), - translate: true, + translate: false, }, fr: { ...getDefaultLocaleConfig('fr'), - translate: false, + translate: true, }, de: { ...getDefaultLocaleConfig('de'), @@ -182,19 +206,19 @@ describe('loadI18n', () => { it('loads I18n for multi-locale config with some custom locale configs', async () => { await expect( - loadI18nTest( - { + loadI18nTest({ + i18nConfig: { path: 'i18n', defaultLocale: 'fr', locales: ['en', 'fr', 'de'], localeConfigs: { fr: {label: 'Français', translate: false}, - en: {}, + en: {translate: true}, de: {translate: false}, }, }, - 'de', - ), + currentLocale: 'de', + }), ).resolves.toEqual({ defaultLocale: 'fr', path: 'i18n', @@ -222,15 +246,15 @@ describe('loadI18n', () => { }); it('warns when trying to load undeclared locale', async () => { - await loadI18nTest( - { + await loadI18nTest({ + i18nConfig: { path: 'i18n', defaultLocale: 'fr', locales: ['en', 'fr', 'de'], localeConfigs: {}, }, - 'it', - ); + currentLocale: 'it', + }); expect(consoleSpy.mock.calls[0]![0]).toMatch( /The locale .*it.* was not found in your site configuration/, ); diff --git a/packages/docusaurus/src/server/i18n.ts b/packages/docusaurus/src/server/i18n.ts index d58d6ab32f..9d97140b3a 100644 --- a/packages/docusaurus/src/server/i18n.ts +++ b/packages/docusaurus/src/server/i18n.ts @@ -5,10 +5,11 @@ * 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 combinePromises from 'combine-promises'; import type {I18n, DocusaurusConfig, I18nLocaleConfig} from '@docusaurus/types'; -import type {LoadContextParams} from './site'; function inferLanguageDisplayName(locale: string) { const tryLocale = (l: string) => { @@ -98,27 +99,17 @@ export function getDefaultLocaleConfig( } } -async function shouldTranslateLocale({ - locale, - defaultLocale, - localeConfigInput, +export async function loadI18n({ + siteDir, + config, + currentLocale, }: { - locale: string; - defaultLocale: string; - localeConfigInput: Partial; -}): Promise { - // 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, -): Promise { + siteDir: string; + config: DocusaurusConfig; + currentLocale: string; +}): Promise { const {i18n: i18nConfig} = config; - const currentLocale = options?.locale ?? i18nConfig.defaultLocale; - 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.`; @@ -132,14 +123,24 @@ Note: Docusaurus only support running one locale at a time.`; locale: string, ): Promise { const localeConfigInput = i18nConfig.localeConfigs[locale] ?? {}; - const translate = await shouldTranslateLocale({ - locale, - defaultLocale: i18nConfig.defaultLocale, - localeConfigInput, - }); - return { + const localeConfig: Omit = { ...getDefaultLocaleConfig(locale), ...localeConfigInput, + }; + + // By default, translations will be enabled if i18n/ 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, }; } diff --git a/packages/docusaurus/src/server/site.ts b/packages/docusaurus/src/server/site.ts index a38c9fda9c..a48e68dd8a 100644 --- a/packages/docusaurus/src/server/site.ts +++ b/packages/docusaurus/src/server/site.ts @@ -97,7 +97,11 @@ export async function loadContext( siteConfig: initialSiteConfig, }); - const i18n = await loadI18n(initialSiteConfig, {locale}); + const i18n = await loadI18n({ + siteDir, + config: initialSiteConfig, + currentLocale: locale ?? initialSiteConfig.i18n.defaultLocale, + }); const baseUrl = localizePath({ path: initialSiteConfig.baseUrl,