From b743edf5fbde8f954633388f41eb186dc9e41f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Wed, 7 Apr 2021 18:28:48 +0200 Subject: [PATCH] feat(v2): default theme translations: locale "pt" should load "pt-BR" translations (#4581) * code translation utils => locale "pt" should attempt to load "pt-BR" (not "pt-PT") * useless async test --- .../{fr_FR.json => fr-FR.json} | 0 .../__tests__/codeTranslationsUtils.test.ts | 112 ++++++++++++++++++ .../src/__tests__/index.test.ts | 88 -------------- .../src/codeTranslationsUtils.ts | 56 +++++++++ packages/docusaurus-utils/src/index.ts | 35 +----- 5 files changed, 170 insertions(+), 121 deletions(-) rename packages/docusaurus-utils/src/__tests__/__fixtures__/defaultCodeTranslations/{fr_FR.json => fr-FR.json} (100%) create mode 100644 packages/docusaurus-utils/src/__tests__/codeTranslationsUtils.test.ts create mode 100644 packages/docusaurus-utils/src/codeTranslationsUtils.ts diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/defaultCodeTranslations/fr_FR.json b/packages/docusaurus-utils/src/__tests__/__fixtures__/defaultCodeTranslations/fr-FR.json similarity index 100% rename from packages/docusaurus-utils/src/__tests__/__fixtures__/defaultCodeTranslations/fr_FR.json rename to packages/docusaurus-utils/src/__tests__/__fixtures__/defaultCodeTranslations/fr-FR.json diff --git a/packages/docusaurus-utils/src/__tests__/codeTranslationsUtils.test.ts b/packages/docusaurus-utils/src/__tests__/codeTranslationsUtils.test.ts new file mode 100644 index 0000000000..ffb7b0b62c --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/codeTranslationsUtils.test.ts @@ -0,0 +1,112 @@ +/** + * 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 fs from 'fs-extra'; +import { + codeTranslationLocalesToTry, + readDefaultCodeTranslationMessages, +} from '../codeTranslationsUtils'; + +describe('codeTranslationLocalesToTry', () => { + test('should return appropriate locale lists', () => { + expect(codeTranslationLocalesToTry('fr')).toEqual(['fr', 'fr-FR']); + expect(codeTranslationLocalesToTry('fr-FR')).toEqual(['fr-FR', 'fr']); + // Note: "pt" is expanded into "pt-BR", not "pt-PT", as "pt-BR" is more widely used! + // See https://github.com/facebook/docusaurus/pull/4536#issuecomment-810088783 + expect(codeTranslationLocalesToTry('pt')).toEqual(['pt', 'pt-BR']); + expect(codeTranslationLocalesToTry('pt-BR')).toEqual(['pt-BR', 'pt']); + expect(codeTranslationLocalesToTry('pt-PT')).toEqual(['pt-PT', 'pt']); + }); +}); + +describe('readDefaultCodeTranslationMessages', () => { + const dirPath = path.resolve( + __dirname, + '__fixtures__', + 'defaultCodeTranslations', + ); + + async function readAsJSON(filename: string) { + return JSON.parse( + await fs.readFile(path.resolve(dirPath, filename), 'utf8'), + ); + } + + test('for empty locale', async () => { + await expect( + readDefaultCodeTranslationMessages({ + locale: '', + dirPath, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"First argument to Intl.Locale constructor can't be empty or missing"`, + ); + }); + + test('for unexisting locale', async () => { + await expect( + readDefaultCodeTranslationMessages({ + locale: 'es', + dirPath, + }), + ).resolves.toEqual({}); + }); + + test('for fr but bad folder', async () => { + await expect( + readDefaultCodeTranslationMessages({ + locale: 'fr', + dirPath: __dirname, + }), + ).resolves.toEqual({}); + }); + + test('for fr', async () => { + await expect( + readDefaultCodeTranslationMessages({ + locale: 'fr', + dirPath, + }), + ).resolves.toEqual(await readAsJSON('fr.json')); + }); + + test('for fr-FR', async () => { + await expect( + readDefaultCodeTranslationMessages({ + locale: 'fr-FR', + dirPath, + }), + ).resolves.toEqual(await readAsJSON('fr-FR.json')); + }); + + test('for en', async () => { + await expect( + readDefaultCodeTranslationMessages({ + locale: 'en', + dirPath, + }), + ).resolves.toEqual(await readAsJSON('en.json')); + }); + + test('for en-US', async () => { + await expect( + readDefaultCodeTranslationMessages({ + locale: 'en-US', + dirPath, + }), + ).resolves.toEqual(await readAsJSON('en.json')); + }); + + test('for en-WHATEVER', async () => { + await expect( + readDefaultCodeTranslationMessages({ + locale: 'en-WHATEVER', + dirPath, + }), + ).resolves.toEqual(await readAsJSON('en.json')); + }); +}); diff --git a/packages/docusaurus-utils/src/__tests__/index.test.ts b/packages/docusaurus-utils/src/__tests__/index.test.ts index 0b24c87052..1f9881c2e5 100644 --- a/packages/docusaurus-utils/src/__tests__/index.test.ts +++ b/packages/docusaurus-utils/src/__tests__/index.test.ts @@ -6,7 +6,6 @@ */ import path from 'path'; -import fs from 'fs-extra'; import { fileToPath, simpleHash, @@ -34,7 +33,6 @@ import { findFolderContainingFile, getFolderContainingFile, updateTranslationFileMessages, - readDefaultCodeTranslationMessages, parseMarkdownHeadingId, } from '../index'; import {sum} from 'lodash'; @@ -722,92 +720,6 @@ describe('updateTranslationFileMessages', () => { }); }); -describe('readDefaultCodeTranslationMessages', () => { - const dirPath = path.resolve( - __dirname, - '__fixtures__', - 'defaultCodeTranslations', - ); - - async function readAsJSON(filename: string) { - return JSON.parse( - await fs.readFile(path.resolve(dirPath, filename), 'utf8'), - ); - } - - test('for empty locale', async () => { - await expect( - readDefaultCodeTranslationMessages({ - locale: '', - dirPath, - }), - ).resolves.toEqual({}); - }); - - test('for unexisting locale', async () => { - await expect( - readDefaultCodeTranslationMessages({ - locale: 'es', - dirPath, - }), - ).resolves.toEqual({}); - }); - - test('for fr but bad folder', async () => { - await expect( - readDefaultCodeTranslationMessages({ - locale: '', - dirPath: __dirname, - }), - ).resolves.toEqual({}); - }); - - test('for fr', async () => { - await expect( - readDefaultCodeTranslationMessages({ - locale: 'fr', - dirPath, - }), - ).resolves.toEqual(await readAsJSON('fr.json')); - }); - - test('for fr_FR', async () => { - await expect( - readDefaultCodeTranslationMessages({ - locale: 'fr_FR', - dirPath, - }), - ).resolves.toEqual(await readAsJSON('fr_FR.json')); - }); - - test('for en', async () => { - await expect( - readDefaultCodeTranslationMessages({ - locale: 'en', - dirPath, - }), - ).resolves.toEqual(await readAsJSON('en.json')); - }); - - test('for en_US', async () => { - await expect( - readDefaultCodeTranslationMessages({ - locale: 'en_US', - dirPath, - }), - ).resolves.toEqual(await readAsJSON('en.json')); - }); - - test('for en_WHATEVER', async () => { - await expect( - readDefaultCodeTranslationMessages({ - locale: 'en_WHATEVER', - dirPath, - }), - ).resolves.toEqual(await readAsJSON('en.json')); - }); -}); - describe('parseMarkdownHeadingId', () => { test('can parse simple heading without id', () => { expect(parseMarkdownHeadingId('## Some heading')).toEqual({ diff --git a/packages/docusaurus-utils/src/codeTranslationsUtils.ts b/packages/docusaurus-utils/src/codeTranslationsUtils.ts new file mode 100644 index 0000000000..b908d380df --- /dev/null +++ b/packages/docusaurus-utils/src/codeTranslationsUtils.ts @@ -0,0 +1,56 @@ +/** + * 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 fs from 'fs-extra'; + +// Return an ordered list of locales we should try +export function codeTranslationLocalesToTry(locale: string): string[] { + // @ts-expect-error: TODO until available in TS, see https://github.com/microsoft/TypeScript/issues/37326 + const intlLocale = Intl.Locale ? new Intl.Locale(locale) : undefined; + if (!intlLocale) { + return [locale]; + } + // if locale is just a simple language like "pt", we want to fallback to pt-BR (not pt-PT!) + // see https://github.com/facebook/docusaurus/pull/4536#issuecomment-810088783 + if (intlLocale.language === locale) { + const maximizedLocale = intlLocale.maximize(); // pt-Latn-BR` + // ["pt","pt-BR"] + return [locale, `${maximizedLocale.language}-${maximizedLocale.region}`]; + } + // if locale is like "pt-BR", we want to fallback to "pt" + else { + return [locale, intlLocale.language]; + } +} + +// Useful to implement getDefaultCodeTranslationMessages() in themes +export async function readDefaultCodeTranslationMessages({ + dirPath, + locale, +}: { + dirPath: string; + locale: string; +}): Promise> { + const localesToTry = codeTranslationLocalesToTry(locale); + + // Return the content of the first file that match + // fr_FR.json => fr.json => nothing + // eslint-disable-next-line no-restricted-syntax + for (const fileName of localesToTry) { + const filePath = path.resolve(dirPath, `${fileName}.json`); + + // eslint-disable-next-line no-await-in-loop + if (await fs.pathExists(filePath)) { + // eslint-disable-next-line no-await-in-loop + const fileContent = await fs.readFile(filePath, 'utf8'); + return JSON.parse(fileContent); + } + } + + return {}; +} diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index 35b0df6b31..d2ebb90b84 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -22,6 +22,8 @@ import { // @ts-expect-error: no typedefs :s import resolvePathnameUnsafe from 'resolve-pathname'; +export * from './codeTranslationsUtils'; + const fileHash = new Map(); export async function generate( generatedFilesDir: string, @@ -642,39 +644,6 @@ export function updateTranslationFileMessages( }; } -export async function readDefaultCodeTranslationMessages({ - dirPath, - locale, -}: { - dirPath: string; - locale: string; -}): Promise> { - const fileNamesToTry = [locale]; - - if (locale.includes('_')) { - const language = locale.split('_')[0]; - if (language) { - fileNamesToTry.push(language); - } - } - - // Return the content of the first file that match - // fr_FR.json => fr.json => nothing - // eslint-disable-next-line no-restricted-syntax - for (const fileName of fileNamesToTry) { - const filePath = path.resolve(dirPath, `${fileName}.json`); - - // eslint-disable-next-line no-await-in-loop - if (await fs.pathExists(filePath)) { - // eslint-disable-next-line no-await-in-loop - const fileContent = await fs.readFile(filePath, 'utf8'); - return JSON.parse(fileContent); - } - } - - return {}; -} - // Input: ## Some heading {#some-heading} // Output: {text: "## Some heading", id: "some-heading"} export function parseMarkdownHeadingId(