From 5cfb046816c124b1747ae9449838205e6c1e6bc4 Mon Sep 17 00:00:00 2001 From: Kerry Date: Mon, 10 Jan 2022 14:54:57 +0100 Subject: [PATCH] fallback properly with pluralized strings (#7495) Signed-off-by: Kerry Archibald --- src/languageHandler.tsx | 7 +- test/i18n-test/languageHandler-test.tsx | 88 ++++++++++++++++++------- 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 7e4fc095e2..e1bdec9649 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -41,6 +41,7 @@ counterpart.setSeparator('|'); // see `translateWithFallback` for an explanation of fallback handling const FALLBACK_LOCALE = 'en'; +counterpart.setFallbackLocale(FALLBACK_LOCALE); interface ITranslatableError extends Error { translatedMessage: string; @@ -79,12 +80,12 @@ export function _td(s: string): string { // eslint-disable-line @typescript-esli * should be wrapped with an appropriate `lang='en'` attribute * counterpart's `translate` doesn't expose a way to determine if the resulting translation * is in the target locale or a fallback locale - * for this reason, we do not set a fallback via `counterpart.setFallbackLocale` + * for this reason, force fallbackLocale === locale in the first call to translate * and fallback 'manually' so we can mark fallback strings appropriately * */ const translateWithFallback = (text: string, options?: object): { translated?: string, isFallback?: boolean } => { - const translated = counterpart.translate(text, options); - if (/^missing translation:/.test(translated)) { + const translated = counterpart.translate(text, { ...options, fallbackLocale: counterpart.getLocale() }); + if (!translated || /^missing translation:/.test(translated)) { const fallbackTranslated = counterpart.translate(text, { ...options, locale: FALLBACK_LOCALE }); return { translated: fallbackTranslated, isFallback: true }; } diff --git a/test/i18n-test/languageHandler-test.tsx b/test/i18n-test/languageHandler-test.tsx index 668ff5ba1b..b2349f7d1d 100644 --- a/test/i18n-test/languageHandler-test.tsx +++ b/test/i18n-test/languageHandler-test.tsx @@ -118,7 +118,7 @@ describe('languageHandler', function() { }); }); - describe('when a translation string does not exist in active language', () => { + describe('for a non-en language', () => { beforeEach(async () => { stubClient(); await setLanguage('lv'); @@ -130,33 +130,75 @@ describe('languageHandler', function() { setMissingEntryGenerator(counterpartDefaultMissingEntryGen); }); + // mocked lv has only `"Uploading %(filename)s and %(count)s others|one"` const lvExistingPlural = 'Uploading %(filename)s and %(count)s others'; + const lvNonExistingPlural = '%(spaceName)s and %(count)s others'; - // lv does not have a pluralizer function - const noPluralizerCase = [ - 'handles plural strings when no pluralizer exists for language', - lvExistingPlural, - { count: 1, filename: 'test.txt' }, - undefined, - 'Uploading test.txt and 1 other', - ] as TestCase; + describe('pluralization', () => { + const pluralCases = [ + [ + 'falls back when plural string exists but not for for count', + lvExistingPlural, + { count: 2, filename: 'test.txt' }, + undefined, + 'Uploading test.txt and 2 others', + ], + [ + 'falls back when plural string does not exists at all', + lvNonExistingPlural, + { count: 2, spaceName: 'test' }, + undefined, + 'test and 2 others', + ], + ] as TestCase[]; - describe('_t', () => { - it.each([...testCasesEn, noPluralizerCase])( - "%s and translates with fallback locale", - async (_d, translationString, variables, tags, result) => { - expect(_t(translationString, variables, tags)).toEqual(result); - }, - ); + describe('_t', () => { + it('translated correctly when plural string exists for count', () => { + expect(_t( + lvExistingPlural, + { count: 1, filename: 'test.txt' }, undefined)).toEqual('Качване на test.txt и 1 друг'); + }); + it.each(pluralCases)( + "%s", + async (_d, translationString, variables, tags, result) => { + expect(_t(translationString, variables, tags)).toEqual(result); + }, + ); + }); + + describe('_tDom()', () => { + it('translated correctly when plural string exists for count', () => { + expect(_tDom( + lvExistingPlural, + { count: 1, filename: 'test.txt' }, undefined)).toEqual('Качване на test.txt и 1 друг'); + }); + it.each(pluralCases)( + "%s and translates with fallback locale, attributes fallback locale", + async (_d, translationString, variables, tags, result) => { + expect(_tDom(translationString, variables, tags)).toEqual({ result }); + }, + ); + }); }); - describe('_tDom()', () => { - it.each([...testCasesEn, noPluralizerCase])( - "%s and translates with fallback locale, attributes fallback locale", - async (_d, translationString, variables, tags, result) => { - expect(_tDom(translationString, variables, tags)).toEqual({ result }); - }, - ); + describe('when a translation string does not exist in active language', () => { + describe('_t', () => { + it.each(testCasesEn)( + "%s and translates with fallback locale", + async (_d, translationString, variables, tags, result) => { + expect(_t(translationString, variables, tags)).toEqual(result); + }, + ); + }); + + describe('_tDom()', () => { + it.each(testCasesEn)( + "%s and translates with fallback locale, attributes fallback locale", + async (_d, translationString, variables, tags, result) => { + expect(_tDom(translationString, variables, tags)).toEqual({ result }); + }, + ); + }); }); }); });