From a7b13cd6d8d45c4167e00914f78d5a2430e3b340 Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Tue, 3 Feb 2026 17:23:47 +0100 Subject: [PATCH 1/4] fix(i18n): fallback on missing plural translation --- config/i18n.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/config/i18n.ts b/config/i18n.ts index 9f8c8c4de..cb24241fa 100644 --- a/config/i18n.ts +++ b/config/i18n.ts @@ -206,11 +206,23 @@ const locales: (LocaleObjectData | (Omit & { code: str code: 'uk-UA', file: 'uk-UA.json', name: 'Українська', - pluralRule: (choice: number) => { + pluralRule: (choice: number, choicesLength: number) => { if (choice === 0) return 0 const name = new Intl.PluralRules('uk-UA').select(choice) - return { zero: 0, one: 1, two: 0, few: 2, many: 3, other: 4 }[name] + const plural = { zero: 0, one: 1, two: 0, few: 2, many: 3, other: 4 }[name] + + // In case translation doesn't have all plural forms, use the last available form + if (plural > choicesLength - 1) { + if (import.meta.dev) { + console.warn( + `Plural form index ${plural} for choice ${choice} exceeds available forms ${choicesLength} for locale uk-UA.`, + ) + } + return choicesLength - 1 + } + + return plural }, }, /*{ From 421b4ad437e7416cc44c06624eaaa59b4226ccf7 Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Tue, 3 Feb 2026 17:29:10 +0100 Subject: [PATCH 2/4] fix(i18n): implement createPluralRule function for better plural handling --- config/i18n.ts | 97 +++++++++++++++++++++++++++++++------------------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/config/i18n.ts b/config/i18n.ts index cb24241fa..5e2613cd2 100644 --- a/config/i18n.ts +++ b/config/i18n.ts @@ -78,6 +78,25 @@ export const countryLocaleVariants: Record) { + return (choice: number, choicesLength: number) => { + const name = new Intl.PluralRules(locale).select(choice) + const plural = mapping[name] || 0 + + // In case translation doesn't have all plural forms, use the last available form + if (plural > choicesLength - 1) { + if (import.meta.dev) { + console.warn( + `Plural form index ${plural} for choice ${choice} exceeds available forms ${choicesLength} for locale ${locale}.`, + ) + } + return choicesLength - 1 + } + + return plural + } +} + const locales: (LocaleObjectData | (Omit & { code: string }))[] = [ { code: 'en', @@ -89,10 +108,11 @@ const locales: (LocaleObjectData | (Omit & { code: str file: 'ar.json', name: 'العربية', dir: 'rtl', - pluralRule: (choice: number) => { - const name = new Intl.PluralRules('ar-EG').select(choice) - return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name] - }, + pluralRule: createPluralRule('ar-EG', { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }), + // pluralRule: (choice: number) => { + // const name = new Intl.PluralRules('ar-EG').select(choice) + // return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name] + // }, }, { code: 'az-AZ', @@ -148,10 +168,11 @@ const locales: (LocaleObjectData | (Omit & { code: str code: 'hu-HU', file: 'hu-HU.json', name: 'Magyar', - pluralRule: (choice: number) => { - const name = new Intl.PluralRules('hu-HU').select(choice) - return { zero: 0, one: 0, two: 1, few: 1, many: 1, other: 1 }[name] - }, + pluralRule: createPluralRule('hu-HU', { zero: 0, one: 0, two: 1, few: 1, many: 1, other: 1 }), + // pluralRule: (choice: number) => { + // const name = new Intl.PluralRules('hu-HU').select(choice) + // return { zero: 0, one: 0, two: 1, few: 1, many: 1, other: 1 }[name] + // }, }, { code: 'zh-CN', @@ -197,33 +218,35 @@ const locales: (LocaleObjectData | (Omit & { code: str code: 'ru-RU', file: 'ru-RU.json', name: 'Русский', - pluralRule: (choice: number) => { - const name = new Intl.PluralRules('ru-RU').select(choice) - return { zero: 2, one: 0, two: 1, few: 1, many: 2, other: 3 }[name] - }, + pluralRule: createPluralRule('ru-RU', { zero: 2, one: 0, two: 1, few: 1, many: 2, other: 3 }), + // pluralRule: (choice: number) => { + // const name = new Intl.PluralRules('ru-RU').select(choice) + // return { zero: 2, one: 0, two: 1, few: 1, many: 2, other: 3 }[name] + // }, }, { code: 'uk-UA', file: 'uk-UA.json', name: 'Українська', - pluralRule: (choice: number, choicesLength: number) => { - if (choice === 0) return 0 + pluralRule: createPluralRule('uk-UA', { zero: 0, one: 1, two: 0, few: 2, many: 3, other: 4 }), + // pluralRule: (choice: number, choicesLength: number) => { + // if (choice === 0) return 0 - const name = new Intl.PluralRules('uk-UA').select(choice) - const plural = { zero: 0, one: 1, two: 0, few: 2, many: 3, other: 4 }[name] + // const name = new Intl.PluralRules('uk-UA').select(choice) + // const plural = { zero: 0, one: 1, two: 0, few: 2, many: 3, other: 4 }[name] - // In case translation doesn't have all plural forms, use the last available form - if (plural > choicesLength - 1) { - if (import.meta.dev) { - console.warn( - `Plural form index ${plural} for choice ${choice} exceeds available forms ${choicesLength} for locale uk-UA.`, - ) - } - return choicesLength - 1 - } + // // In case translation doesn't have all plural forms, use the last available form + // if (plural > choicesLength - 1) { + // if (import.meta.dev) { + // console.warn( + // `Plural form index ${plural} for choice ${choice} exceeds available forms ${choicesLength} for locale uk-UA.`, + // ) + // } + // return choicesLength - 1 + // } - return plural - }, + // return plural + // }, }, /*{ code: 'ru-RU', @@ -238,10 +261,11 @@ const locales: (LocaleObjectData | (Omit & { code: str code: 'cs-CZ', file: 'cs-CZ.json', name: 'Čeština', - pluralRule: (choice: number) => { - const name = new Intl.PluralRules('cs-CZ').select(choice) - return { zero: 2, one: 0, two: 1, few: 1, many: 2, other: 2 }[name] - }, + pluralRule: createPluralRule('cs-CZ', { zero: 2, one: 0, two: 1, few: 1, many: 2, other: 2 }), + // pluralRule: (choice: number) => { + // const name = new Intl.PluralRules('cs-CZ').select(choice) + // return { zero: 2, one: 0, two: 1, few: 1, many: 2, other: 2 }[name] + // }, } /* { code: 'pl-PL', @@ -299,12 +323,13 @@ const locales: (LocaleObjectData | (Omit & { code: str code: 'pl-PL', file: 'pl-PL.json', name: 'Polski', - pluralRule: (choice: number) => { - if (choice === 0) return 0 + pluralRule: createPluralRule('pl-PL', { zero: 0, one: 1, two: 0, few: 2, many: 3, other: 4 }), + // pluralRule: (choice: number) => { + // if (choice === 0) return 0 - const name = new Intl.PluralRules('pl-PL').select(choice) - return { zero: 0, one: 1, two: 0, few: 2, many: 3, other: 4 }[name] - }, + // const name = new Intl.PluralRules('pl-PL').select(choice) + // return { zero: 0, one: 1, two: 0, few: 2, many: 3, other: 4 }[name] + // }, }, { code: 'pt-BR', From a2454610fdc40a38450434b5738cd38982e838f7 Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Tue, 3 Feb 2026 17:33:10 +0100 Subject: [PATCH 3/4] chore: cleanup --- config/i18n.ts | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/config/i18n.ts b/config/i18n.ts index 5e2613cd2..7c6f07db5 100644 --- a/config/i18n.ts +++ b/config/i18n.ts @@ -109,10 +109,6 @@ const locales: (LocaleObjectData | (Omit & { code: str name: 'العربية', dir: 'rtl', pluralRule: createPluralRule('ar-EG', { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }), - // pluralRule: (choice: number) => { - // const name = new Intl.PluralRules('ar-EG').select(choice) - // return { zero: 0, one: 1, two: 2, few: 3, many: 4, other: 5 }[name] - // }, }, { code: 'az-AZ', @@ -169,10 +165,6 @@ const locales: (LocaleObjectData | (Omit & { code: str file: 'hu-HU.json', name: 'Magyar', pluralRule: createPluralRule('hu-HU', { zero: 0, one: 0, two: 1, few: 1, many: 1, other: 1 }), - // pluralRule: (choice: number) => { - // const name = new Intl.PluralRules('hu-HU').select(choice) - // return { zero: 0, one: 0, two: 1, few: 1, many: 1, other: 1 }[name] - // }, }, { code: 'zh-CN', @@ -219,34 +211,12 @@ const locales: (LocaleObjectData | (Omit & { code: str file: 'ru-RU.json', name: 'Русский', pluralRule: createPluralRule('ru-RU', { zero: 2, one: 0, two: 1, few: 1, many: 2, other: 3 }), - // pluralRule: (choice: number) => { - // const name = new Intl.PluralRules('ru-RU').select(choice) - // return { zero: 2, one: 0, two: 1, few: 1, many: 2, other: 3 }[name] - // }, }, { code: 'uk-UA', file: 'uk-UA.json', name: 'Українська', pluralRule: createPluralRule('uk-UA', { zero: 0, one: 1, two: 0, few: 2, many: 3, other: 4 }), - // pluralRule: (choice: number, choicesLength: number) => { - // if (choice === 0) return 0 - - // const name = new Intl.PluralRules('uk-UA').select(choice) - // const plural = { zero: 0, one: 1, two: 0, few: 2, many: 3, other: 4 }[name] - - // // In case translation doesn't have all plural forms, use the last available form - // if (plural > choicesLength - 1) { - // if (import.meta.dev) { - // console.warn( - // `Plural form index ${plural} for choice ${choice} exceeds available forms ${choicesLength} for locale uk-UA.`, - // ) - // } - // return choicesLength - 1 - // } - - // return plural - // }, }, /*{ code: 'ru-RU', @@ -262,10 +232,6 @@ const locales: (LocaleObjectData | (Omit & { code: str file: 'cs-CZ.json', name: 'Čeština', pluralRule: createPluralRule('cs-CZ', { zero: 2, one: 0, two: 1, few: 1, many: 2, other: 2 }), - // pluralRule: (choice: number) => { - // const name = new Intl.PluralRules('cs-CZ').select(choice) - // return { zero: 2, one: 0, two: 1, few: 1, many: 2, other: 2 }[name] - // }, } /* { code: 'pl-PL', @@ -324,12 +290,6 @@ const locales: (LocaleObjectData | (Omit & { code: str file: 'pl-PL.json', name: 'Polski', pluralRule: createPluralRule('pl-PL', { zero: 0, one: 1, two: 0, few: 2, many: 3, other: 4 }), - // pluralRule: (choice: number) => { - // if (choice === 0) return 0 - - // const name = new Intl.PluralRules('pl-PL').select(choice) - // return { zero: 0, one: 1, two: 0, few: 2, many: 3, other: 4 }[name] - // }, }, { code: 'pt-BR', From 07ba07852b24a8db51d6c947c817e70324dacc5d Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Tue, 3 Feb 2026 17:35:03 +0100 Subject: [PATCH 4/4] fix: oxlint disable console linting error --- config/i18n.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/config/i18n.ts b/config/i18n.ts index 7c6f07db5..7a5b77d38 100644 --- a/config/i18n.ts +++ b/config/i18n.ts @@ -86,6 +86,7 @@ function createPluralRule(locale: string, mapping: Record) { // In case translation doesn't have all plural forms, use the last available form if (plural > choicesLength - 1) { if (import.meta.dev) { + // oxlint-disable-next-line no-console -- warn logging console.warn( `Plural form index ${plural} for choice ${choice} exceeds available forms ${choicesLength} for locale ${locale}.`, )