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 367872215282..b02b620456c4 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts @@ -26,6 +26,7 @@ const DefaultI18N: I18n = { direction: 'ltr', htmlLang: 'en', calendar: 'gregory', + path: 'en', }, }, }; 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 4ec710ce0a39..5107dc2f88df 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -54,6 +54,7 @@ function getI18n(locale: string): I18n { label: locale, htmlLang: locale, direction: 'ltr', + path: locale, }, }, }; @@ -71,7 +72,11 @@ const getPlugin = async ( i18n: I18n = DefaultI18N, ) => { 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 = { title: 'Hello', baseUrl: '/', diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/site-with-custom-i18n-path/docs/category1/doc1.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/site-with-custom-i18n-path/docs/category1/doc1.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/site-with-custom-i18n-path/docs/category2/doc2.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/site-with-custom-i18n-path/docs/category2/doc2.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/site-with-custom-i18n-path/i18n-custom/zh-Hans-custom/docusaurus-plugin-content-docs/current/category1/doc1.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/site-with-custom-i18n-path/i18n-custom/zh-Hans-custom/docusaurus-plugin-content-docs/current/category1/doc1.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/site-with-custom-i18n-path/i18n-custom/zh-Hans-custom/docusaurus-plugin-content-docs/current/category2/doc2.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/site-with-custom-i18n-path/i18n-custom/zh-Hans-custom/docusaurus-plugin-content-docs/current/category2/doc2.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/site-with-custom-i18n-path/sidebars.json b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/site-with-custom-i18n-path/sidebars.json new file mode 100644 index 000000000000..9441eff999e7 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/site-with-custom-i18n-path/sidebars.json @@ -0,0 +1 @@ +[{ "type": "autogenerated", "dirName": "." }] diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap index 668d5277e941..edff61da5b8b 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap @@ -66,3 +66,12 @@ exports[`docsVersion second docs instance versioning 1`] = ` ], } `; + +exports[`docsVersion works with custom i18n paths 1`] = ` +[ + { + "dirName": ".", + "type": "autogenerated", + }, +] +`; 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 d9ed52eea89d..643642b76163 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 1818a3f4e22e..23cd26e7e461 100644 --- a/packages/docusaurus-plugin-content-docs/src/cli.ts +++ b/packages/docusaurus-plugin-content-docs/src/cli.ts @@ -85,9 +85,11 @@ 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); + const localizationDir = path.resolve( + siteDir, + i18n.path, + i18n.localeConfigs[locale]!.path, + ); // Copy docs files. const docsDir = locale === i18n.defaultLocale diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index ced9b8bf6fe1..3b74be40730f 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -61,6 +61,12 @@ export type I18nLocaleConfig = { * or `en-US` (`en` means `en-US`). */ 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 = { diff --git a/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts b/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts index c0b45f358568..55c0b52e7c47 100644 --- a/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts @@ -123,7 +123,7 @@ describe('localizePath', () => { path: 'i18n', locales: ['en', 'fr'], currentLocale: 'fr', - localeConfigs: {}, + localeConfigs: {fr: {path: 'fr'}, en: {path: 'en'}}, }, options: {localizePath: true}, }), @@ -140,7 +140,7 @@ describe('localizePath', () => { path: 'i18n', locales: ['en', 'fr'], currentLocale: 'en', - localeConfigs: {}, + localeConfigs: {fr: {path: 'fr'}, en: {path: 'en'}}, }, options: {localizePath: true}, }), @@ -157,7 +157,7 @@ describe('localizePath', () => { path: 'i18n', locales: ['en', 'fr'], currentLocale: 'en', - localeConfigs: {}, + localeConfigs: {fr: {path: 'fr'}, en: {path: 'en'}}, }, }), ).toBe('/baseUrl/'); @@ -173,7 +173,7 @@ describe('localizePath', () => { path: 'i18n', locales: ['en', 'fr'], currentLocale: 'en', - localeConfigs: {}, + localeConfigs: {fr: {path: 'fr'}, en: {path: 'en'}}, }, }), ).toBe('/baseUrl/'); diff --git a/packages/docusaurus-utils/src/i18nUtils.ts b/packages/docusaurus-utils/src/i18nUtils.ts index 8c706f1b8ce7..15723648d72a 100644 --- a/packages/docusaurus-utils/src/i18nUtils.ts +++ b/packages/docusaurus-utils/src/i18nUtils.ts @@ -68,6 +68,9 @@ export function getPluginI18nPath({ /** * Takes a path and returns a localized a version (which is basically `path + * 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({ pathType, @@ -94,13 +97,15 @@ export function localizePath({ }; }): string { const shouldLocalizePath: boolean = - // options.localizePath ?? i18n.currentLocale !== i18n.defaultLocale; if (!shouldLocalizePath) { 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') { return path.join(originalPath, i18n.currentLocale); } diff --git a/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/docusaurus.config.js b/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/docusaurus.config.js new file mode 100644 index 000000000000..4fea46c9bf0d --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/docusaurus.config.js @@ -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' + } + } + } +}; diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/index.test.ts.snap new file mode 100644 index 000000000000..fb73309cd272 --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/index.test.ts.snap @@ -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": "/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": "/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/i18n/en-custom", + "outDir": "/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": "/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": "/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site/docusaurus.config.js", + "siteDir": "/packages/docusaurus/src/server/__tests__/__fixtures__/custom-i18n-site", + "siteMetadata": { + "docusaurusVersion": "", + "pluginVersions": {}, + "siteVersion": undefined, + }, +} +`; diff --git a/packages/docusaurus/src/server/__tests__/i18n.test.ts b/packages/docusaurus/src/server/__tests__/i18n.test.ts index 8ef8fea9faf3..e846f942f527 100644 --- a/packages/docusaurus/src/server/__tests__/i18n.test.ts +++ b/packages/docusaurus/src/server/__tests__/i18n.test.ts @@ -32,42 +32,49 @@ describe('defaultLocaleConfig', () => { direction: 'ltr', htmlLang: 'fr', calendar: 'gregory', + path: 'fr', }); expect(getDefaultLocaleConfig('fr-FR')).toEqual({ label: 'Français (France)', direction: 'ltr', htmlLang: 'fr-FR', calendar: 'gregory', + path: 'fr-FR', }); expect(getDefaultLocaleConfig('en')).toEqual({ label: 'English', direction: 'ltr', htmlLang: 'en', calendar: 'gregory', + path: 'en', }); expect(getDefaultLocaleConfig('en-US')).toEqual({ label: 'American English', direction: 'ltr', htmlLang: 'en-US', calendar: 'gregory', + path: 'en-US', }); expect(getDefaultLocaleConfig('zh')).toEqual({ label: '中文', direction: 'ltr', htmlLang: 'zh', calendar: 'gregory', + path: 'zh', }); expect(getDefaultLocaleConfig('zh-CN')).toEqual({ label: '中文(中国)', direction: 'ltr', htmlLang: 'zh-CN', calendar: 'gregory', + path: 'zh-CN', }); expect(getDefaultLocaleConfig('en-US')).toEqual({ label: 'American English', direction: 'ltr', htmlLang: 'en-US', calendar: 'gregory', + path: 'en-US', }); expect(getDefaultLocaleConfig('fa')).toEqual({ // cSpell:ignore فارسی @@ -75,6 +82,7 @@ describe('defaultLocaleConfig', () => { direction: 'rtl', htmlLang: 'fa', calendar: 'gregory', + path: 'fa', }); expect(getDefaultLocaleConfig('fa-IR')).toEqual({ // cSpell:ignore ایران فارسیا @@ -82,12 +90,14 @@ describe('defaultLocaleConfig', () => { direction: 'rtl', htmlLang: 'fa-IR', calendar: 'gregory', + path: 'fa-IR', }); expect(getDefaultLocaleConfig('en-US-u-ca-buddhist')).toEqual({ label: 'American English', direction: 'ltr', htmlLang: 'en-US-u-ca-buddhist', calendar: 'buddhist', + path: 'en-US-u-ca-buddhist', }); }); }); @@ -170,6 +180,7 @@ describe('loadI18n', () => { direction: 'ltr', htmlLang: 'fr', calendar: 'gregory', + path: 'fr', }, en: getDefaultLocaleConfig('en'), de: getDefaultLocaleConfig('de'), diff --git a/packages/docusaurus/src/server/__tests__/index.test.ts b/packages/docusaurus/src/server/__tests__/index.test.ts new file mode 100644 index 000000000000..28bdc3f265e3 --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/index.test.ts @@ -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>({ + 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, + }), + ); + }); +}); diff --git a/packages/docusaurus/src/server/__tests__/testUtils.ts b/packages/docusaurus/src/server/__tests__/testUtils.ts index 1d42d255f9e8..c295f05dad51 100644 --- a/packages/docusaurus/src/server/__tests__/testUtils.ts +++ b/packages/docusaurus/src/server/__tests__/testUtils.ts @@ -6,20 +6,14 @@ */ import path from 'path'; -import {load} from '../index'; +import {load, type LoadContextOptions} from '../index'; import type {Props} from '@docusaurus/types'; // Helper methods to setup dummy/fake projects. -export default async function loadSetup(name: string): Promise { +export async function loadSetup( + name: string, + options?: Partial, +): Promise { const fixtures = path.join(__dirname, '__fixtures__'); - const simpleSite = path.join(fixtures, 'simple-site'); - const customSite = path.join(fixtures, 'custom-site'); - - switch (name) { - case 'custom': - return load({siteDir: customSite}); - case 'simple': - default: - return load({siteDir: simpleSite}); - } + return load({siteDir: path.join(fixtures, name), ...options}); } diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index 4027f84ca4d9..d63749680487 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -135,6 +135,7 @@ const LocaleConfigSchema = Joi.object({ htmlLang: Joi.string(), direction: Joi.string().equal('ltr', 'rtl').default('ltr'), calendar: Joi.string(), + path: Joi.string(), }); const I18N_CONFIG_SCHEMA = Joi.object({ diff --git a/packages/docusaurus/src/server/i18n.ts b/packages/docusaurus/src/server/i18n.ts index 59b7b85a38f1..92292f2ec5f7 100644 --- a/packages/docusaurus/src/server/i18n.ts +++ b/packages/docusaurus/src/server/i18n.ts @@ -26,6 +26,7 @@ export function getDefaultLocaleConfig(locale: string): I18nLocaleConfig { htmlLang: locale, // If the locale name includes -u-ca-xxx the calendar will be defined calendar: new Intl.Locale(locale).calendar ?? 'gregory', + path: locale, }; } diff --git a/packages/docusaurus/src/server/index.ts b/packages/docusaurus/src/server/index.ts index c0db7139ea61..914752004715 100644 --- a/packages/docusaurus/src/server/index.ts +++ b/packages/docusaurus/src/server/index.ts @@ -85,8 +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 localizationDir = path.resolve( + siteDir, + i18n.path, + i18n.localeConfigs[i18n.currentLocale]!.path, + ); const codeTranslationFileContent = (await readCodeTranslationFileContent({localizationDir})) ?? {}; diff --git a/packages/docusaurus/src/webpack/__tests__/client.test.ts b/packages/docusaurus/src/webpack/__tests__/client.test.ts index 87c825eb9802..f4dd21733779 100644 --- a/packages/docusaurus/src/webpack/__tests__/client.test.ts +++ b/packages/docusaurus/src/webpack/__tests__/client.test.ts @@ -8,18 +8,18 @@ import webpack from 'webpack'; import createClientConfig from '../client'; -import loadSetup from '../../server/__tests__/testUtils'; +import {loadSetup} from '../../server/__tests__/testUtils'; describe('webpack dev config', () => { it('simple', async () => { - const props = await loadSetup('simple'); + const props = await loadSetup('simple-site'); const config = await createClientConfig(props); const errors = webpack.validate(config); expect(errors).toBeUndefined(); }); it('custom', async () => { - const props = await loadSetup('custom'); + const props = await loadSetup('custom-site'); const config = await createClientConfig(props); const errors = webpack.validate(config); expect(errors).toBeUndefined(); diff --git a/packages/docusaurus/src/webpack/__tests__/server.test.ts b/packages/docusaurus/src/webpack/__tests__/server.test.ts index f3207fdc2fe2..7d7c57cfab48 100644 --- a/packages/docusaurus/src/webpack/__tests__/server.test.ts +++ b/packages/docusaurus/src/webpack/__tests__/server.test.ts @@ -9,12 +9,12 @@ import {jest} from '@jest/globals'; import webpack from 'webpack'; import createServerConfig from '../server'; -import loadSetup from '../../server/__tests__/testUtils'; +import {loadSetup} from '../../server/__tests__/testUtils'; describe('webpack production config', () => { it('simple', async () => { jest.spyOn(console, 'log').mockImplementation(() => {}); - const props = await loadSetup('simple'); + const props = await loadSetup('simple-site'); const config = await createServerConfig({ props, onHeadTagsCollected: () => {}, @@ -26,7 +26,7 @@ describe('webpack production config', () => { it('custom', async () => { jest.spyOn(console, 'log').mockImplementation(() => {}); - const props = await loadSetup('custom'); + const props = await loadSetup('custom-site'); const config = await createServerConfig({ props, onHeadTagsCollected: () => {}, diff --git a/website/docs/api/docusaurus.config.js.md b/website/docs/api/docusaurus.config.js.md index 45142e79ed64..82a9275ad598 100644 --- a/website/docs/api/docusaurus.config.js.md +++ b/website/docs/api/docusaurus.config.js.md @@ -130,18 +130,21 @@ module.exports = { i18n: { defaultLocale: 'en', locales: ['en', 'fa'], + path: 'i18n', localeConfigs: { en: { label: 'English', direction: 'ltr', htmlLang: 'en-US', calendar: 'gregory', + path: 'en', }, fa: { label: 'فارسی', direction: 'rtl', htmlLang: 'fa-IR', 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 `` tag - `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. - `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. - `htmlLang`: BCP 47 language tag to use in `` and in `` - `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}