diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
index d7747d16300c..367872215282 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
@@ -19,6 +19,7 @@ const DefaultI18N: I18n = {
currentLocale: 'en',
locales: ['en'],
defaultLocale: 'en',
+ path: '1i8n',
localeConfigs: {
en: {
label: 'English',
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts
index 47d4b98fd4e0..4ec710ce0a39 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts
@@ -47,6 +47,7 @@ function getI18n(locale: string): I18n {
currentLocale: locale,
locales: [locale],
defaultLocale: locale,
+ path: 'i18n',
localeConfigs: {
[locale]: {
calendar: 'gregory',
@@ -70,6 +71,7 @@ const getPlugin = async (
i18n: I18n = DefaultI18N,
) => {
const generatedFilesDir: string = path.resolve(siteDir, '.docusaurus');
+ const localizationDir = path.join(siteDir, i18n.path, i18n.currentLocale);
const siteConfig = {
title: 'Hello',
baseUrl: '/',
@@ -81,6 +83,7 @@ const getPlugin = async (
siteConfig,
generatedFilesDir,
i18n,
+ localizationDir,
} as LoadContext,
validateOptions({
validate: normalizePluginOptions as Validate<
diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts
index 2b0355a0f3f1..f0c1f8cc24ed 100644
--- a/packages/docusaurus-plugin-content-blog/src/index.ts
+++ b/packages/docusaurus-plugin-content-blog/src/index.ts
@@ -59,6 +59,7 @@ export default async function pluginContentBlog(
siteDir,
siteConfig,
generatedFilesDir,
+ localizationDir,
i18n: {currentLocale},
} = context;
const {onBrokenMarkdownLinks, baseUrl} = siteConfig;
@@ -66,8 +67,7 @@ export default async function pluginContentBlog(
const contentPaths: BlogContentPaths = {
contentPath: path.resolve(siteDir, options.path),
contentPathLocalized: getPluginI18nPath({
- siteDir,
- locale: currentLocale,
+ localizationDir,
pluginName: 'docusaurus-plugin-content-blog',
pluginId: options.id,
}),
diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts
index 194ce3c4e46e..d9ed52eea89d 100644
Binary files a/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts and b/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts differ
diff --git a/packages/docusaurus-plugin-content-docs/src/cli.ts b/packages/docusaurus-plugin-content-docs/src/cli.ts
index 65e4984affa0..1818a3f4e22e 100644
--- a/packages/docusaurus-plugin-content-docs/src/cli.ts
+++ b/packages/docusaurus-plugin-content-docs/src/cli.ts
@@ -85,13 +85,15 @@ export async function cliDocsVersionCommand(
await Promise.all(
i18n.locales.map(async (locale) => {
+ // TODO duplicated logic from core, so duplicate comment as well: we need
+ // to support customization per-locale in the future
+ const localizationDir = path.resolve(siteDir, i18n.path, locale);
// Copy docs files.
const docsDir =
locale === i18n.defaultLocale
? path.resolve(siteDir, docsPath)
: getDocsDirPathLocalized({
- siteDir,
- locale,
+ localizationDir,
pluginId,
versionName: CURRENT_VERSION_NAME,
});
@@ -114,8 +116,7 @@ export async function cliDocsVersionCommand(
locale === i18n.defaultLocale
? getVersionDocsDirPath(siteDir, pluginId, version)
: getDocsDirPathLocalized({
- siteDir,
- locale,
+ localizationDir,
pluginId,
versionName: version,
});
diff --git a/packages/docusaurus-plugin-content-docs/src/versions/__tests__/index.test.ts b/packages/docusaurus-plugin-content-docs/src/versions/__tests__/index.test.ts
index 15bb3d06d720..a52ba1b7d186 100644
--- a/packages/docusaurus-plugin-content-docs/src/versions/__tests__/index.test.ts
+++ b/packages/docusaurus-plugin-content-docs/src/versions/__tests__/index.test.ts
@@ -17,6 +17,7 @@ import type {
} from '@docusaurus/plugin-content-docs';
const DefaultI18N: I18n = {
+ path: 'i18n',
currentLocale: 'en',
locales: ['en'],
defaultLocale: 'en',
@@ -37,6 +38,7 @@ describe('readVersionsMetadata', () => {
siteDir: simpleSiteDir,
baseUrl: '/',
i18n: DefaultI18N,
+ localizationDir: path.join(simpleSiteDir, 'i18n/en'),
} as LoadContext;
const vCurrent: VersionMetadata = {
@@ -198,6 +200,7 @@ describe('readVersionsMetadata', () => {
siteDir: versionedSiteDir,
baseUrl: '/',
i18n: DefaultI18N,
+ localizationDir: path.join(versionedSiteDir, 'i18n/en'),
} as LoadContext;
const vCurrent: VersionMetadata = {
@@ -636,6 +639,7 @@ describe('readVersionsMetadata', () => {
siteDir: versionedSiteDir,
baseUrl: '/',
i18n: DefaultI18N,
+ localizationDir: path.join(versionedSiteDir, 'i18n/en'),
} as LoadContext;
const vCurrent: VersionMetadata = {
diff --git a/packages/docusaurus-plugin-content-docs/src/versions/files.ts b/packages/docusaurus-plugin-content-docs/src/versions/files.ts
index 04c97efaebaa..2f045717c29e 100644
--- a/packages/docusaurus-plugin-content-docs/src/versions/files.ts
+++ b/packages/docusaurus-plugin-content-docs/src/versions/files.ts
@@ -55,19 +55,16 @@ export function getVersionSidebarsPath(
}
export function getDocsDirPathLocalized({
- siteDir,
- locale,
+ localizationDir,
pluginId,
versionName,
}: {
- siteDir: string;
- locale: string;
+ localizationDir: string;
pluginId: string;
versionName: string;
}): string {
return getPluginI18nPath({
- siteDir,
- locale,
+ localizationDir,
pluginName: 'docusaurus-plugin-content-docs',
pluginId,
subPaths: [
@@ -175,8 +172,7 @@ export async function getVersionMetadataPaths({
> {
const isCurrent = versionName === CURRENT_VERSION_NAME;
const contentPathLocalized = getDocsDirPathLocalized({
- siteDir: context.siteDir,
- locale: context.i18n.currentLocale,
+ localizationDir: context.localizationDir,
pluginId: options.id,
versionName,
});
diff --git a/packages/docusaurus-plugin-content-docs/src/versions/index.ts b/packages/docusaurus-plugin-content-docs/src/versions/index.ts
index 369e0fd6efc2..d39bee56198d 100644
--- a/packages/docusaurus-plugin-content-docs/src/versions/index.ts
+++ b/packages/docusaurus-plugin-content-docs/src/versions/index.ts
@@ -49,8 +49,7 @@ function getVersionEditUrls({
const editDirPath = options.editCurrentVersion ? options.path : contentPath;
const editDirPathLocalized = options.editCurrentVersion
? getDocsDirPathLocalized({
- siteDir: context.siteDir,
- locale: context.i18n.currentLocale,
+ localizationDir: context.localizationDir,
versionName: CURRENT_VERSION_NAME,
pluginId: options.id,
})
diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap
index 818f94da35f5..bc0f5cef2947 100644
--- a/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap
+++ b/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap
@@ -55,19 +55,19 @@ exports[`docusaurus-plugin-content-pages loads simple pages 1`] = `
exports[`docusaurus-plugin-content-pages loads simple pages with french translations 1`] = `
[
{
- "permalink": "/",
+ "permalink": "/fr/",
"source": "@site/src/pages/index.js",
"type": "jsx",
},
{
- "permalink": "/typescript",
+ "permalink": "/fr/typescript",
"source": "@site/src/pages/typescript.tsx",
"type": "jsx",
},
{
"description": "Markdown index page",
"frontMatter": {},
- "permalink": "/hello/",
+ "permalink": "/fr/hello/",
"source": "@site/src/pages/hello/index.md",
"title": "Index",
"type": "mdx",
@@ -78,26 +78,26 @@ exports[`docusaurus-plugin-content-pages loads simple pages with french translat
"description": "my mdx page",
"title": "mdx page",
},
- "permalink": "/hello/mdxPage",
+ "permalink": "/fr/hello/mdxPage",
"source": "@site/src/pages/hello/mdxPage.mdx",
"title": "mdx page",
"type": "mdx",
},
{
- "permalink": "/hello/translatedJs",
+ "permalink": "/fr/hello/translatedJs",
"source": "@site/i18n/fr/docusaurus-plugin-content-pages/hello/translatedJs.js",
"type": "jsx",
},
{
"description": "translated markdown page (fr)",
"frontMatter": {},
- "permalink": "/hello/translatedMd",
+ "permalink": "/fr/hello/translatedMd",
"source": "@site/i18n/fr/docusaurus-plugin-content-pages/hello/translatedMd.md",
"title": undefined,
"type": "mdx",
},
{
- "permalink": "/hello/world",
+ "permalink": "/fr/hello/world",
"source": "@site/src/pages/hello/world.js",
"type": "jsx",
},
diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts
index 31706d3a4cd4..dd570260a417 100644
--- a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts
+++ b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts
@@ -32,15 +32,9 @@ describe('docusaurus-plugin-content-pages', () => {
it('loads simple pages with french translations', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
- const context = await loadContext({siteDir});
+ const context = await loadContext({siteDir, locale: 'fr'});
const plugin = pluginContentPages(
- {
- ...context,
- i18n: {
- ...context.i18n,
- currentLocale: 'fr',
- },
- },
+ context,
validateOptions({
validate: normalizePluginOptions,
options: {
diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts
index a9051e4545e6..7480962d07cc 100644
--- a/packages/docusaurus-plugin-content-pages/src/index.ts
+++ b/packages/docusaurus-plugin-content-pages/src/index.ts
@@ -48,18 +48,12 @@ export default function pluginContentPages(
[admonitions, options.admonitions],
]);
}
- const {
- siteConfig,
- siteDir,
- generatedFilesDir,
- i18n: {currentLocale},
- } = context;
+ const {siteConfig, siteDir, generatedFilesDir, localizationDir} = context;
const contentPaths: PagesContentPaths = {
contentPath: path.resolve(siteDir, options.path),
contentPathLocalized: getPluginI18nPath({
- siteDir,
- locale: currentLocale,
+ localizationDir,
pluginName: 'docusaurus-plugin-content-pages',
pluginId: options.id,
}),
diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts
index ee4151930eee..2cae26753d5b 100644
--- a/packages/docusaurus-types/src/index.d.ts
+++ b/packages/docusaurus-types/src/index.d.ts
@@ -70,6 +70,11 @@ export type I18nConfig = {
* 3. Will be used for the `` tag
*/
defaultLocale: string;
+ /**
+ * Root folder which all locale folders are relative to. Can be absolute or
+ * relative to the config file. e.g. `i18n`
+ */
+ path: string;
/** List of locales deployed on your site. Must contain `defaultLocale`. */
locales: [string, ...string[]];
/** Individual options for each locale. */
@@ -416,6 +421,12 @@ export type LoadContext = {
siteConfig: DocusaurusConfig;
siteConfigPath: string;
outDir: string;
+ /**
+ * Directory where all source translations for the current locale can be found
+ * in. Constructed with `i18n.path` + `i18n.currentLocale.path` (e.g.
+ * `/i18n/en`)
+ */
+ localizationDir: string;
/**
* Duplicated from `siteConfig.baseUrl`, but probably worth keeping. We mutate
* `siteConfig` to make `baseUrl` there localized as well, but that's mostly
diff --git a/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts b/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts
index c0a458d3a8ec..c0b45f358568 100644
--- a/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts
+++ b/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts
@@ -65,34 +65,33 @@ describe('getPluginI18nPath', () => {
it('gets correct path', () => {
expect(
getPluginI18nPath({
- siteDir: __dirname,
- locale: 'zh-Hans',
+ localizationDir: '/i18n/zh-Hans',
pluginName: 'plugin-content-docs',
pluginId: 'community',
subPaths: ['foo'],
}),
).toMatchInlineSnapshot(
- `"/packages/docusaurus-utils/src/__tests__/i18n/zh-Hans/plugin-content-docs-community/foo"`,
+ `"/i18n/zh-Hans/plugin-content-docs-community/foo"`,
);
});
it('gets correct path for default plugin', () => {
expect(
getPluginI18nPath({
- siteDir: __dirname,
- locale: 'zh-Hans',
+ localizationDir: '/i18n/zh-Hans',
pluginName: 'plugin-content-docs',
subPaths: ['foo'],
- }).replace(__dirname, ''),
- ).toMatchInlineSnapshot(`"/i18n/zh-Hans/plugin-content-docs/foo"`);
+ }),
+ ).toMatchInlineSnapshot(
+ `"/i18n/zh-Hans/plugin-content-docs/foo"`,
+ );
});
it('gets correct path when no sub-paths', () => {
expect(
getPluginI18nPath({
- siteDir: __dirname,
- locale: 'zh-Hans',
+ localizationDir: '/i18n/zh-Hans',
pluginName: 'plugin-content-docs',
- }).replace(__dirname, ''),
- ).toMatchInlineSnapshot(`"/i18n/zh-Hans/plugin-content-docs"`);
+ }),
+ ).toMatchInlineSnapshot(`"/i18n/zh-Hans/plugin-content-docs"`);
});
});
@@ -104,6 +103,7 @@ describe('localizePath', () => {
path: '/baseUrl',
i18n: {
defaultLocale: 'en',
+ path: 'i18n',
locales: ['en', 'fr'],
currentLocale: 'fr',
localeConfigs: {},
@@ -120,6 +120,7 @@ describe('localizePath', () => {
path: '/baseFsPath',
i18n: {
defaultLocale: 'en',
+ path: 'i18n',
locales: ['en', 'fr'],
currentLocale: 'fr',
localeConfigs: {},
@@ -136,6 +137,7 @@ describe('localizePath', () => {
path: '/baseUrl/',
i18n: {
defaultLocale: 'en',
+ path: 'i18n',
locales: ['en', 'fr'],
currentLocale: 'en',
localeConfigs: {},
@@ -152,6 +154,7 @@ describe('localizePath', () => {
path: '/baseUrl/',
i18n: {
defaultLocale: 'en',
+ path: 'i18n',
locales: ['en', 'fr'],
currentLocale: 'en',
localeConfigs: {},
@@ -167,6 +170,7 @@ describe('localizePath', () => {
path: '/baseUrl/',
i18n: {
defaultLocale: 'en',
+ path: 'i18n',
locales: ['en', 'fr'],
currentLocale: 'en',
localeConfigs: {},
diff --git a/packages/docusaurus-utils/src/constants.ts b/packages/docusaurus-utils/src/constants.ts
index 9f8c452669bb..6ecba7aea0f5 100644
--- a/packages/docusaurus-utils/src/constants.ts
+++ b/packages/docusaurus-utils/src/constants.ts
@@ -75,7 +75,7 @@ export const THEME_PATH = `${SRC_DIR_NAME}/theme`;
* All translation-related data live here, relative to site directory. Content
* will be namespaced by locale.
*/
-export const I18N_DIR_NAME = 'i18n';
+export const DEFAULT_I18N_DIR_NAME = 'i18n';
/**
* Translations for React code.
diff --git a/packages/docusaurus-utils/src/i18nUtils.ts b/packages/docusaurus-utils/src/i18nUtils.ts
index a6d08cf83749..8c706f1b8ce7 100644
--- a/packages/docusaurus-utils/src/i18nUtils.ts
+++ b/packages/docusaurus-utils/src/i18nUtils.ts
@@ -7,7 +7,7 @@
import path from 'path';
import _ from 'lodash';
-import {DEFAULT_PLUGIN_ID, I18N_DIR_NAME} from './constants';
+import {DEFAULT_PLUGIN_ID} from './constants';
import {normalizeUrl} from './urlUtils';
import type {
TranslationFileContent,
@@ -46,24 +46,18 @@ export function updateTranslationFileMessages(
* expect everything it needs for translations to be found under this path.
*/
export function getPluginI18nPath({
- siteDir,
- locale,
+ localizationDir,
pluginName,
pluginId = DEFAULT_PLUGIN_ID,
subPaths = [],
}: {
- siteDir: string;
- locale: string;
+ localizationDir: string;
pluginName: string;
pluginId?: string | undefined;
subPaths?: string[];
}): string {
return path.join(
- siteDir,
- I18N_DIR_NAME,
- // Namespace first by locale: convenient to work in a single folder for a
- // translator
- locale,
+ localizationDir,
// Make it convenient to use for single-instance
// ie: return "docs", not "docs-default" nor "docs/default"
`${pluginName}${pluginId === DEFAULT_PLUGIN_ID ? '' : `-${pluginId}`}`,
diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts
index 520f5f73aab2..30549ba99c03 100644
--- a/packages/docusaurus-utils/src/index.ts
+++ b/packages/docusaurus-utils/src/index.ts
@@ -17,7 +17,7 @@ export {
DEFAULT_STATIC_DIR_NAME,
OUTPUT_STATIC_ASSETS_DIR_NAME,
THEME_PATH,
- I18N_DIR_NAME,
+ DEFAULT_I18N_DIR_NAME,
CODE_TRANSLATIONS_FILE_NAME,
DEFAULT_PORT,
DEFAULT_PLUGIN_ID,
diff --git a/packages/docusaurus/src/commands/start.ts b/packages/docusaurus/src/commands/start.ts
index f49874839d68..e41ae556cddf 100644
--- a/packages/docusaurus/src/commands/start.ts
+++ b/packages/docusaurus/src/commands/start.ts
@@ -25,7 +25,6 @@ import {
getHttpsConfig,
} from '../webpack/utils';
import {getHostPort, type HostPortOptions} from '../server/getHostPort';
-import {getTranslationsLocaleDirPath} from '../server/translations/translations';
export type StartCLIOptions = HostPortOptions &
Pick & {
@@ -82,7 +81,7 @@ export async function start(
logger.error(err.stack);
});
}, 500);
- const {siteConfig, plugins} = props;
+ const {siteConfig, plugins, localizationDir} = props;
const normalizeToSiteDir = (filepath: string) => {
if (filepath && path.isAbsolute(filepath)) {
@@ -96,14 +95,7 @@ export async function start(
.filter(Boolean)
.map(normalizeToSiteDir);
- const pathsToWatch = [
- ...pluginPaths,
- props.siteConfigPath,
- getTranslationsLocaleDirPath({
- siteDir,
- locale: props.i18n.currentLocale,
- }),
- ];
+ const pathsToWatch = [...pluginPaths, props.siteConfigPath, localizationDir];
const pollingOptions = {
usePolling: !!cliOptions.poll,
diff --git a/packages/docusaurus/src/commands/writeTranslations.ts b/packages/docusaurus/src/commands/writeTranslations.ts
index 14ae1e64adc6..d69014306fcf 100644
--- a/packages/docusaurus/src/commands/writeTranslations.ts
+++ b/packages/docusaurus/src/commands/writeTranslations.ts
@@ -47,14 +47,12 @@ async function getExtraSourceCodeFilePaths(): Promise {
}
async function writePluginTranslationFiles({
- siteDir,
+ localizationDir,
plugin,
- locale,
options,
}: {
- siteDir: string;
+ localizationDir: string;
plugin: InitializedPlugin;
- locale: string;
options: WriteTranslationsOptions;
}) {
if (plugin.getTranslationFiles) {
@@ -66,10 +64,9 @@ async function writePluginTranslationFiles({
await Promise.all(
translationFiles.map(async (translationFile) => {
await writePluginTranslations({
- siteDir,
+ localizationDir,
plugin,
translationFile,
- locale,
options,
});
}),
@@ -86,6 +83,7 @@ export async function writeTranslations(
config: options.config,
locale: options.locale,
});
+ const {localizationDir} = context;
const plugins = await initPlugins(context);
const locale = options.locale ?? context.i18n.defaultLocale;
@@ -116,11 +114,11 @@ Available locales are: ${context.i18n.locales.join(',')}.`,
defaultCodeMessages,
});
- await writeCodeTranslations({siteDir, locale}, codeTranslations, options);
+ await writeCodeTranslations({localizationDir}, codeTranslations, options);
await Promise.all(
plugins.map(async (plugin) => {
- await writePluginTranslationFiles({siteDir, plugin, locale, options});
+ await writePluginTranslationFiles({localizationDir, plugin, options});
}),
);
}
diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap
index 2c20bb59c885..64dfeff98eea 100644
--- a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap
+++ b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap
@@ -13,6 +13,7 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = `
"locales": [
"en",
],
+ "path": "i18n",
},
"noIndex": false,
"onBrokenLinks": "throw",
@@ -49,6 +50,7 @@ exports[`loadSiteConfig website with valid async config 1`] = `
"locales": [
"en",
],
+ "path": "i18n",
},
"noIndex": false,
"onBrokenLinks": "throw",
@@ -87,6 +89,7 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = `
"locales": [
"en",
],
+ "path": "i18n",
},
"noIndex": false,
"onBrokenLinks": "throw",
@@ -125,6 +128,7 @@ exports[`loadSiteConfig website with valid config creator function 1`] = `
"locales": [
"en",
],
+ "path": "i18n",
},
"noIndex": false,
"onBrokenLinks": "throw",
@@ -166,6 +170,7 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = `
"locales": [
"en",
],
+ "path": "i18n",
},
"noIndex": false,
"onBrokenLinks": "throw",
diff --git a/packages/docusaurus/src/server/__tests__/i18n.test.ts b/packages/docusaurus/src/server/__tests__/i18n.test.ts
index 84e1e83ada2d..8ef8fea9faf3 100644
--- a/packages/docusaurus/src/server/__tests__/i18n.test.ts
+++ b/packages/docusaurus/src/server/__tests__/i18n.test.ts
@@ -8,7 +8,7 @@
import {jest} from '@jest/globals';
import {loadI18n, getDefaultLocaleConfig} from '../i18n';
import {DEFAULT_I18N_CONFIG} from '../configValidation';
-import type {I18nConfig} from '@docusaurus/types';
+import type {DocusaurusConfig, I18nConfig} from '@docusaurus/types';
function testLocaleConfigsFor(locales: string[]) {
return Object.fromEntries(
@@ -18,10 +18,9 @@ function testLocaleConfigsFor(locales: string[]) {
function loadI18nTest(i18nConfig: I18nConfig, locale?: string) {
return loadI18n(
- // @ts-expect-error: enough for this test
{
i18n: i18nConfig,
- },
+ } as DocusaurusConfig,
{locale},
);
}
@@ -101,6 +100,7 @@ describe('loadI18n', () => {
it('loads I18n for default config', async () => {
await expect(loadI18nTest(DEFAULT_I18N_CONFIG)).resolves.toEqual({
+ path: 'i18n',
defaultLocale: 'en',
locales: ['en'],
currentLocale: 'en',
@@ -111,12 +111,14 @@ describe('loadI18n', () => {
it('loads I18n for multi-lang config', async () => {
await expect(
loadI18nTest({
+ path: 'i18n',
defaultLocale: 'fr',
locales: ['en', 'fr', 'de'],
localeConfigs: {},
}),
).resolves.toEqual({
defaultLocale: 'fr',
+ path: 'i18n',
locales: ['en', 'fr', 'de'],
currentLocale: 'fr',
localeConfigs: testLocaleConfigsFor(['en', 'fr', 'de']),
@@ -127,6 +129,7 @@ describe('loadI18n', () => {
await expect(
loadI18nTest(
{
+ path: 'i18n',
defaultLocale: 'fr',
locales: ['en', 'fr', 'de'],
localeConfigs: {},
@@ -135,6 +138,7 @@ describe('loadI18n', () => {
),
).resolves.toEqual({
defaultLocale: 'fr',
+ path: 'i18n',
locales: ['en', 'fr', 'de'],
currentLocale: 'de',
localeConfigs: testLocaleConfigsFor(['en', 'fr', 'de']),
@@ -145,6 +149,7 @@ describe('loadI18n', () => {
await expect(
loadI18nTest(
{
+ path: 'i18n',
defaultLocale: 'fr',
locales: ['en', 'fr', 'de'],
localeConfigs: {
@@ -156,6 +161,7 @@ describe('loadI18n', () => {
),
).resolves.toEqual({
defaultLocale: 'fr',
+ path: 'i18n',
locales: ['en', 'fr', 'de'],
currentLocale: 'de',
localeConfigs: {
@@ -174,6 +180,7 @@ describe('loadI18n', () => {
it('warns when trying to load undeclared locale', async () => {
await loadI18nTest(
{
+ path: 'i18n',
defaultLocale: 'fr',
locales: ['en', 'fr', 'de'],
localeConfigs: {},
diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts
index 15a66776b19d..4027f84ca4d9 100644
--- a/packages/docusaurus/src/server/configValidation.ts
+++ b/packages/docusaurus/src/server/configValidation.ts
@@ -5,7 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
-import {DEFAULT_STATIC_DIR_NAME} from '@docusaurus/utils';
+import {
+ DEFAULT_STATIC_DIR_NAME,
+ DEFAULT_I18N_DIR_NAME,
+} from '@docusaurus/utils';
import {Joi, URISchema, printWarning} from '@docusaurus/utils-validation';
import type {DocusaurusConfig, I18nConfig} from '@docusaurus/types';
@@ -13,6 +16,7 @@ const DEFAULT_I18N_LOCALE = 'en';
export const DEFAULT_I18N_CONFIG: I18nConfig = {
defaultLocale: DEFAULT_I18N_LOCALE,
+ path: DEFAULT_I18N_DIR_NAME,
locales: [DEFAULT_I18N_LOCALE],
localeConfigs: {},
};
@@ -135,6 +139,7 @@ const LocaleConfigSchema = Joi.object({
const I18N_CONFIG_SCHEMA = Joi.object({
defaultLocale: Joi.string().required(),
+ path: Joi.string().default(DEFAULT_I18N_CONFIG.path),
locales: Joi.array().items().min(1).items(Joi.string().required()).required(),
localeConfigs: Joi.object()
.pattern(/.*/, LocaleConfigSchema)
diff --git a/packages/docusaurus/src/server/i18n.ts b/packages/docusaurus/src/server/i18n.ts
index cd9bbe4990ea..59b7b85a38f1 100644
--- a/packages/docusaurus/src/server/i18n.ts
+++ b/packages/docusaurus/src/server/i18n.ts
@@ -60,6 +60,7 @@ Note: Docusaurus only support running one locale at a time.`;
return {
defaultLocale: i18nConfig.defaultLocale,
locales,
+ path: i18nConfig.path,
currentLocale,
localeConfigs,
};
diff --git a/packages/docusaurus/src/server/index.ts b/packages/docusaurus/src/server/index.ts
index 90908237b04f..27a21d5248cb 100644
--- a/packages/docusaurus/src/server/index.ts
+++ b/packages/docusaurus/src/server/index.ts
@@ -85,11 +85,11 @@ export async function loadContext(
const siteConfig: DocusaurusConfig = {...initialSiteConfig, baseUrl};
+ // TODO allow customizing localizationDir per-locale
+ const localizationDir = path.resolve(siteDir, i18n.path, i18n.currentLocale);
+
const codeTranslationFileContent =
- (await readCodeTranslationFileContent({
- siteDir,
- locale: i18n.currentLocale,
- })) ?? {};
+ (await readCodeTranslationFileContent({localizationDir})) ?? {};
// We only need key->message for code translations
const codeTranslations = _.mapValues(
@@ -100,6 +100,7 @@ export async function loadContext(
return {
siteDir,
generatedFilesDir,
+ localizationDir,
siteConfig,
siteConfigPath,
outDir,
@@ -125,6 +126,7 @@ export async function load(options: LoadContextOptions): Promise {
outDir,
baseUrl,
i18n,
+ localizationDir,
codeTranslations: siteCodeTranslations,
} = context;
const {plugins, pluginsRouteConfigs, globalData} = await loadPlugins(context);
@@ -246,6 +248,7 @@ ${Object.entries(registry)
outDir,
baseUrl,
i18n,
+ localizationDir,
generatedFilesDir,
routes: pluginsRouteConfigs,
routesPaths,
diff --git a/packages/docusaurus/src/server/plugins/index.ts b/packages/docusaurus/src/server/plugins/index.ts
index 5a8dbb98d095..73fba212b289 100644
--- a/packages/docusaurus/src/server/plugins/index.ts
+++ b/packages/docusaurus/src/server/plugins/index.ts
@@ -56,8 +56,7 @@ export async function loadPlugins(context: LoadContext): Promise<{
const translationFiles = await Promise.all(
rawTranslationFiles.map((translationFile) =>
localizePluginTranslationFile({
- locale: context.i18n.currentLocale,
- siteDir: context.siteDir,
+ localizationDir: context.localizationDir,
translationFile,
plugin,
}),
diff --git a/packages/docusaurus/src/server/translations/__tests__/translations.test.ts b/packages/docusaurus/src/server/translations/__tests__/translations.test.ts
index 1e1b59aad85f..a62aae849802 100644
--- a/packages/docusaurus/src/server/translations/__tests__/translations.test.ts
+++ b/packages/docusaurus/src/server/translations/__tests__/translations.test.ts
@@ -44,8 +44,10 @@ async function createTmpTranslationFile(
}
return {
- siteDir,
- readFile: () => fs.readJSON(filePath),
+ localizationDir: path.join(siteDir, 'i18n/en'),
+ readFile() {
+ return fs.readJSON(filePath);
+ },
};
}
@@ -58,9 +60,9 @@ describe('writeCodeTranslations', () => {
});
it('creates new translation file', async () => {
- const {siteDir, readFile} = await createTmpTranslationFile(null);
+ const {localizationDir, readFile} = await createTmpTranslationFile(null);
await writeCodeTranslations(
- {siteDir, locale: 'en'},
+ {localizationDir},
{
key1: {message: 'key1 message'},
key2: {message: 'key2 message'},
@@ -80,9 +82,9 @@ describe('writeCodeTranslations', () => {
});
it('creates new translation file with prefix', async () => {
- const {siteDir, readFile} = await createTmpTranslationFile(null);
+ const {localizationDir, readFile} = await createTmpTranslationFile(null);
await writeCodeTranslations(
- {siteDir, locale: 'en'},
+ {localizationDir},
{
key1: {message: 'key1 message'},
key2: {message: 'key2 message'},
@@ -104,14 +106,14 @@ describe('writeCodeTranslations', () => {
});
it('appends missing translations', async () => {
- const {siteDir, readFile} = await createTmpTranslationFile({
+ const {localizationDir, readFile} = await createTmpTranslationFile({
key1: {message: 'key1 message'},
key2: {message: 'key2 message'},
key3: {message: 'key3 message'},
});
await writeCodeTranslations(
- {siteDir, locale: 'en'},
+ {localizationDir},
{
key1: {message: 'key1 message new'},
key2: {message: 'key2 message new'},
@@ -133,12 +135,12 @@ describe('writeCodeTranslations', () => {
});
it('appends missing.* translations with prefix', async () => {
- const {siteDir, readFile} = await createTmpTranslationFile({
+ const {localizationDir, readFile} = await createTmpTranslationFile({
key1: {message: 'key1 message'},
});
await writeCodeTranslations(
- {siteDir, locale: 'en'},
+ {localizationDir},
{
key1: {message: 'key1 message new'},
key2: {message: 'key2 message new'},
@@ -158,12 +160,12 @@ describe('writeCodeTranslations', () => {
});
it('overrides missing translations', async () => {
- const {siteDir, readFile} = await createTmpTranslationFile({
+ const {localizationDir, readFile} = await createTmpTranslationFile({
key1: {message: 'key1 message'},
});
await writeCodeTranslations(
- {siteDir, locale: 'en'},
+ {localizationDir},
{
key1: {message: 'key1 message new'},
key2: {message: 'key2 message new'},
@@ -183,12 +185,12 @@ describe('writeCodeTranslations', () => {
});
it('overrides missing translations with prefix', async () => {
- const {siteDir, readFile} = await createTmpTranslationFile({
+ const {localizationDir, readFile} = await createTmpTranslationFile({
key1: {message: 'key1 message'},
});
await writeCodeTranslations(
- {siteDir, locale: 'en'},
+ {localizationDir},
{
key1: {message: 'key1 message new'},
key2: {message: 'key2 message new'},
@@ -209,14 +211,14 @@ describe('writeCodeTranslations', () => {
});
it('always overrides message description', async () => {
- const {siteDir, readFile} = await createTmpTranslationFile({
+ const {localizationDir, readFile} = await createTmpTranslationFile({
key1: {message: 'key1 message', description: 'key1 desc'},
key2: {message: 'key2 message', description: 'key2 desc'},
key3: {message: 'key3 message', description: undefined},
});
await writeCodeTranslations(
- {siteDir, locale: 'en'},
+ {localizationDir},
{
key1: {message: 'key1 message new', description: undefined},
key2: {message: 'key2 message new', description: 'key2 desc new'},
@@ -236,9 +238,9 @@ describe('writeCodeTranslations', () => {
});
it('does not create empty translation files', async () => {
- const {siteDir, readFile} = await createTmpTranslationFile(null);
+ const {localizationDir, readFile} = await createTmpTranslationFile(null);
- await writeCodeTranslations({siteDir, locale: 'en'}, {}, {});
+ await writeCodeTranslations({localizationDir}, {}, {});
await expect(readFile()).rejects.toThrowError(
/ENOENT: no such file or directory, open /,
@@ -247,14 +249,14 @@ describe('writeCodeTranslations', () => {
});
it('throws for invalid content', async () => {
- const {siteDir} = await createTmpTranslationFile(
+ const {localizationDir} = await createTmpTranslationFile(
// @ts-expect-error: bad content on purpose
{bad: 'content'},
);
await expect(() =>
writeCodeTranslations(
- {siteDir, locale: 'en'},
+ {localizationDir},
{
key1: {message: 'key1 message'},
},
@@ -269,19 +271,16 @@ describe('writeCodeTranslations', () => {
describe('writePluginTranslations', () => {
it('writes plugin translations', async () => {
- const siteDir = await createTmpSiteDir();
+ const localizationDir = await createTmpSiteDir();
const filePath = path.join(
- siteDir,
- 'i18n',
- 'fr',
+ localizationDir,
'my-plugin-name',
'my/translation/file.json',
);
await writePluginTranslations({
- siteDir,
- locale: 'fr',
+ localizationDir,
translationFile: {
path: 'my/translation/file',
content: {
@@ -306,12 +305,10 @@ describe('writePluginTranslations', () => {
});
it('writes plugin translations consecutively with different options', async () => {
- const siteDir = await createTmpSiteDir();
+ const localizationDir = await createTmpSiteDir();
const filePath = path.join(
- siteDir,
- 'i18n',
- 'fr',
+ localizationDir,
'my-plugin-name-my-plugin-id',
'my/translation/file.json',
);
@@ -321,7 +318,7 @@ describe('writePluginTranslations', () => {
options?: WriteTranslationsOptions,
) {
return writePluginTranslations({
- siteDir,
+ localizationDir,
locale: 'fr',
translationFile: {
path: 'my/translation/file',
@@ -381,12 +378,11 @@ describe('writePluginTranslations', () => {
});
it('throws with explicit extension', async () => {
- const siteDir = await createTmpSiteDir();
+ const localizationDir = await createTmpSiteDir();
await expect(() =>
writePluginTranslations({
- siteDir,
- locale: 'fr',
+ localizationDir,
translationFile: {
path: 'my/translation/file.json',
content: {},
@@ -409,7 +405,7 @@ describe('writePluginTranslations', () => {
describe('localizePluginTranslationFile', () => {
it('does not localize if localized file does not exist', async () => {
- const siteDir = await createTmpSiteDir();
+ const localizationDir = await createTmpSiteDir();
const translationFile: TranslationFile = {
path: 'my/translation/file',
@@ -421,8 +417,7 @@ describe('localizePluginTranslationFile', () => {
};
const localizedTranslationFile = await localizePluginTranslationFile({
- siteDir,
- locale: 'fr',
+ localizationDir,
translationFile,
plugin: {
name: 'my-plugin-name',
@@ -434,16 +429,10 @@ describe('localizePluginTranslationFile', () => {
});
it('normalizes partially localized translation files', async () => {
- const siteDir = await createTmpSiteDir();
+ const localizationDir = await createTmpSiteDir();
await fs.outputJSON(
- path.join(
- siteDir,
- 'i18n',
- 'fr',
- 'my-plugin-name',
- 'my/translation/file.json',
- ),
+ path.join(localizationDir, 'my-plugin-name', 'my/translation/file.json'),
{
key2: {message: 'key2 message localized'},
key4: {message: 'key4 message localized'},
@@ -460,8 +449,7 @@ describe('localizePluginTranslationFile', () => {
};
const localizedTranslationFile = await localizePluginTranslationFile({
- siteDir,
- locale: 'fr',
+ localizationDir,
translationFile,
plugin: {
name: 'my-plugin-name',
@@ -486,13 +474,13 @@ describe('localizePluginTranslationFile', () => {
describe('readCodeTranslationFileContent', () => {
async function testReadTranslation(val: TranslationFileContent) {
- const {siteDir} = await createTmpTranslationFile(val);
- return readCodeTranslationFileContent({siteDir, locale: 'en'});
+ const {localizationDir} = await createTmpTranslationFile(val);
+ return readCodeTranslationFileContent({localizationDir});
}
it("returns undefined if file does't exist", async () => {
await expect(
- readCodeTranslationFileContent({siteDir: 'foo', locale: 'en'}),
+ readCodeTranslationFileContent({localizationDir: 'foo'}),
).resolves.toBeUndefined();
});
diff --git a/packages/docusaurus/src/server/translations/translations.ts b/packages/docusaurus/src/server/translations/translations.ts
index fd3fb0305523..83d19495bffc 100644
--- a/packages/docusaurus/src/server/translations/translations.ts
+++ b/packages/docusaurus/src/server/translations/translations.ts
@@ -12,7 +12,6 @@ import logger from '@docusaurus/logger';
import {
getPluginI18nPath,
toMessageRelativeFilePath,
- I18N_DIR_NAME,
CODE_TRANSLATIONS_FILE_NAME,
} from '@docusaurus/utils';
import {Joi} from '@docusaurus/utils-validation';
@@ -29,8 +28,7 @@ export type WriteTranslationsOptions = {
};
type TranslationContext = {
- siteDir: string;
- locale: string;
+ localizationDir: string;
};
const TranslationFileContentSchema = Joi.object()
@@ -143,18 +141,8 @@ Maybe you should remove them? ${unknownKeys}`;
}
}
-// Should we make this configurable?
-export function getTranslationsLocaleDirPath(
- context: TranslationContext,
-): string {
- return path.join(context.siteDir, I18N_DIR_NAME, context.locale);
-}
-
function getCodeTranslationsFilePath(context: TranslationContext): string {
- return path.join(
- getTranslationsLocaleDirPath(context),
- CODE_TRANSLATIONS_FILE_NAME,
- );
+ return path.join(context.localizationDir, CODE_TRANSLATIONS_FILE_NAME);
}
export async function readCodeTranslationFileContent(
@@ -187,17 +175,15 @@ function addTranslationFileExtension(translationFilePath: string) {
}
function getPluginTranslationFilePath({
- siteDir,
+ localizationDir,
plugin,
- locale,
translationFilePath,
}: TranslationContext & {
plugin: InitializedPlugin;
translationFilePath: string;
}): string {
const dirPath = getPluginI18nPath({
- siteDir,
- locale,
+ localizationDir,
pluginName: plugin.name,
pluginId: plugin.options.id,
});
@@ -206,9 +192,8 @@ function getPluginTranslationFilePath({
}
export async function writePluginTranslations({
- siteDir,
+ localizationDir,
plugin,
- locale,
translationFile,
options,
}: TranslationContext & {
@@ -218,8 +203,7 @@ export async function writePluginTranslations({
}): Promise {
const filePath = getPluginTranslationFilePath({
plugin,
- siteDir,
- locale,
+ localizationDir,
translationFilePath: translationFile.path,
});
await writeTranslationFileContent({
@@ -230,9 +214,8 @@ export async function writePluginTranslations({
}
export async function localizePluginTranslationFile({
- siteDir,
+ localizationDir,
plugin,
- locale,
translationFile,
}: TranslationContext & {
plugin: InitializedPlugin;
@@ -240,8 +223,7 @@ export async function localizePluginTranslationFile({
}): Promise {
const filePath = getPluginTranslationFilePath({
plugin,
- siteDir,
- locale,
+ localizationDir,
translationFilePath: translationFile.path,
});