diff --git a/CHANGELOG.md b/CHANGELOG.md index 11b830350..884281b8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,7 @@ and this project adheres to - 🐛(frontend) fix redirect after deleting a document #2706 - 🐛(frontend) keep documents draggable with a mouse when zoomed #2727 - 📝(docs) fix markdown typo in installation README #2766 +- 🐛(i18n) export locale correctly #2750 ### Removed diff --git a/crowdin/config.yml b/crowdin/config.yml index 724e321b0..19909fc6c 100644 --- a/crowdin/config.yml +++ b/crowdin/config.yml @@ -23,7 +23,7 @@ files: [ { source: "/frontend/packages/i18n/locales/impress/translations-crowdin.json", dest: "/frontend-impress.json", - translation: "/frontend/packages/i18n/locales/impress/%two_letters_code%/translations.json", + translation: "/frontend/packages/i18n/locales/impress/%locale%/translations.json", skip_untranslated_strings: true, }, ] diff --git a/src/frontend/packages/i18n/__tests__/i18n.test.ts b/src/frontend/packages/i18n/__tests__/i18n.test.ts index 98e45e250..2041004ad 100644 --- a/src/frontend/packages/i18n/__tests__/i18n.test.ts +++ b/src/frontend/packages/i18n/__tests__/i18n.test.ts @@ -60,6 +60,36 @@ describe('integration testing on i18n package', () => { }); }); + test('cmd format-deploy keeps the region only when locales collide', () => { + // To be sure the tests folder is not here + fs.rmSync('./locales/tests', { recursive: true, force: true }); + + const locales = { + en: 'My test', + 'fr-FR': 'Mon test', + 'zh-CN': 'įŽ€', + 'zh-TW': 'įš', + }; + Object.entries(locales).forEach(([locale, message]) => { + fs.mkdirSync(`./locales/tests/${locale}/`, { recursive: true }); + fs.writeFileSync( + `./locales/tests/${locale}/translations.json`, + JSON.stringify({ test: { message } }), + 'utf8', + ); + }); + + const output = './locales/tests/translations.json'; + execSync(`node ./format-deploy.mjs --app=tests --output=${output}`); + const json = JSON.parse(fs.readFileSync(output, 'utf8')); + expect(json).toEqual({ + en: { translation: { test: 'My test' } }, + fr: { translation: { test: 'Mon test' } }, + 'zh-cn': { translation: { test: 'įŽ€' } }, + 'zh-tw': { translation: { test: 'įš' } }, + }); + }); + test('cmd format-deploy throws an error when translation file is not found', () => { // To be sure the tests folder is not here fs.rmSync('./locales/tests', { recursive: true, force: true }); diff --git a/src/frontend/packages/i18n/format-deploy.mjs b/src/frontend/packages/i18n/format-deploy.mjs index ccd3ee382..08c618a55 100644 --- a/src/frontend/packages/i18n/format-deploy.mjs +++ b/src/frontend/packages/i18n/format-deploy.mjs @@ -11,13 +11,31 @@ const folderPath = './locales/' + app; const namefile = 'translations.json'; const jsonI18n = {}; -// Fetch the files in the locales folder -fs.readdirSync(folderPath).map((language) => { - const languagePath = path.join(folderPath, path.sep, language); +// Crowdin outputs one folder per locale (fr-FR, zh-CN, zh-TW, en...) +const locales = fs.readdirSync(folderPath).filter((entry) => { // Crowdin output file in folder, we want to treat only these ones - if (!fs.lstatSync(languagePath).isDirectory()) { - return; - } + return fs.lstatSync(path.join(folderPath, entry)).isDirectory(); +}); + +const getLanguage = (locale) => locale.split(/[-_]/)[0].toLowerCase(); + +const localesPerLanguage = {}; +locales.forEach((locale) => { + const language = getLanguage(locale); + localesPerLanguage[language] = (localesPerLanguage[language] ?? 0) + 1; +}); + +// The language alone (fr) is enough when only one locale of it is translated, +// the region is kept (zh-cn, zh-tw) when the locales would otherwise collide. +const getKey = (locale) => + localesPerLanguage[getLanguage(locale)] > 1 + ? locale.replace('_', '-').toLowerCase() + : getLanguage(locale); + +// Fetch the files in the locales folder +locales.forEach((locale) => { + const language = getKey(locale); + const languagePath = path.join(folderPath, path.sep, locale); jsonI18n[language] = { translation: {},