feat: add Estonian (et) locale

Complete Estonian translation (every key in en), wired into the locale list, the message loaders, the language switcher (with an Estonian flag drawn in the Twemoji style, since Twemoji's set is not used for it) and the locale-count test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
raudraido
2026-10-16 18:19:29 +03:00
co-authored by Claude Sonnet 5.5
parent fc22f111ef
commit ae7f794d2f
7 changed files with 4415 additions and 3 deletions
+1
View File
@@ -27,6 +27,7 @@ export const LOCALE_LOADERS: Record<string, () => Promise<Messages>> = {
en: () => Promise.resolve(enMessages as Messages),
es: () => import('@/locales/es/common.json').then((m) => m.default),
he: () => import('@/locales/he/common.json').then((m) => m.default),
et: () => import('@/locales/et/common.json').then((m) => m.default),
fa: () => import('@/locales/fa/common.json').then((m) => m.default),
fr: () => import('@/locales/fr/common.json').then((m) => m.default),
hu: () => import('@/locales/hu/common.json').then((m) => m.default),
+6
View File
@@ -126,6 +126,11 @@ function FlagTW(props: FlagProps) {
return <Flag {...props}><path fill="#FE0000" d="M32 5H17v13H0v9c0 2.209 1.791 4 4 4h28c2.209 0 4-1.791 4-4V9c0-2.209-1.791-4-4-4z" /><path fill="#000095" d="M17 5H4C1.791 5 0 6.791 0 9v9h17V5zm-6.063 4.61l2.035-.548-1.491 1.487c-.121-.348-.307-.665-.544-.939zm.251 1.89c0 1.346-1.091 2.438-2.438 2.438S6.312 12.846 6.312 11.5 7.404 9.062 8.75 9.062s2.438 1.092 2.438 2.438zm0-4.222l-.548 2.035c-.273-.237-.591-.423-.939-.544l1.487-1.491zM8.75 6.625l.542 2.034c-.175-.033-.357-.052-.542-.052s-.367.019-.542.052l.542-2.034zm-.951 2.144c-.348.121-.665.307-.939.544l-.548-2.035 1.487 1.491zm-1.236.841c-.237.273-.423.591-.544.939L4.528 9.062l2.035.548zm-.654 1.348c-.034.176-.052.357-.052.542 0 .185.018.367.052.542L3.875 11.5l2.034-.542zm.11 1.492c.121.348.308.666.544.939l-2.035.548 1.491-1.487zm.293 3.272l.548-2.035c.273.236.591.423.939.544l-1.487 1.491zm2.438.653l-.542-2.034c.176.034.357.052.542.052s.367-.018.542-.052l-.542 2.034zm.951-2.144c.348-.121.666-.308.939-.544l.548 2.035-1.487-1.491zm1.236-.841c.237-.273.423-.591.544-.939l1.491 1.487-2.035-.548zm.654-1.348c.034-.176.052-.357.052-.542 0-.185-.018-.367-.052-.542l2.034.542-2.034.542z" /><path fill="#FFF" d="M9.292 8.659L8.75 6.625l-.542 2.034c.175-.033.357-.052.542-.052s.367.019.542.052zM5.857 11.5c0-.185.018-.367.052-.542l-2.034.542 2.034.542c-.033-.175-.052-.357-.052-.542zm2.351 2.841l.542 2.034.542-2.034c-.176.034-.357.052-.542.052s-.367-.019-.542-.052zm3.435-2.841c0 .185-.018.367-.052.542l2.034-.542-2.034-.542c.033.175.052.357.052.542zm-.455-4.222L9.701 8.769c.348.122.666.308.939.544l.548-2.035zm-.251 6.112l2.035.548-1.491-1.487c-.121.348-.307.665-.544.939zm-4.625 2.332l1.487-1.491c-.348-.121-.666-.308-.939-.544l-.548 2.035zm.251-6.112l-2.035-.548 1.491 1.487c.121-.348.307-.665.544-.939zm3.138 4.621l1.487 1.491-.548-2.035c-.274.237-.591.423-.939.544zM6.019 12.45l-1.491 1.487 2.035-.548c-.237-.273-.423-.59-.544-.939zm1.78-3.681L6.312 7.278l.548 2.035c.274-.237.591-.423.939-.544zm5.173.293l-2.035.548c.237.273.423.591.544.939l1.491-1.487z" /><circle fill="#FFF" cx="8.75" cy="11.5" r="2.438" /></Flag>;
}
// Estonia is not in Twemoji's set used above; drawn here in the same style.
function FlagEE(props: FlagProps) {
return <Flag {...props}><clipPath id="flag-ee-clip"><rect x="0" y="5" width="36" height="26" rx="4" /></clipPath><g clipPath="url(#flag-ee-clip)"><path fill="#0072CE" d="M0 5h36v8.67H0z" /><path fill="#000" d="M0 13.67h36v8.66H0z" /><path fill="#EEE" d="M0 22.33h36V31H0z" /></g></Flag>;
}
export const flagComponents: Record<string, (props: FlagProps) => ReactElement> = {
ar: FlagAE,
ca: FlagCAT,
@@ -136,6 +141,7 @@ export const flagComponents: Record<string, (props: FlagProps) => ReactElement>
en: FlagGB,
fa: FlagIR,
es: FlagES,
et: FlagEE,
fr: FlagFR,
he: FlagIL,
it: FlagIT,
+1
View File
@@ -17,6 +17,7 @@ const languages = [
{ value: 'da', label: 'Dansk' },
{ value: 'de', label: 'Deutsch' },
{ value: 'en', label: 'English' },
{ value: 'et', label: 'Eesti' },
{ value: 'fa', label: 'فارسی' },
{ value: 'es', label: 'Español' },
{ value: 'fr', label: 'Français' },
+3
View File
@@ -43,6 +43,9 @@ export default getRequestConfig(async ({ requestLocale }) => {
case 'he':
messages = (await import('../locales/he/common.json')).default;
break;
case 'et':
messages = (await import('../locales/et/common.json')).default;
break;
case 'fa':
messages = (await import('../locales/fa/common.json')).default;
break;
+1 -1
View File
@@ -12,7 +12,7 @@ const localePrefix = (process.env.NEXT_PUBLIC_LOCALE_PREFIX ?? 'never') as
| 'always'
| 'as-needed';
const SUPPORTED_LOCALES = ['ar', 'ca', 'cs', 'da', 'de', 'en', 'es', 'fa', 'fr', 'he', 'hu', 'it', 'ja', 'ko', 'lv', 'mn', 'nb', 'nl', 'pl', 'pt', 'ro', 'ru', 'sk', 'tr', 'uk', 'zh', 'zh-TW'] as const;
const SUPPORTED_LOCALES = ['ar', 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'fa', 'fr', 'he', 'hu', 'it', 'ja', 'ko', 'lv', 'mn', 'nb', 'nl', 'pl', 'pt', 'ro', 'ru', 'sk', 'tr', 'uk', 'zh', 'zh-TW'] as const;
// Fallback locale used when the visitor's Accept-Language header does not
// match any supported locale (and no NEXT_LOCALE cookie is set yet). Admins
+2 -2
View File
@@ -14,8 +14,8 @@ describe('permission texts', () => {
const shown = ALL_PERMISSIONS.filter((p) => !implicit.has(p));
const langs = readdirSync(LOCALES).filter((l) => !l.includes('.'));
it('covers all 27 locales', () => {
expect(langs).toHaveLength(27);
it('covers all 28 locales', () => {
expect(langs).toHaveLength(28);
});
it.each(langs)('has a title and body for every permission in %s', (lang) => {
File diff suppressed because it is too large Load Diff