feat(core): allow customizing the i18n directory path (#7386)

Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
Joshua Chen 2022-06-02 23:37:14 +08:00 committed by GitHub
parent c07a514730
commit abe5450526
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 147 additions and 166 deletions

View file

@ -19,6 +19,7 @@ const DefaultI18N: I18n = {
currentLocale: 'en',
locales: ['en'],
defaultLocale: 'en',
path: '1i8n',
localeConfigs: {
en: {
label: 'English',

View file

@ -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<

View file

@ -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,
}),

View file

@ -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,
});

View file

@ -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 = {

View file

@ -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,
});

View file

@ -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,
})

View file

@ -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",
},

View file

@ -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: {

View file

@ -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,
}),

View file

@ -70,6 +70,11 @@ export type I18nConfig = {
* 3. Will be used for the `<link hrefLang="x-default">` 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.
* `<siteDir>/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

View file

@ -65,34 +65,33 @@ describe('getPluginI18nPath', () => {
it('gets correct path', () => {
expect(
getPluginI18nPath({
siteDir: __dirname,
locale: 'zh-Hans',
localizationDir: '<SITE_DIR>/i18n/zh-Hans',
pluginName: 'plugin-content-docs',
pluginId: 'community',
subPaths: ['foo'],
}),
).toMatchInlineSnapshot(
`"<PROJECT_ROOT>/packages/docusaurus-utils/src/__tests__/i18n/zh-Hans/plugin-content-docs-community/foo"`,
`"<SITE_DIR>/i18n/zh-Hans/plugin-content-docs-community/foo"`,
);
});
it('gets correct path for default plugin', () => {
expect(
getPluginI18nPath({
siteDir: __dirname,
locale: 'zh-Hans',
localizationDir: '<SITE_DIR>/i18n/zh-Hans',
pluginName: 'plugin-content-docs',
subPaths: ['foo'],
}).replace(__dirname, ''),
).toMatchInlineSnapshot(`"/i18n/zh-Hans/plugin-content-docs/foo"`);
}),
).toMatchInlineSnapshot(
`"<SITE_DIR>/i18n/zh-Hans/plugin-content-docs/foo"`,
);
});
it('gets correct path when no sub-paths', () => {
expect(
getPluginI18nPath({
siteDir: __dirname,
locale: 'zh-Hans',
localizationDir: '<SITE_DIR>/i18n/zh-Hans',
pluginName: 'plugin-content-docs',
}).replace(__dirname, ''),
).toMatchInlineSnapshot(`"/i18n/zh-Hans/plugin-content-docs"`);
}),
).toMatchInlineSnapshot(`"<SITE_DIR>/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: {},

View file

@ -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.

View file

@ -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}`}`,

View file

@ -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,

View file

@ -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<LoadContextOptions, 'locale' | 'config'> & {
@ -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,

View file

@ -47,14 +47,12 @@ async function getExtraSourceCodeFilePaths(): Promise<string[]> {
}
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});
}),
);
}

View file

@ -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",

View file

@ -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: {},

View file

@ -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<I18nConfig>({
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)

View file

@ -60,6 +60,7 @@ Note: Docusaurus only support running one locale at a time.`;
return {
defaultLocale: i18nConfig.defaultLocale,
locales,
path: i18nConfig.path,
currentLocale,
localeConfigs,
};

View file

@ -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<Props> {
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,

View file

@ -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,
}),

View file

@ -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();
});

View file

@ -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<TranslationFileContent>()
@ -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<void> {
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<TranslationFile> {
const filePath = getPluginTranslationFilePath({
plugin,
siteDir,
locale,
localizationDir,
translationFilePath: translationFile.path,
});