fix(i18n): eager-load nl bundle, fix language selector, remove hardcoded backend messages

- Eager-load both en and nl translation bundles at i18n init to eliminate
  the async gap that caused English flash when Dutch was the detected language
- LanguageSwitcher: use i18n.language (synchronous) instead of resolvedLanguage
  (asynchronous) so the visual selection is always correct after switching
- AvailabilityController: remove hardcoded English messages ("System is running
  normally.") from GET /availability/status; return empty string so the frontend
  translations control the display text

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 14:16:27 +02:00
co-authored by Claude Haiku 4.5
parent 3c6a06028e
commit d69512a449
3 changed files with 13 additions and 22 deletions
+7 -1
View File
@@ -20,7 +20,13 @@ import {
/** Language selector for the topbar; lazy-loads the chosen locale (Q4-B). */
export function LanguageSwitcher() {
const { t, i18n } = useTranslation();
const current = (i18n.resolvedLanguage ?? 'en') as SupportedLanguage;
// Use i18n.language (set synchronously on change) rather than resolvedLanguage
// (which can lag when bundles were lazy-loaded). Split on '-' to normalise
// region variants like 'nl-NL' → 'nl'.
const rawLang = i18n.language?.split('-')[0] ?? 'en';
const current = (SUPPORTED_LANGUAGES.includes(rawLang as SupportedLanguage)
? rawLang
: 'en') as SupportedLanguage;
return (
<DropdownMenu>
+5 -18
View File
@@ -2,6 +2,7 @@ import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import enTranslation from './locales/en/translation.json';
import nlTranslation from './locales/nl/translation.json';
export const SUPPORTED_LANGUAGES = ['en', 'nl'] as const;
export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
@@ -11,25 +12,15 @@ export const LANGUAGE_LABELS: Record<SupportedLanguage, string> = {
nl: 'Nederlands',
};
// English (the fallback) is bundled eagerly so the UI never flashes raw keys.
// Other locales are lazy-loaded on demand (Q4-B / NFR-U1-05).
const loaded = new Set<string>(['en']);
async function loadLocale(lng: string): Promise<void> {
if (loaded.has(lng) || !SUPPORTED_LANGUAGES.includes(lng as SupportedLanguage)) {
return;
}
const module = await import(`./locales/${lng}/translation.json`);
i18n.addResourceBundle(lng, 'translation', module.default, true, true);
loaded.add(lng);
}
// Both supported locales are bundled eagerly so the detected language is
// available immediately on init — no async gap that would cause an English flash.
void i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: { translation: enTranslation },
nl: { translation: nlTranslation },
},
fallbackLng: 'en',
supportedLngs: SUPPORTED_LANGUAGES as unknown as string[],
@@ -42,12 +33,8 @@ void i18n
react: { useSuspense: false },
});
// Ensure the detected language is loaded after init.
void loadLocale(i18n.resolvedLanguage ?? 'en');
/** Lazy-load the target locale, then switch to it (persisted by the detector). */
/** Switch to a supported locale and persist the choice via the detector cache. */
export async function changeLanguage(lng: SupportedLanguage): Promise<void> {
await loadLocale(lng);
await i18n.changeLanguage(lng);
}
@@ -25,9 +25,7 @@ public class AvailabilityController : ControllerBase
{
Status = status.ToString(),
CheckedAt = DateTimeOffset.UtcNow,
Message = status == AvailabilityStatus.Available
? "System is running normally."
: "System is in maintenance or unavailable."
Message = string.Empty,
});
}