Changing Locales
This guide shows you how to switch languages at runtime and react to locale changes. You'll learn synchronous and asynchronous switching, fallback configuration, and UI update patterns.
Prerequisites
Before starting, you should:
Overview
We'll implement locale switching by:
- Setting up multiple locales
- Switching with
setLocale() - Subscribing to changes with
onChange - Configuring fallback behavior
Step 1: Define Multiple Locales
Include translations for each supported language:
import { createI18n } from '@motioneffector/i18n'
const i18n = createI18n({
defaultLocale: 'en',
translations: {
en: {
greeting: 'Hello',
farewell: 'Goodbye',
buttons: {
save: 'Save',
cancel: 'Cancel'
}
},
es: {
greeting: 'Hola',
farewell: 'Adiós',
buttons: {
save: 'Guardar',
cancel: 'Cancelar'
}
},
fr: {
greeting: 'Bonjour',
farewell: 'Au revoir',
buttons: {
save: 'Sauvegarder',
cancel: 'Annuler'
}
}
}
})
Step 2: Switch Locales
Use setLocale() to change the current language:
// Check current locale
console.log(i18n.getLocale()) // "en"
console.log(i18n.t('greeting')) // "Hello"
// Switch to Spanish
i18n.setLocale('es')
console.log(i18n.getLocale()) // "es"
console.log(i18n.t('greeting')) // "Hola"
// Switch to French
i18n.setLocale('fr')
console.log(i18n.t('greeting')) // "Bonjour"
setLocale() returns the i18n instance for chaining:
const greeting = i18n.setLocale('es').t('greeting') // "Hola"
Step 3: React to Locale Changes
Subscribe to onChange to update your UI when the locale changes:
// Subscribe
const unsubscribe = i18n.onChange((newLocale, previousLocale) => {
console.log(`Language changed: ${previousLocale} -> ${newLocale}`)
// Update your UI here
document.documentElement.lang = newLocale
renderApp()
})
// Trigger a change
i18n.setLocale('es')
// Logs: "Language changed: en -> es"
// Later, unsubscribe when no longer needed
unsubscribe()
Step 4: Configure Fallback
Use fallbackLocale to handle missing translations gracefully:
const i18n = createI18n({
defaultLocale: 'de',
fallbackLocale: 'en', // Use English when German is missing
translations: {
en: {
greeting: 'Hello',
newFeature: 'Check out this new feature!'
},
de: {
greeting: 'Hallo'
// 'newFeature' not translated yet
}
}
})
i18n.t('greeting') // "Hallo" (found in German)
i18n.t('newFeature') // "Check out this new feature!" (falls back to English)
Complete Example
import { createI18n } from '@motioneffector/i18n'
// Create i18n with multiple locales
const i18n = createI18n({
defaultLocale: 'en',
fallbackLocale: 'en',
translations: {
en: {
nav: { home: 'Home', about: 'About', contact: 'Contact' },
welcome: 'Welcome to our site!',
language: 'Language'
},
es: {
nav: { home: 'Inicio', about: 'Acerca de', contact: 'Contacto' },
welcome: '¡Bienvenido a nuestro sitio!',
language: 'Idioma'
}
}
})
// Track current locale in DOM
function updateDocumentLang(locale: string) {
document.documentElement.lang = locale
}
// Subscribe to changes
i18n.onChange((newLocale) => {
updateDocumentLang(newLocale)
console.log('UI should re-render now')
})
// Language selector handler
function handleLanguageChange(newLocale: string) {
if (i18n.getAvailableLocales().includes(newLocale)) {
i18n.setLocale(newLocale)
} else {
console.error(`Locale ${newLocale} is not available`)
}
}
// Usage
handleLanguageChange('es')
console.log(i18n.t('welcome')) // "¡Bienvenido a nuestro sitio!"
Variations
Persisting User Preference
Save the user's language choice to localStorage:
// On app start, restore saved locale
const savedLocale = localStorage.getItem('locale')
if (savedLocale && i18n.getAvailableLocales().includes(savedLocale)) {
i18n.setLocale(savedLocale)
}
// When locale changes, save it
i18n.onChange((newLocale) => {
localStorage.setItem('locale', newLocale)
})
Detecting Browser Language
Set initial locale based on browser preference:
function detectLocale(): string {
const browserLocales = navigator.languages || [navigator.language]
const available = i18n.getAvailableLocales()
for (const locale of browserLocales) {
// Try exact match first (e.g., 'en-US')
if (available.includes(locale)) {
return locale
}
// Try language only (e.g., 'en')
const lang = locale.split('-')[0]
if (available.includes(lang)) {
return lang
}
}
return 'en' // Default fallback
}
i18n.setLocale(detectLocale())
Async Loading Before Switch
If translations need to be loaded, use setLocaleAsync():
const i18n = createI18n({
defaultLocale: 'en',
translations: { en: { hello: 'Hello' } },
loadPath: async (locale) => {
const response = await fetch(`/locales/${locale}.json`)
return response.json()
}
})
// This loads translations if needed, then switches
await i18n.setLocaleAsync('es')
console.log(i18n.t('hello'))
Troubleshooting
Error: No translations available for locale
Symptom: setLocale() throws an error.
Cause: The locale doesn't exist and there's no fallback configured.
Solution: Either add translations for the locale, configure a fallbackLocale, or use setLocaleAsync() with loadPath:
// Option 1: Add translations first
i18n.addTranslations('de', { hello: 'Hallo' })
i18n.setLocale('de')
// Option 2: Configure fallback
const i18n = createI18n({
defaultLocale: 'en',
fallbackLocale: 'en', // Now any locale is allowed
translations: { en: { hello: 'Hello' } }
})
onChange Not Firing
Symptom: Your callback isn't called when you switch locales.
Cause: You're setting the locale to its current value.
Solution: onChange only fires when the locale actually changes:
i18n.setLocale('en') // Already 'en', no change, callback not fired
i18n.setLocale('es') // Different locale, callback fires
See Also
- Locale Management Concept - How locales work
- Lazy Loading Guide - Load translations on demand
- Locale Management API - Full method documentation