import { useTranslation } from 'react-i18next';
const MyComponent = () => {
const { t } = useTranslation();
return <div>{t('section.key')}</div>;
};<button>{t('common.save')}</button>
<h1>{t('home.heroTitle')}</h1><p>{t('home.welcome', { username: user.name })}</p>
<span>{t('tournament.participants', { count: 42 })}</span>// Translation: "items": "{{count}} item", "items_plural": "{{count}} items"
<p>{t('items', { count: items.length })}</p>t('common.save') // Enregistrer / Save
t('common.cancel') // Annuler / Cancel
t('common.delete') // Supprimer / Delete
t('common.edit') // Modifier / Edit
t('common.close') // Fermer / Close
t('common.loading') // Chargement... / Loading...
t('common.search') // Rechercher / Search
t('common.filter') // Filtrer / Filtert('auth.login') // Connexion / Log In
t('auth.logout') // Déconnexion / Log Out
t('auth.signup') // S'inscrire / Sign Up
t('auth.email') // Email / Email
t('auth.password') // Mot de passe / Password
t('auth.username') // Nom d'utilisateur / Usernamet('forms.emailPlaceholder') // votre@email.com / your@email.com
t('forms.passwordPlaceholder') // •••••••• / ••••••••
t('forms.searchPlaceholder') // Rechercher... / Search...t('errors.generic') // Une erreur s'est produite
t('errors.loadingError') // Erreur lors du chargement
t('errors.networkError') // Erreur de connexion réseau
t('validation.required') // Ce champ est requis
t('validation.invalidEmail') // Adresse email invalidet('navigation.home') // Accueil / Home
t('navigation.tournaments') // Tournois / Tournaments
t('navigation.profile') // Profil / Profile
t('navigation.friends') // Amis / Friends
t('navigation.leaderboards') // Classements / Leaderboardsimport { useLocale } from '../contexts/LocaleContext';
const { currentLanguage } = useLocale();
console.log(currentLanguage); // 'fr' or 'en'const { changeLanguage } = useLocale();
// Switch to English
await changeLanguage('en');
// Switch to French
await changeLanguage('fr');import { useLocale } from '../contexts/LocaleContext';
const LanguageSwitcher = () => {
const { currentLanguage, changeLanguage } = useLocale();
return (
<div>
<button
onClick={() => changeLanguage('fr')}
disabled={currentLanguage === 'fr'}
>
FR
</button>
<button
onClick={() => changeLanguage('en')}
disabled={currentLanguage === 'en'}
>
EN
</button>
</div>
);
};{
"myFeature": {
"title": "Mon Titre",
"subtitle": "Mon sous-titre",
"action": "Mon action"
}
}{
"myFeature": {
"title": "My Title",
"subtitle": "My subtitle",
"action": "My action"
}
}<h1>{t('myFeature.title')}</h1>
<p>{t('myFeature.subtitle')}</p>
<button>{t('myFeature.action')}</button>section.subsection.element
│ │ │
│ │ └─ Specific element (title, button, label)
│ └─ Optional subsection (editForm, listView)
└─ Main section (auth, profile, tournament)
auth.loginTitle
auth.loginForm.emailLabel
auth.loginForm.submitButton
profile.editForm.saveButton
profile.settings.privacyTitle
tournament.card.registerButton
tournament.details.prizePool
// In browser console
localStorage.getItem('userLanguagePreference')// In browser console
localStorage.setItem('userLanguagePreference', 'en');
location.reload();// In browser console
localStorage.removeItem('userLanguagePreference');
location.reload();Look in browser console for warnings like:
i18next: key "section.missingKey" not found
Instead of:
{
"page1": { "save": "Enregistrer" },
"page2": { "save": "Enregistrer" },
"page3": { "save": "Enregistrer" }
}Use:
{
"common": { "save": "Enregistrer" }
}Then:
<button>{t('common.save')}</button>Bad:
{items.map(item => <div>{t('item', { name: item.name })}</div>)}Good:
const itemText = t('item', { name: item.name });
{items.map(item => <div>{itemText}</div>)}// Hardcoded text
<button>Save</button>
// Wrong interpolation
<p>{t('welcome', username)}</p>
// Missing translation key
<div>{t('nonexistent.key')}</div>// Use translation
<button>{t('common.save')}</button>
// Correct interpolation
<p>{t('welcome', { username })}</p>
// Add missing keys to JSON first
<div>{t('existing.key')}</div>common- Common UI elementsauth- Authenticationvalidation- Form validationerrors- Error messagesnavigation- Navigationheader- Header componentfooter- Footer componenthome- Home pagetournament- Tournamentsprofile- User profilefriends- Friends featuresmessages- Messagingnotifications- Notificationsleaderboards- Rankingssupport- Help/Supportgames- Gamescommunities- Communitieschat- Chat featuresforms- Form elementsstatus- Status labelsdates- Date/timeactions- Action buttonsgaming- Gaming accountserrors404- 404 pageerrorBoundary- Error boundary
- Full Documentation:
src/locales/README.md - Implementation Guide:
I18N_IMPLEMENTATION_GUIDE.md - Example Component:
src/components/layout/Footer.tsx
- Keep keys flat when possible:
auth.loginTitleis better thanauth.login.title.main - Be consistent: Use same terminology across translations
- Test both languages: Always check French AND English
- Update both files: Never add key to only one language file
- Use meaningful names:
profile.editButtonnotprofile.btn1
Different languages have different text lengths. Test your UI with both languages:
- French text is typically 10-20% longer than English
- Buttons should have flexible width or ellipsis
- Multi-line text containers should allow wrapping
- Fixed-width layouts may need adjustment
Example:
/* Allow button text to wrap if needed */
.btn {
white-space: normal;
min-height: 40px;
}Quick Start Checklist:
- Import
useTranslation - Add
const { t } = useTranslation(); - Replace text with
t('key') - Test in both languages
- Check console for warnings
Need Help? Check src/locales/README.md for detailed documentation!