Files
SRCmail/components/providers/intl-provider.tsx
T
xhzeemandLinus Rath d531ad1930 fix(i18n): register ar messages in the client IntlProvider
components/providers/intl-provider.tsx keeps its own static ALL_MESSAGES
map separate from i18n/request.ts's server-side loader. It was missed
when ar was added, so switching to Arabic flipped to RTL (direction.ts
knew about ar) but rendered English text (messages lookup fell through
to the en fallback).
2026-07-21 23:07:40 +02:00

121 lines
3.9 KiB
TypeScript

"use client";
import { useEffect, useMemo, useState } from 'react';
import { NextIntlClientProvider } from 'next-intl';
import { useLocaleStore } from '@/stores/locale-store';
import arMessages from '@/locales/ar/common.json';
import csMessages from '@/locales/cs/common.json';
import daMessages from '@/locales/da/common.json';
import deMessages from '@/locales/de/common.json';
import { getLocaleDirection } from '@/i18n/direction';
import { mergeMessages } from '@/i18n/merge-messages';
import { detectBrowserLocale } from '@/i18n/detect-locale';
import enMessages from '@/locales/en/common.json';
import esMessages from '@/locales/es/common.json';
import heMessages from '@/locales/he/common.json';
import faMessages from '@/locales/fa/common.json';
import frMessages from '@/locales/fr/common.json';
import huMessages from '@/locales/hu/common.json';
import itMessages from '@/locales/it/common.json';
import jaMessages from '@/locales/ja/common.json';
import koMessages from '@/locales/ko/common.json';
import lvMessages from '@/locales/lv/common.json';
import nlMessages from '@/locales/nl/common.json';
import plMessages from '@/locales/pl/common.json';
import ptMessages from '@/locales/pt/common.json';
import roMessages from '@/locales/ro/common.json';
import ruMessages from '@/locales/ru/common.json';
import skMessages from '@/locales/sk/common.json';
import trMessages from '@/locales/tr/common.json';
import ukMessages from '@/locales/uk/common.json';
import zhMessages from '@/locales/zh/common.json';
// Pre-loaded translations (loaded at build time, not runtime)
const ALL_MESSAGES = {
ar: arMessages,
cs: csMessages,
da: daMessages,
de: deMessages,
en: enMessages,
es: esMessages,
he: heMessages,
fa: faMessages,
fr: frMessages,
hu: huMessages,
it: itMessages,
ja: jaMessages,
ko: koMessages,
lv: lvMessages,
nl: nlMessages,
pl: plMessages,
pt: ptMessages,
ro: roMessages,
ru: ruMessages,
sk: skMessages,
tr: trMessages,
uk: ukMessages,
zh: zhMessages,
};
interface IntlProviderProps {
locale: string;
messages: Record<string, unknown>;
children: React.ReactNode;
}
export function IntlProvider({ locale: initialLocale, children }: IntlProviderProps) {
const currentLocale = useLocaleStore((state) => state.locale);
const [activeLocale, setActiveLocale] = useState(initialLocale);
const [timeZone, setTimeZone] = useState<string>('UTC');
// Detect user's timezone on mount
useEffect(() => {
try {
const detectedTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
setTimeZone(detectedTimeZone);
} catch (error) {
// Fallback to UTC if detection fails
console.warn('Failed to detect timezone, using UTC:', error);
setTimeZone('UTC');
}
}, []);
// Resolve the active locale from the user's stored choice. Empty or 'auto'
// means "follow the browser" (English default); a specific code forces it and
// is never overridden by detection.
useEffect(() => {
setActiveLocale(
!currentLocale || currentLocale === 'auto'
? detectBrowserLocale(initialLocale)
: currentLocale
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentLocale]);
// Keep <html> lang/dir in sync with the active locale (RTL for he/fa).
useEffect(() => {
document.documentElement.lang = activeLocale;
document.documentElement.dir = getLocaleDirection(activeLocale);
}, [activeLocale]);
// Fall back to English for any key the active locale has not translated, so
// untranslated strings show English text instead of a raw message key.
const messages = useMemo(
() => mergeMessages(
ALL_MESSAGES.en as Record<string, unknown>,
(ALL_MESSAGES[activeLocale as keyof typeof ALL_MESSAGES] ?? {}) as Record<string, unknown>
),
[activeLocale]
);
return (
<NextIntlClientProvider
locale={activeLocale}
messages={messages}
timeZone={timeZone}
>
{children}
</NextIntlClientProvider>
);
}