Internationalization (i18n) Setup in SparkyFitnessFrontend
This document outlines the technical setup for internationalization (i18n) in the SparkyFitnessFrontend application using i18next and react-i18next.
INFO
Only the English (en) files may be edited by hand. Every other language is translated on Weblate and synced from SparkyFitnessTranslations.
1. Core Libraries
The following npm packages are used for i18n:
i18next: The core i18n library.react-i18next: Integration for React applications.i18next-browser-languagedetector: Detects the user's language from the browser.i18next-http-backend: Loads translation files over HTTP.
These dependencies are installed in the SparkyFitnessFrontend directory.
2. Translation File Structure
Translation files are stored in the public/locales directory, following the format public/locales//translation.json.
public/locales/en/translation.json: the English source, and the only file a code contributor edits.public/locales//translation.json: every other language, owned by translators and synced from SparkyFitnessTranslations via Weblate.
Each translation.json file is a simple JSON object where keys represent translation identifiers and values are the translated strings. Nested objects can be used to organize translations (e.g., "nav.diary").
Example (translation.json):
{
"nav": {
"diary": "Diary",
"checkin": "Check-In"
},
"settings": {
"profileInformation": {
"title": "Profile Information"
}
}
}3. i18next Configuration (src/i18n.ts)
The i18next instance is configured in SparkyFitnessFrontend/src/i18n.ts.
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpApi from 'i18next-http-backend';
import { getSupportedLanguages } from './utils/languageUtils';
i18n
.use(HttpApi)
.use(LanguageDetector)
.use(initReactI18next)
.init({
supportedLngs: getSupportedLanguages(), // from src/utils/languageUtils.ts
fallbackLng: 'en', // Fallback language if a translation is missing
detection: {
order: ['localStorage', 'querystring', 'cookie', 'sessionStorage', 'navigator', 'htmlTag'],
caches: ['localStorage', 'cookie'],
},
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json', // Path to load translation files
},
react: {
useSuspense: false, // Set to true if you want to use React.Suspense for loading translations
},
});
export default i18n;Key Configuration Details:
supportedLngs: the languages the app offers, fromgetSupportedLanguages()inSparkyFitnessFrontend/src/utils/languageUtils.ts. A locale directory that is not listed there cannot be selected.fallbackLng: The language to use if a translation for the current language is missing.detection.order: Specifies the order in whichi18nexttries to detect the user's language.localStorageis prioritized to use the user's saved preference.backend.loadPath: The URL pattern to fetch translation files.is replaced by the current language code, andby the namespace (defaulting totranslation).react.useSuspense: Set tofalseto avoid using React's Suspense feature for translations, simplifying initial setup.
4. Integration into React Application (src/main.tsx)
The i18next instance is initialized and provided to the React application in SparkyFitnessFrontend/src/main.tsx.
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import './i18n'; // side-effect import: configures the shared i18next instance
import { Suspense } from 'react';
// ...
createRoot(document.getElementById('root')!).render(
<Suspense fallback="loading">
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</Suspense>,
);react.useSuspense is false, so i18next does not suspend while a catalog loads — it renders the fallback language until the translation arrives. The Suspense boundary above is therefore not what handles translation loading; set useSuspense to true if you want it to.
5. Language Handling Component (src/components/LanguageHandler.tsx)
A dedicated component, SparkyFitnessFrontend/src/components/LanguageHandler.tsx, is used to synchronize the i18next language with the user's preference stored in the PreferencesContext.
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { usePreferences } from '@/contexts/PreferencesContext';
const LanguageHandler = () => {
const { i18n } = useTranslation();
const { language } = usePreferences();
useEffect(() => {
if (language) {
i18n.changeLanguage(language);
}
}, [language, i18n]);
return null; // This component doesn't render anything
};
export default LanguageHandler;This component ensures that when the language preference changes (e.g., via the language switcher in settings), i18next updates its active language.
6. Using Translations in Components
To use translations in any React component, import the useTranslation hook from react-i18next.
import { useTranslation } from 'react-i18next';
const MyComponent = () => {
const { t } = useTranslation();
return (
<div>
<h1>{t('nav.diary')}</h1>
<p>{t('settings.profileInformation.description')}</p>
</div>
);
};The t function takes a translation key (e.g., "nav.diary") and returns the corresponding translated string for the currently active language.
7. Language Switcher in Settings (src/pages/Settings/PreferenceSettings.tsx)
The language switcher lives in the Preferences section of the settings page. It renders one entry per supported language, so adding a language to languageUtils.ts is enough to make it appear — the list is never hardcoded here.
import { getLanguageDisplayName, getSupportedLanguages } from "@/utils/languageUtils";
// ...
<Select value={language} onValueChange={setLanguage}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{getSupportedLanguages().map((langCode) => (
<SelectItem key={langCode} value={langCode}>
{getLanguageDisplayName(langCode)}
</SelectItem>
))}
</SelectContent>
</Select>getLanguageDisplayName returns each language's endonym — Deutsch, Español, 日本語 — so the list reads the same whatever the interface language is.
8. Adding New Languages
Translation files are not created by hand. A language is requested on Weblate and translated there. A maintainer then runs the Sync Translations workflow (workflow_dispatch), which copies the non-English files into public/locales/ and opens a pull request. Enabling the language is a separate edit: add the code to getSupportedLanguages() and getLanguageDisplayName() in src/utils/languageUtils.ts.
