| name | internationalization |
| description | Internationalization (i18n) best practices and implementation |
| license | MIT |
| compatibility | opencode |
| metadata | {"audience":"developers","category":"frontend"} |
What I do
- Implement i18n in applications
- Handle locale detection and switching
- Format dates, numbers, and currencies
- Manage translation files
- Handle plurals and gender
- Support RTL languages
- Test translations
- Design for localization
When to use me
When implementing internationalization or localization.
i18n Architecture
i18n/
├── en/
│ └── messages.json
├── es/
│ └── messages.json
├── zh/
│ └── messages.json
├── ar/
│ └── messages.json # RTL language
└── i18n.ts # i18n utilities
Translation Files
{
"app": {
"title": "My Application",
"tagline": "Building the future"
},
"common": {
"save": "Save",
"cancel": "Cancel",
"submit": "Submit",
"loading": "Loading...",
"error": "An error occurred",
"success": "Operation successful"
},
"user": {
"welcome": "Welcome, {{name}}!",
"profile": "User Profile",
"settings": "Settings",
"logout": "Log Out"
},
"date": {
"today": "Today",
"yesterday": "Yesterday",
"tomorrow": "Tomorrow"
},
"errors": {
"required": "{{field}} is required",
"email": "Please enter a valid email address",
"min_length": "{{field}} must be at least {{min}} characters",
"max_length": "{{field}} must be no more than {{max}} characters"
}
}
{
"app": {
"title": "Mi Aplicación",
"tagline": "Construyendo el futuro"
},
"common": {
"save": "Guardar",
"cancel": "Cancelar",
"submit": "Enviar",
"loading": "Cargando...",
"error": "Ocurrió un error",
"success": "Operación exitosa"
},
"user": {
"welcome": "¡Bienvenido, {{name}}!"
}
}
i18n Implementation
import i18next from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18next
.use(Backend)
.use(LanguageDetector)
.init({
supportedLngs: ['en', 'es', 'zh', 'ar', 'fr', 'de'],
fallbackLng: 'en',
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
detection: {
order: ['querystring', 'cookie', 'localStorage', 'navigator'],
caches: ['localStorage', 'cookie'],
},
interpolation: {
escapeValue: false,
},
react: {
useSuspense: false,
},
});
export default i18next;
import { useTranslation } from 'react-i18next';
() {
{ t, i18n } = ();
(
);
}
() {
{ i18n } = ();
(
);
}
Pluralization
{
"items_one": "{{count}} item",
"items_other": "{{count}} items",
"items_zero": "No items",
"notifications_one": "You have {{count}} notification",
"notifications_other": "You have {{count}} notifications",
"messages_one": "{{count}} message",
"messages_other": "{{count}} messages"
}
function ItemCount({ count }: { count: number }) {
const { t } = useTranslation();
return (
<span>
{t('items', { count, defaultValue: '{{count}} items' })}
</span>
);
}
Formatting
import { format } from 'i18next';
const date = new Date('2024-01-15');
format(date, 'EEEE, MMMM d, yyyy', { lng: 'en' });
format(date, 'EEEE, MMMM d, yyyy', { lng: 'es' });
format(date, 'yyyy/MM/dd', { lng: 'zh' });
const number = 1234567.89;
format(number, 'currency', { lng: 'en', currency: 'USD' });
format(number, 'currency', { lng: 'de', currency: 'EUR' });
format(date, 'relativeTime', { lng: 'en', addSuffix: true });
format(date, , { : , : });
RTL Support
.content {
padding-left: 16px;
padding-right: 16px;
margin-left: auto;
margin-right: 0;
text-align: left;
border-left: 2px solid blue;
border-right: none;
}
[dir="rtl"] .content {
padding-left: 16px;
padding-right: 16px;
margin-left: 0;
margin-right: auto;
text-align: right;
border-left: none;
border-right: 2px solid blue;
}
.element {
padding-inline-start: 16px;
padding-inline-end: 16px;
margin-inline-start: auto;
margin-inline-end: 0;
border-inline-start: 2px solid blue;
border-inline-end: none;
}
.bidi-text {
direction: ltr;
unicode-bidi: isolate;
}
Testing i18n
import i18next from 'i18n';
describe('Internationalization', () => {
beforeEach(() => {
i18next.changeLanguage('en');
});
it('translates common actions', () => {
expect(i18next.t('common.save')).toBe('Save');
expect(i18next.t('common.cancel')).toBe('Cancel');
});
it('handles plurals', () => {
expect(i18next.t('items', { count: 1 })).toBe('1 item');
expect(i18next.t('items', { count: 5 })).toBe('5 items');
});
it('handles interpolation', () => {
expect(i18next.t('user.welcome', { name: 'John' }))
.toBe('Welcome, John!');
});
it(, {
i18next.();
(i18next.()).();
});
(, {
(i18next.()).();
});
});
Best Practices
1. Keys should be semantic, not literal
BAD: "save_button_text"
GOOD: "common.save"
2. Never concatenate strings
BAD: "Hello " + name + ", you have " + count + " messages"
GOOD: "user.welcome" with interpolation
3. Use namespaces for organization
common, user, validation, errors, etc.
4. Design for variable content
Account for text expansion (German +30%)
5. Consider date/time formats
Different regions use different formats
6. Handle currency and numbers
Different decimal separators, thousands separators
7. Test with real content
Not just Lorem Ipsum
8. Use professional translation services
Don't rely on automated translation for production