mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-11 08:07:26 +02:00
feat: allow customizing localization path of each locale (#7624)
This commit is contained in:
parent
39e3e3715e
commit
1b9bec1042
24 changed files with 254 additions and 30 deletions
|
@ -26,6 +26,7 @@ const DefaultI18N: I18n = {
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
htmlLang: 'en',
|
htmlLang: 'en',
|
||||||
calendar: 'gregory',
|
calendar: 'gregory',
|
||||||
|
path: 'en',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -54,6 +54,7 @@ function getI18n(locale: string): I18n {
|
||||||
label: locale,
|
label: locale,
|
||||||
htmlLang: locale,
|
htmlLang: locale,
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
|
path: locale,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -71,7 +72,11 @@ const getPlugin = async (
|
||||||
i18n: I18n = DefaultI18N,
|
i18n: I18n = DefaultI18N,
|
||||||
) => {
|
) => {
|
||||||
const generatedFilesDir: string = path.resolve(siteDir, '.docusaurus');
|
const generatedFilesDir: string = path.resolve(siteDir, '.docusaurus');
|
||||||
const localizationDir = path.join(siteDir, i18n.path, i18n.currentLocale);
|
const localizationDir = path.join(
|
||||||
|
siteDir,
|
||||||
|
i18n.path,
|
||||||
|
i18n.localeConfigs[i18n.currentLocale]!.path,
|
||||||
|
);
|
||||||
const siteConfig = {
|
const siteConfig = {
|
||||||
title: 'Hello',
|
title: 'Hello',
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
[{ "type": "autogenerated", "dirName": "." }]
|
|
@ -66,3 +66,12 @@ exports[`docsVersion second docs instance versioning 1`] = `
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`docsVersion works with custom i18n paths 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"dirName": ".",
|
||||||
|
"type": "autogenerated",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
Binary file not shown.
|
@ -85,9 +85,11 @@ export async function cliDocsVersionCommand(
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
i18n.locales.map(async (locale) => {
|
i18n.locales.map(async (locale) => {
|
||||||
// TODO duplicated logic from core, so duplicate comment as well: we need
|
const localizationDir = path.resolve(
|
||||||
// to support customization per-locale in the future
|
siteDir,
|
||||||
const localizationDir = path.resolve(siteDir, i18n.path, locale);
|
i18n.path,
|
||||||
|
i18n.localeConfigs[locale]!.path,
|
||||||
|
);
|
||||||
// Copy docs files.
|
// Copy docs files.
|
||||||
const docsDir =
|
const docsDir =
|
||||||
locale === i18n.defaultLocale
|
locale === i18n.defaultLocale
|
||||||
|
|
6
packages/docusaurus-types/src/index.d.ts
vendored
6
packages/docusaurus-types/src/index.d.ts
vendored
|
@ -61,6 +61,12 @@ export type I18nLocaleConfig = {
|
||||||
* or `en-US` (`en` means `en-US`).
|
* or `en-US` (`en` means `en-US`).
|
||||||
*/
|
*/
|
||||||
calendar: string;
|
calendar: string;
|
||||||
|
/**
|
||||||
|
* Root folder that all plugin localization folders of this locale are
|
||||||
|
* relative to. Will be resolved against `i18n.path`. Defaults to the locale's
|
||||||
|
* name.
|
||||||
|
*/
|
||||||
|
path: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type I18nConfig = {
|
export type I18nConfig = {
|
||||||
|
|
|
@ -123,7 +123,7 @@ describe('localizePath', () => {
|
||||||
path: 'i18n',
|
path: 'i18n',
|
||||||
locales: ['en', 'fr'],
|
locales: ['en', 'fr'],
|
||||||
currentLocale: 'fr',
|
currentLocale: 'fr',
|
||||||
localeConfigs: {},
|
localeConfigs: {fr: {path: 'fr'}, en: {path: 'en'}},
|
||||||
},
|
},
|
||||||
options: {localizePath: true},
|
options: {localizePath: true},
|
||||||
}),
|
}),
|
||||||
|
@ -140,7 +140,7 @@ describe('localizePath', () => {
|
||||||
path: 'i18n',
|
path: 'i18n',
|
||||||
locales: ['en', 'fr'],
|
locales: ['en', 'fr'],
|
||||||
currentLocale: 'en',
|
currentLocale: 'en',
|
||||||
localeConfigs: {},
|
localeConfigs: {fr: {path: 'fr'}, en: {path: 'en'}},
|
||||||
},
|
},
|
||||||
options: {localizePath: true},
|
options: {localizePath: true},
|
||||||
}),
|
}),
|
||||||
|
@ -157,7 +157,7 @@ describe('localizePath', () => {
|
||||||
path: 'i18n',
|
path: 'i18n',
|
||||||
locales: ['en', 'fr'],
|
locales: ['en', 'fr'],
|
||||||
currentLocale: 'en',
|
currentLocale: 'en',
|
||||||
localeConfigs: {},
|
localeConfigs: {fr: {path: 'fr'}, en: {path: 'en'}},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).toBe('/baseUrl/');
|
).toBe('/baseUrl/');
|
||||||
|
@ -173,7 +173,7 @@ describe('localizePath', () => {
|
||||||
path: 'i18n',
|
path: 'i18n',
|
||||||
locales: ['en', 'fr'],
|
locales: ['en', 'fr'],
|
||||||
currentLocale: 'en',
|
currentLocale: 'en',
|
||||||
localeConfigs: {},
|
localeConfigs: {fr: {path: 'fr'}, en: {path: 'en'}},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).toBe('/baseUrl/');
|
).toBe('/baseUrl/');
|
||||||
|
|
|
@ -68,6 +68,9 @@ export function getPluginI18nPath({
|
||||||
/**
|
/**
|
||||||
* Takes a path and returns a localized a version (which is basically `path +
|
* Takes a path and returns a localized a version (which is basically `path +
|
||||||
* i18n.currentLocale`).
|
* i18n.currentLocale`).
|
||||||
|
*
|
||||||
|
* This is used to resolve the `outDir` and `baseUrl` of each locale; it is NOT
|
||||||
|
* used to determine plugin localization file locations.
|
||||||
*/
|
*/
|
||||||
export function localizePath({
|
export function localizePath({
|
||||||
pathType,
|
pathType,
|
||||||
|
@ -94,13 +97,15 @@ export function localizePath({
|
||||||
};
|
};
|
||||||
}): string {
|
}): string {
|
||||||
const shouldLocalizePath: boolean =
|
const shouldLocalizePath: boolean =
|
||||||
//
|
|
||||||
options.localizePath ?? i18n.currentLocale !== i18n.defaultLocale;
|
options.localizePath ?? i18n.currentLocale !== i18n.defaultLocale;
|
||||||
|
|
||||||
if (!shouldLocalizePath) {
|
if (!shouldLocalizePath) {
|
||||||
return originalPath;
|
return originalPath;
|
||||||
}
|
}
|
||||||
// FS paths need special care, for Windows support
|
// FS paths need special care, for Windows support. Note: we don't use the
|
||||||
|
// locale config's `path` here, because this function is used for resolving
|
||||||
|
// outDir, which must be the same as baseUrl. When we have the baseUrl config,
|
||||||
|
// we need to sync the two.
|
||||||
if (pathType === 'fs') {
|
if (pathType === 'fs') {
|
||||||
return path.join(originalPath, i18n.currentLocale);
|
return path.join(originalPath, i18n.currentLocale);
|
||||||
}
|
}
|
||||||
|
|
17
packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/docusaurus.config.js
generated
Normal file
17
packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/docusaurus.config.js
generated
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
module.exports = {
|
||||||
|
title: 'Site',
|
||||||
|
url: 'https://example.com',
|
||||||
|
baseUrl: '/',
|
||||||
|
i18n: {
|
||||||
|
locales: ['en', 'zh-Hans'],
|
||||||
|
defaultLocale: 'en',
|
||||||
|
localeConfigs: {
|
||||||
|
en: {
|
||||||
|
path: 'en-custom'
|
||||||
|
},
|
||||||
|
'zh-Hans': {
|
||||||
|
path: 'zh-Hans-custom'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,118 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`load loads props for site with custom i18n path 1`] = `
|
||||||
|
{
|
||||||
|
"baseUrl": "/",
|
||||||
|
"codeTranslations": {},
|
||||||
|
"generatedFilesDir": "<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/.docusaurus",
|
||||||
|
"headTags": "",
|
||||||
|
"i18n": {
|
||||||
|
"currentLocale": "en",
|
||||||
|
"defaultLocale": "en",
|
||||||
|
"localeConfigs": {
|
||||||
|
"en": {
|
||||||
|
"calendar": "gregory",
|
||||||
|
"direction": "ltr",
|
||||||
|
"htmlLang": "en",
|
||||||
|
"label": "English",
|
||||||
|
"path": "en-custom",
|
||||||
|
},
|
||||||
|
"zh-Hans": {
|
||||||
|
"calendar": "gregory",
|
||||||
|
"direction": "ltr",
|
||||||
|
"htmlLang": "zh-Hans",
|
||||||
|
"label": "简体中文",
|
||||||
|
"path": "zh-Hans-custom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"locales": [
|
||||||
|
"en",
|
||||||
|
"zh-Hans",
|
||||||
|
],
|
||||||
|
"path": "i18n",
|
||||||
|
},
|
||||||
|
"localizationDir": "<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/i18n/en-custom",
|
||||||
|
"outDir": "<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/build",
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"content": undefined,
|
||||||
|
"getClientModules": [Function],
|
||||||
|
"injectHtmlTags": [Function],
|
||||||
|
"name": "docusaurus-bootstrap-plugin",
|
||||||
|
"options": {
|
||||||
|
"id": "default",
|
||||||
|
},
|
||||||
|
"path": "<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site",
|
||||||
|
"version": {
|
||||||
|
"type": "synthetic",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configureWebpack": [Function],
|
||||||
|
"content": undefined,
|
||||||
|
"name": "docusaurus-mdx-fallback-plugin",
|
||||||
|
"options": {
|
||||||
|
"id": "default",
|
||||||
|
},
|
||||||
|
"path": ".",
|
||||||
|
"version": {
|
||||||
|
"type": "synthetic",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"postBodyTags": "",
|
||||||
|
"preBodyTags": "",
|
||||||
|
"routes": [],
|
||||||
|
"routesPaths": [
|
||||||
|
"/404.html",
|
||||||
|
],
|
||||||
|
"siteConfig": {
|
||||||
|
"baseUrl": "/",
|
||||||
|
"baseUrlIssueBanner": true,
|
||||||
|
"clientModules": [],
|
||||||
|
"customFields": {},
|
||||||
|
"i18n": {
|
||||||
|
"defaultLocale": "en",
|
||||||
|
"localeConfigs": {
|
||||||
|
"en": {
|
||||||
|
"direction": "ltr",
|
||||||
|
"path": "en-custom",
|
||||||
|
},
|
||||||
|
"zh-Hans": {
|
||||||
|
"direction": "ltr",
|
||||||
|
"path": "zh-Hans-custom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"locales": [
|
||||||
|
"en",
|
||||||
|
"zh-Hans",
|
||||||
|
],
|
||||||
|
"path": "i18n",
|
||||||
|
},
|
||||||
|
"noIndex": false,
|
||||||
|
"onBrokenLinks": "throw",
|
||||||
|
"onBrokenMarkdownLinks": "warn",
|
||||||
|
"onDuplicateRoutes": "warn",
|
||||||
|
"plugins": [],
|
||||||
|
"presets": [],
|
||||||
|
"scripts": [],
|
||||||
|
"staticDirectories": [
|
||||||
|
"static",
|
||||||
|
],
|
||||||
|
"stylesheets": [],
|
||||||
|
"tagline": "",
|
||||||
|
"themeConfig": {},
|
||||||
|
"themes": [],
|
||||||
|
"title": "Site",
|
||||||
|
"titleDelimiter": "|",
|
||||||
|
"url": "https://example.com",
|
||||||
|
},
|
||||||
|
"siteConfigPath": "<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/docusaurus.config.js",
|
||||||
|
"siteDir": "<PROJECT_ROOT>/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site",
|
||||||
|
"siteMetadata": {
|
||||||
|
"docusaurusVersion": "<CURRENT_VERSION>",
|
||||||
|
"pluginVersions": {},
|
||||||
|
"siteVersion": undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
|
@ -32,42 +32,49 @@ describe('defaultLocaleConfig', () => {
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
htmlLang: 'fr',
|
htmlLang: 'fr',
|
||||||
calendar: 'gregory',
|
calendar: 'gregory',
|
||||||
|
path: 'fr',
|
||||||
});
|
});
|
||||||
expect(getDefaultLocaleConfig('fr-FR')).toEqual({
|
expect(getDefaultLocaleConfig('fr-FR')).toEqual({
|
||||||
label: 'Français (France)',
|
label: 'Français (France)',
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
htmlLang: 'fr-FR',
|
htmlLang: 'fr-FR',
|
||||||
calendar: 'gregory',
|
calendar: 'gregory',
|
||||||
|
path: 'fr-FR',
|
||||||
});
|
});
|
||||||
expect(getDefaultLocaleConfig('en')).toEqual({
|
expect(getDefaultLocaleConfig('en')).toEqual({
|
||||||
label: 'English',
|
label: 'English',
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
htmlLang: 'en',
|
htmlLang: 'en',
|
||||||
calendar: 'gregory',
|
calendar: 'gregory',
|
||||||
|
path: 'en',
|
||||||
});
|
});
|
||||||
expect(getDefaultLocaleConfig('en-US')).toEqual({
|
expect(getDefaultLocaleConfig('en-US')).toEqual({
|
||||||
label: 'American English',
|
label: 'American English',
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
htmlLang: 'en-US',
|
htmlLang: 'en-US',
|
||||||
calendar: 'gregory',
|
calendar: 'gregory',
|
||||||
|
path: 'en-US',
|
||||||
});
|
});
|
||||||
expect(getDefaultLocaleConfig('zh')).toEqual({
|
expect(getDefaultLocaleConfig('zh')).toEqual({
|
||||||
label: '中文',
|
label: '中文',
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
htmlLang: 'zh',
|
htmlLang: 'zh',
|
||||||
calendar: 'gregory',
|
calendar: 'gregory',
|
||||||
|
path: 'zh',
|
||||||
});
|
});
|
||||||
expect(getDefaultLocaleConfig('zh-CN')).toEqual({
|
expect(getDefaultLocaleConfig('zh-CN')).toEqual({
|
||||||
label: '中文(中国)',
|
label: '中文(中国)',
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
htmlLang: 'zh-CN',
|
htmlLang: 'zh-CN',
|
||||||
calendar: 'gregory',
|
calendar: 'gregory',
|
||||||
|
path: 'zh-CN',
|
||||||
});
|
});
|
||||||
expect(getDefaultLocaleConfig('en-US')).toEqual({
|
expect(getDefaultLocaleConfig('en-US')).toEqual({
|
||||||
label: 'American English',
|
label: 'American English',
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
htmlLang: 'en-US',
|
htmlLang: 'en-US',
|
||||||
calendar: 'gregory',
|
calendar: 'gregory',
|
||||||
|
path: 'en-US',
|
||||||
});
|
});
|
||||||
expect(getDefaultLocaleConfig('fa')).toEqual({
|
expect(getDefaultLocaleConfig('fa')).toEqual({
|
||||||
// cSpell:ignore فارسی
|
// cSpell:ignore فارسی
|
||||||
|
@ -75,6 +82,7 @@ describe('defaultLocaleConfig', () => {
|
||||||
direction: 'rtl',
|
direction: 'rtl',
|
||||||
htmlLang: 'fa',
|
htmlLang: 'fa',
|
||||||
calendar: 'gregory',
|
calendar: 'gregory',
|
||||||
|
path: 'fa',
|
||||||
});
|
});
|
||||||
expect(getDefaultLocaleConfig('fa-IR')).toEqual({
|
expect(getDefaultLocaleConfig('fa-IR')).toEqual({
|
||||||
// cSpell:ignore ایران فارسیا
|
// cSpell:ignore ایران فارسیا
|
||||||
|
@ -82,12 +90,14 @@ describe('defaultLocaleConfig', () => {
|
||||||
direction: 'rtl',
|
direction: 'rtl',
|
||||||
htmlLang: 'fa-IR',
|
htmlLang: 'fa-IR',
|
||||||
calendar: 'gregory',
|
calendar: 'gregory',
|
||||||
|
path: 'fa-IR',
|
||||||
});
|
});
|
||||||
expect(getDefaultLocaleConfig('en-US-u-ca-buddhist')).toEqual({
|
expect(getDefaultLocaleConfig('en-US-u-ca-buddhist')).toEqual({
|
||||||
label: 'American English',
|
label: 'American English',
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
htmlLang: 'en-US-u-ca-buddhist',
|
htmlLang: 'en-US-u-ca-buddhist',
|
||||||
calendar: 'buddhist',
|
calendar: 'buddhist',
|
||||||
|
path: 'en-US-u-ca-buddhist',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -170,6 +180,7 @@ describe('loadI18n', () => {
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
htmlLang: 'fr',
|
htmlLang: 'fr',
|
||||||
calendar: 'gregory',
|
calendar: 'gregory',
|
||||||
|
path: 'fr',
|
||||||
},
|
},
|
||||||
en: getDefaultLocaleConfig('en'),
|
en: getDefaultLocaleConfig('en'),
|
||||||
de: getDefaultLocaleConfig('de'),
|
de: getDefaultLocaleConfig('de'),
|
||||||
|
|
45
packages/docusaurus/src/server/__tests__/index.test.ts
Normal file
45
packages/docusaurus/src/server/__tests__/index.test.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
import {mergeWithCustomize} from 'webpack-merge';
|
||||||
|
import {loadSetup} from './testUtils';
|
||||||
|
import type {Props} from '@docusaurus/types';
|
||||||
|
import type {DeepPartial} from 'utility-types';
|
||||||
|
|
||||||
|
describe('load', () => {
|
||||||
|
it('loads props for site with custom i18n path', async () => {
|
||||||
|
const props = await loadSetup('custom-i18n-site');
|
||||||
|
expect(props).toMatchSnapshot();
|
||||||
|
const props2 = await loadSetup('custom-i18n-site', {locale: 'zh-Hans'});
|
||||||
|
expect(props2).toEqual(
|
||||||
|
mergeWithCustomize<DeepPartial<Props>>({
|
||||||
|
customizeArray(a, b, key) {
|
||||||
|
return ['routesPaths', 'plugins'].includes(key) ? b : undefined;
|
||||||
|
},
|
||||||
|
})(props, {
|
||||||
|
baseUrl: '/zh-Hans/',
|
||||||
|
i18n: {
|
||||||
|
currentLocale: 'zh-Hans',
|
||||||
|
},
|
||||||
|
localizationDir: path.join(
|
||||||
|
__dirname,
|
||||||
|
'__fixtures__/custom-i18n-site/i18n/zh-Hans-custom',
|
||||||
|
),
|
||||||
|
outDir: path.join(
|
||||||
|
__dirname,
|
||||||
|
'__fixtures__/custom-i18n-site/build/zh-Hans',
|
||||||
|
),
|
||||||
|
routesPaths: ['/zh-Hans/404.html'],
|
||||||
|
siteConfig: {
|
||||||
|
baseUrl: '/zh-Hans/',
|
||||||
|
},
|
||||||
|
plugins: props2.plugins,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -6,20 +6,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {load} from '../index';
|
import {load, type LoadContextOptions} from '../index';
|
||||||
import type {Props} from '@docusaurus/types';
|
import type {Props} from '@docusaurus/types';
|
||||||
|
|
||||||
// Helper methods to setup dummy/fake projects.
|
// Helper methods to setup dummy/fake projects.
|
||||||
export default async function loadSetup(name: string): Promise<Props> {
|
export async function loadSetup(
|
||||||
|
name: string,
|
||||||
|
options?: Partial<LoadContextOptions>,
|
||||||
|
): Promise<Props> {
|
||||||
const fixtures = path.join(__dirname, '__fixtures__');
|
const fixtures = path.join(__dirname, '__fixtures__');
|
||||||
const simpleSite = path.join(fixtures, 'simple-site');
|
return load({siteDir: path.join(fixtures, name), ...options});
|
||||||
const customSite = path.join(fixtures, 'custom-site');
|
|
||||||
|
|
||||||
switch (name) {
|
|
||||||
case 'custom':
|
|
||||||
return load({siteDir: customSite});
|
|
||||||
case 'simple':
|
|
||||||
default:
|
|
||||||
return load({siteDir: simpleSite});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,7 @@ const LocaleConfigSchema = Joi.object({
|
||||||
htmlLang: Joi.string(),
|
htmlLang: Joi.string(),
|
||||||
direction: Joi.string().equal('ltr', 'rtl').default('ltr'),
|
direction: Joi.string().equal('ltr', 'rtl').default('ltr'),
|
||||||
calendar: Joi.string(),
|
calendar: Joi.string(),
|
||||||
|
path: Joi.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const I18N_CONFIG_SCHEMA = Joi.object<I18nConfig>({
|
const I18N_CONFIG_SCHEMA = Joi.object<I18nConfig>({
|
||||||
|
|
|
@ -26,6 +26,7 @@ export function getDefaultLocaleConfig(locale: string): I18nLocaleConfig {
|
||||||
htmlLang: locale,
|
htmlLang: locale,
|
||||||
// If the locale name includes -u-ca-xxx the calendar will be defined
|
// If the locale name includes -u-ca-xxx the calendar will be defined
|
||||||
calendar: new Intl.Locale(locale).calendar ?? 'gregory',
|
calendar: new Intl.Locale(locale).calendar ?? 'gregory',
|
||||||
|
path: locale,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,8 +85,11 @@ export async function loadContext(
|
||||||
|
|
||||||
const siteConfig: DocusaurusConfig = {...initialSiteConfig, baseUrl};
|
const siteConfig: DocusaurusConfig = {...initialSiteConfig, baseUrl};
|
||||||
|
|
||||||
// TODO allow customizing localizationDir per-locale
|
const localizationDir = path.resolve(
|
||||||
const localizationDir = path.resolve(siteDir, i18n.path, i18n.currentLocale);
|
siteDir,
|
||||||
|
i18n.path,
|
||||||
|
i18n.localeConfigs[i18n.currentLocale]!.path,
|
||||||
|
);
|
||||||
|
|
||||||
const codeTranslationFileContent =
|
const codeTranslationFileContent =
|
||||||
(await readCodeTranslationFileContent({localizationDir})) ?? {};
|
(await readCodeTranslationFileContent({localizationDir})) ?? {};
|
||||||
|
|
|
@ -8,18 +8,18 @@
|
||||||
import webpack from 'webpack';
|
import webpack from 'webpack';
|
||||||
|
|
||||||
import createClientConfig from '../client';
|
import createClientConfig from '../client';
|
||||||
import loadSetup from '../../server/__tests__/testUtils';
|
import {loadSetup} from '../../server/__tests__/testUtils';
|
||||||
|
|
||||||
describe('webpack dev config', () => {
|
describe('webpack dev config', () => {
|
||||||
it('simple', async () => {
|
it('simple', async () => {
|
||||||
const props = await loadSetup('simple');
|
const props = await loadSetup('simple-site');
|
||||||
const config = await createClientConfig(props);
|
const config = await createClientConfig(props);
|
||||||
const errors = webpack.validate(config);
|
const errors = webpack.validate(config);
|
||||||
expect(errors).toBeUndefined();
|
expect(errors).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('custom', async () => {
|
it('custom', async () => {
|
||||||
const props = await loadSetup('custom');
|
const props = await loadSetup('custom-site');
|
||||||
const config = await createClientConfig(props);
|
const config = await createClientConfig(props);
|
||||||
const errors = webpack.validate(config);
|
const errors = webpack.validate(config);
|
||||||
expect(errors).toBeUndefined();
|
expect(errors).toBeUndefined();
|
||||||
|
|
|
@ -9,12 +9,12 @@ import {jest} from '@jest/globals';
|
||||||
import webpack from 'webpack';
|
import webpack from 'webpack';
|
||||||
|
|
||||||
import createServerConfig from '../server';
|
import createServerConfig from '../server';
|
||||||
import loadSetup from '../../server/__tests__/testUtils';
|
import {loadSetup} from '../../server/__tests__/testUtils';
|
||||||
|
|
||||||
describe('webpack production config', () => {
|
describe('webpack production config', () => {
|
||||||
it('simple', async () => {
|
it('simple', async () => {
|
||||||
jest.spyOn(console, 'log').mockImplementation(() => {});
|
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
const props = await loadSetup('simple');
|
const props = await loadSetup('simple-site');
|
||||||
const config = await createServerConfig({
|
const config = await createServerConfig({
|
||||||
props,
|
props,
|
||||||
onHeadTagsCollected: () => {},
|
onHeadTagsCollected: () => {},
|
||||||
|
@ -26,7 +26,7 @@ describe('webpack production config', () => {
|
||||||
|
|
||||||
it('custom', async () => {
|
it('custom', async () => {
|
||||||
jest.spyOn(console, 'log').mockImplementation(() => {});
|
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
const props = await loadSetup('custom');
|
const props = await loadSetup('custom-site');
|
||||||
const config = await createServerConfig({
|
const config = await createServerConfig({
|
||||||
props,
|
props,
|
||||||
onHeadTagsCollected: () => {},
|
onHeadTagsCollected: () => {},
|
||||||
|
|
|
@ -130,18 +130,21 @@ module.exports = {
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: 'en',
|
defaultLocale: 'en',
|
||||||
locales: ['en', 'fa'],
|
locales: ['en', 'fa'],
|
||||||
|
path: 'i18n',
|
||||||
localeConfigs: {
|
localeConfigs: {
|
||||||
en: {
|
en: {
|
||||||
label: 'English',
|
label: 'English',
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
htmlLang: 'en-US',
|
htmlLang: 'en-US',
|
||||||
calendar: 'gregory',
|
calendar: 'gregory',
|
||||||
|
path: 'en',
|
||||||
},
|
},
|
||||||
fa: {
|
fa: {
|
||||||
label: 'فارسی',
|
label: 'فارسی',
|
||||||
direction: 'rtl',
|
direction: 'rtl',
|
||||||
htmlLang: 'fa-IR',
|
htmlLang: 'fa-IR',
|
||||||
calendar: 'persian',
|
calendar: 'persian',
|
||||||
|
path: 'fa',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -150,11 +153,13 @@ module.exports = {
|
||||||
|
|
||||||
- `defaultLocale`: The locale that (1) does not have its name in the base URL (2) gets started with `docusaurus start` without `--locale` option (3) will be used for the `<link hrefLang="x-default">` tag
|
- `defaultLocale`: The locale that (1) does not have its name in the base URL (2) gets started with `docusaurus start` without `--locale` option (3) will be used for the `<link hrefLang="x-default">` tag
|
||||||
- `locales`: List of locales deployed on your site. Must contain `defaultLocale`.
|
- `locales`: List of locales deployed on your site. Must contain `defaultLocale`.
|
||||||
|
- `path`: Root folder which all locale folders are relative to. Can be absolute or relative to the config file. Defaults to `i18n`.
|
||||||
- `localeConfigs`: Individual options for each locale.
|
- `localeConfigs`: Individual options for each locale.
|
||||||
- `label`: The label displayed for this locale in the locales dropdown.
|
- `label`: The label displayed for this locale in the locales dropdown.
|
||||||
- `direction`: `ltr` (default) or `rtl` (for [right-to-left languages](https://developer.mozilla.org/en-US/docs/Glossary/rtl) like Farsi, Arabic, Hebrew, etc.). Used to select the locale's CSS and HTML meta attribute.
|
- `direction`: `ltr` (default) or `rtl` (for [right-to-left languages](https://developer.mozilla.org/en-US/docs/Glossary/rtl) like Farsi, Arabic, Hebrew, etc.). Used to select the locale's CSS and HTML meta attribute.
|
||||||
- `htmlLang`: BCP 47 language tag to use in `<html lang="...">` and in `<link ... hreflang="...">`
|
- `htmlLang`: BCP 47 language tag to use in `<html lang="...">` and in `<link ... hreflang="...">`
|
||||||
- `calendar`: the [calendar](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar) used to calculate the date era. Note that it doesn't control the actual string displayed: `MM/DD/YYYY` and `DD/MM/YYYY` are both `gregory`. To choose the format (`DD/MM/YYYY` or `MM/DD/YYYY`), set your locale name to `en-GB` or `en-US` (`en` means `en-US`).
|
- `calendar`: the [calendar](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar) used to calculate the date era. Note that it doesn't control the actual string displayed: `MM/DD/YYYY` and `DD/MM/YYYY` are both `gregory`. To choose the format (`DD/MM/YYYY` or `MM/DD/YYYY`), set your locale name to `en-GB` or `en-US` (`en` means `en-US`).
|
||||||
|
- `path`: Root folder that all plugin localization folders of this locale are relative to. Will be resolved against `i18n.path`. Defaults to the locale's name. Note: this has no effect on the locale's `baseUrl`—customization of base URL is a work-in-progress.
|
||||||
|
|
||||||
### `noIndex` {#noIndex}
|
### `noIndex` {#noIndex}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue