diff --git a/locales/rn/et.json b/locales/rn/et.json new file mode 100644 index 00000000..32b5ae5a --- /dev/null +++ b/locales/rn/et.json @@ -0,0 +1,76 @@ +{ + "calendar": { + "detail": { + "delete_confirm": "Kustutada see sündmus?" + } + }, + "contacts": { + "bulk": { + "delete_confirm_title": "Kustuta kontaktid" + }, + "delete_confirm_title": "Kustuta kontakt" + }, + "email_viewer": { + "not_spam_short": "Pole rämpspost", + "spam_short": "Rämpspost" + }, + "files": { + "batch_delete_confirm_message": "Kas oled kindel, et soovid kustutada {count, plural, one {1 üksuse} other {# üksust}}? Seda ei saa tagasi võtta.", + "batch_delete_success": "{count, plural, one {1 üksus kustutatud} other {# üksust kustutatud}}", + "delete_confirm_message": "Kas oled kindel, et soovid kustutada \"{name}\"? Seda ei saa tagasi võtta.", + "delete_confirm_title": "Kustuta ressurss", + "delete_success": "Edukalt kustutatud" + }, + "settings": { + "composer": { + "autosave": { + "1m": "Iga minuti järel", + "2m": "Iga 2 minuti järel", + "30s": "Iga 30 sekundi järel", + "5m": "Iga 5 minuti järel", + "description": "Kui sageli mustandeid automaatselt salvestatakse", + "label": "Automaatsalvestuse intervall" + }, + "description": "Seadista kirja koostamise sätteid", + "title": "Koostamine" + }, + "email_behavior": { + "disable_threading": { + "description": "Kuva e-kirjad üksikute kirjadena vestluste kaupa rühmitamise asemel", + "label": "Keela vestluste rühmitamine" + } + }, + "filters": { + "sieve_editor": { + "confirm_save": "Kinnita salvestamine", + "save_warning": "Salvestamine kirjutab kõik visuaalsed reeglid üle. Seda ei saa tagasi võtta. Kinnitamiseks klõpsa uuesti nuppu Salvesta." + } + }, + "identities": { + "title": "Saatja identiteedid" + }, + "notifications": { + "calendar": { + "invitation_parsing": "Tuvasta e-kirjade kutsed", + "invitation_parsing_desc": "Tuvasta kalendrikutsed e-kirjade manustest ja kuva kalenditoimingud" + } + }, + "tab_groups": { + "advanced": "Täpsemalt", + "appearance": "Välimus", + "general": "Üldine", + "privacy": "Privaatsus ja turvalisus" + }, + "tabs": { + "content_senders": "Sisu ja saatjad", + "downloads": "Allalaadimised", + "identities": "Identiteedid", + "keywords": "Sildid", + "language": "Keel, piirkond ja kellaaeg", + "layout": "Paigutus", + "sidebar_apps": "Külgriba rakendused", + "templates": "Mallid", + "themes": "Teemad" + } + } +} diff --git a/locales/rn/th.json b/locales/rn/th.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/locales/rn/th.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n/__tests__/locale.test.ts b/src/i18n/__tests__/locale.test.ts index 7c4e7bde..acbb4b07 100644 --- a/src/i18n/__tests__/locale.test.ts +++ b/src/i18n/__tests__/locale.test.ts @@ -23,6 +23,11 @@ describe('resolveLocaleTag', () => { expect(resolveLocaleTag({ languageCode: 'nb', languageTag: 'nb-NO' })).toBe('nb'); }); + it('takes Thai and Estonian, which the webmail has too', () => { + expect(resolveLocaleTag({ languageCode: 'th', languageTag: 'th-TH' })).toBe('th'); + expect(resolveLocaleTag({ languageCode: 'et', languageTag: 'et-EE' })).toBe('et'); + }); + it('returns null for unsupported languages', () => { expect(resolveLocaleTag({ languageCode: 'xx', languageTag: 'xx-XX' })).toBeNull(); expect(resolveLocaleTag({})).toBeNull(); @@ -40,6 +45,11 @@ describe('getLocaleDirection', () => { }); describe('translate', () => { + it('reads Thai and Estonian from their own catalogs', () => { + expect(translate('th', 'settings.title')).not.toBe(translate('en', 'settings.title')); + expect(translate('et', 'settings.title')).not.toBe(translate('en', 'settings.title')); + }); + it('falls back locale → en → fallback → key', () => { expect(translate('de', 'settings.title')).not.toBe('settings.title'); expect(translate('de', 'nope.missing', 'Fallback')).toBe('Fallback'); diff --git a/src/i18n/index.ts b/src/i18n/index.ts index ed0a57d0..de22f929 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -30,6 +30,7 @@ export const SUPPORTED_LOCALES = [ { code: 'da', label: 'Dansk' }, { code: 'de', label: 'Deutsch' }, { code: 'en', label: 'English' }, + { code: 'et', label: 'Eesti' }, { code: 'fa', label: 'فارسی' }, { code: 'es', label: 'Español' }, { code: 'fr', label: 'Français' }, @@ -42,6 +43,7 @@ export const SUPPORTED_LOCALES = [ { code: 'pl', label: 'Polski' }, { code: 'pt', label: 'Português' }, { code: 'ro', label: 'Română' }, + { code: 'th', label: 'ไทย' }, { code: 'tr', label: 'Türkçe' }, { code: 'ru', label: 'Русский' }, { code: 'uk', label: 'Українська' }, @@ -74,7 +76,7 @@ function deepMerge(base: Dictionary, overlay: Dictionary): Dictionary { // A catalog is a few hundred KB of JSON. Metro only evaluates a module when it // is first required, so keeping each require inside its loader means startup -// builds English plus the active language instead of all 27, and switching +// builds English plus the active language instead of all 29, and switching // language loads the new catalog synchronously on the next t() call. The // require paths must stay string literals for Metro to bundle them. const CATALOG_LOADERS: Record Dictionary> = { @@ -84,6 +86,7 @@ const CATALOG_LOADERS: Record Dictionary> = { da: () => require('../../locales/da/common.json'), de: () => require('../../locales/de/common.json'), en: () => en as Dictionary, + et: () => require('../../locales/et/common.json'), es: () => require('../../locales/es/common.json'), fa: () => require('../../locales/fa/common.json'), fr: () => require('../../locales/fr/common.json'), @@ -100,6 +103,7 @@ const CATALOG_LOADERS: Record Dictionary> = { pt: () => require('../../locales/pt/common.json'), ro: () => require('../../locales/ro/common.json'), ru: () => require('../../locales/ru/common.json'), + th: () => require('../../locales/th/common.json'), sk: () => require('../../locales/sk/common.json'), tr: () => require('../../locales/tr/common.json'), uk: () => require('../../locales/uk/common.json'), @@ -115,6 +119,7 @@ const OVERLAY_LOADERS: Record Dictionary> = { da: () => require('../../locales/rn/da.json'), de: () => require('../../locales/rn/de.json'), en: () => rnEn as Dictionary, + et: () => require('../../locales/rn/et.json'), es: () => require('../../locales/rn/es.json'), fa: () => require('../../locales/rn/fa.json'), fr: () => require('../../locales/rn/fr.json'), @@ -131,6 +136,7 @@ const OVERLAY_LOADERS: Record Dictionary> = { pt: () => require('../../locales/rn/pt.json'), ro: () => require('../../locales/rn/ro.json'), ru: () => require('../../locales/rn/ru.json'), + th: () => require('../../locales/rn/th.json'), sk: () => require('../../locales/rn/sk.json'), tr: () => require('../../locales/rn/tr.json'), uk: () => require('../../locales/rn/uk.json'), diff --git a/src/lib/calendar-locale.ts b/src/lib/calendar-locale.ts index 3461cb23..5f857c66 100644 --- a/src/lib/calendar-locale.ts +++ b/src/lib/calendar-locale.ts @@ -19,6 +19,7 @@ const DATE_FNS_LOCALE_LOADERS: Record Locale> = { da: () => require('date-fns/locale/da').da, de: () => require('date-fns/locale/de').de, es: () => require('date-fns/locale/es').es, + et: () => require('date-fns/locale/et').et, fa: () => require('date-fns/locale/fa-IR').faIR, fr: () => require('date-fns/locale/fr').fr, he: () => require('date-fns/locale/he').he, @@ -35,6 +36,7 @@ const DATE_FNS_LOCALE_LOADERS: Record Locale> = { ro: () => require('date-fns/locale/ro').ro, ru: () => require('date-fns/locale/ru').ru, sk: () => require('date-fns/locale/sk').sk, + th: () => require('date-fns/locale/th').th, tr: () => require('date-fns/locale/tr').tr, uk: () => require('date-fns/locale/uk').uk, zh: () => require('date-fns/locale/zh-CN').zhCN,