From cc08a46b668d940c89cb91a2790a43ded1d4743a Mon Sep 17 00:00:00 2001 From: sebastien Date: Thu, 10 Jul 2025 14:13:26 +0200 Subject: [PATCH] Add unit tests for i18n localeConfigs baseUrl --- packages/docusaurus-types/src/i18n.d.ts | 15 ++++++ .../src/server/__tests__/i18n.test.ts | 54 ++++++++++++++++++- packages/docusaurus/src/server/i18n.ts | 26 ++++++++- 3 files changed, 91 insertions(+), 4 deletions(-) diff --git a/packages/docusaurus-types/src/i18n.d.ts b/packages/docusaurus-types/src/i18n.d.ts index ea834788e5..82364fe285 100644 --- a/packages/docusaurus-types/src/i18n.d.ts +++ b/packages/docusaurus-types/src/i18n.d.ts @@ -37,6 +37,21 @@ export type I18nLocaleConfig = { * By default, it will only be run if the `./i18n/` exists. */ translate: boolean; + + /** + * The baseUrl to use for this locale. + * This overrides the `siteConfig.baseUrl` attribute. + * + * Default values: + * - Default locale: `/${siteConfig.baseUrl}/` + * - Other locales: `/${siteConfig.baseUrl}//` + * + * Exception: when using the CLI with a single `--locale` parameter, the + * `//` path segment is not included. This is a better default for + * sites looking to deploy each locale to a different subdomain, such as + * `https://.docusaurus.io` + */ + baseUrl: string; }; export type I18nConfig = { diff --git a/packages/docusaurus/src/server/__tests__/i18n.test.ts b/packages/docusaurus/src/server/__tests__/i18n.test.ts index fb075d8b2b..03e7dfebc7 100644 --- a/packages/docusaurus/src/server/__tests__/i18n.test.ts +++ b/packages/docusaurus/src/server/__tests__/i18n.test.ts @@ -19,10 +19,12 @@ const loadI18nSiteDir = path.resolve( function loadI18nTest({ siteDir = loadI18nSiteDir, + baseUrl = '/', i18nConfig, currentLocale, }: { siteDir?: string; + baseUrl?: string; i18nConfig: I18nConfig; currentLocale: string; }) { @@ -30,6 +32,7 @@ function loadI18nTest({ siteDir, config: { i18n: i18nConfig, + baseUrl, } as DocusaurusConfig, currentLocale, }); @@ -133,6 +136,7 @@ describe('loadI18n', () => { en: { ...getDefaultLocaleConfig('en'), translate: false, + baseUrl: '/', }, }, }); @@ -158,14 +162,17 @@ describe('loadI18n', () => { en: { ...getDefaultLocaleConfig('en'), translate: false, + baseUrl: '/en/', }, fr: { ...getDefaultLocaleConfig('fr'), translate: true, + baseUrl: '/', }, de: { ...getDefaultLocaleConfig('de'), translate: true, + baseUrl: '/de/', }, }, }); @@ -191,14 +198,17 @@ describe('loadI18n', () => { en: { ...getDefaultLocaleConfig('en'), translate: false, + baseUrl: '/en/', }, fr: { ...getDefaultLocaleConfig('fr'), translate: true, + baseUrl: '/', }, de: { ...getDefaultLocaleConfig('de'), translate: true, + baseUrl: '/de/', }, }, }); @@ -213,10 +223,11 @@ describe('loadI18n', () => { locales: ['en', 'fr', 'de'], localeConfigs: { fr: {label: 'Français', translate: false}, - en: {translate: true}, - de: {translate: false}, + en: {translate: true, baseUrl: 'en-EN/whatever/else'}, + de: {translate: false, baseUrl: '/de-DE/'}, }, }, + currentLocale: 'de', }), ).resolves.toEqual({ @@ -232,19 +243,58 @@ describe('loadI18n', () => { calendar: 'gregory', path: 'fr', translate: false, + baseUrl: '/', }, en: { ...getDefaultLocaleConfig('en'), translate: true, + baseUrl: '/en-EN/whatever/else/', }, de: { ...getDefaultLocaleConfig('de'), translate: false, + baseUrl: '/de-DE/', }, }, }); }); + it('loads I18n for multi-locale config with baseUrl edge cases', async () => { + await expect( + loadI18nTest({ + baseUrl: 'siteBaseUrl', + i18nConfig: { + path: 'i18n', + defaultLocale: 'fr', + locales: ['en', 'fr', 'de', 'pt'], + localeConfigs: { + fr: {}, + en: {baseUrl: ''}, + de: {baseUrl: '/de-DE/'}, + }, + }, + currentLocale: 'de', + }), + ).resolves.toEqual( + expect.objectContaining({ + localeConfigs: { + fr: expect.objectContaining({ + baseUrl: '/siteBaseUrl/', + }), + en: expect.objectContaining({ + baseUrl: '/', + }), + de: expect.objectContaining({ + baseUrl: '/de-DE/', + }), + pt: expect.objectContaining({ + baseUrl: '/siteBaseUrl/pt/', + }), + }, + }), + ); + }); + it('warns when trying to load undeclared locale', async () => { await loadI18nTest({ i18nConfig: { diff --git a/packages/docusaurus/src/server/i18n.ts b/packages/docusaurus/src/server/i18n.ts index 9d97140b3a..e3e78d663f 100644 --- a/packages/docusaurus/src/server/i18n.ts +++ b/packages/docusaurus/src/server/i18n.ts @@ -9,6 +9,7 @@ import path from 'path'; 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'; function inferLanguageDisplayName(locale: string) { @@ -82,7 +83,7 @@ function getDefaultDirection(localeStr: string) { export function getDefaultLocaleConfig( locale: string, -): Omit { +): Omit { try { return { label: getDefaultLocaleLabel(locale), @@ -123,7 +124,7 @@ Note: Docusaurus only support running one locale at a time.`; locale: string, ): Promise { const localeConfigInput = i18nConfig.localeConfigs[locale] ?? {}; - const localeConfig: Omit = { + const localeConfig: Omit = { ...getDefaultLocaleConfig(locale), ...localeConfigInput, }; @@ -138,10 +139,31 @@ Note: Docusaurus only support running one locale at a time.`; return fs.pathExists(localizationDir); } + function getBaseUrl(): string { + if (typeof localeConfigInput.baseUrl !== 'undefined') { + return normalizeUrl(['/', localeConfigInput.baseUrl, '/']); + } + + // TODO CLI locales.length === 1 case - retro compat + const hasLocaleSegment = locale !== i18nConfig.defaultLocale; + + return normalizeUrl([ + '/', + config.baseUrl, + hasLocaleSegment ? locale : '', + '/', + ]); + } + const translate = localeConfigInput.translate ?? (await inferTranslate()); + const baseUrl = + typeof localeConfigInput.baseUrl !== 'undefined' + ? normalizeUrl(['/', localeConfigInput.baseUrl, '/']) + : getBaseUrl(); return { ...localeConfig, translate, + baseUrl, }; }