| name | i18n-frameworks |
| description | Framework-specific i18n implementation: i18next + react-i18next (React/Next.js), next-intl (Next.js App Router), Django's i18n (gettext/makemessages), Rails I18n (YAML-based), Localizable.strings + SwiftUI (iOS), Android string resources, and Flutter's ARB format. Concrete setup and usage patterns for each. |
i18n Frameworks
vs i18n-patterns: This skill covers framework-specific setup code (react-i18next, next-intl, Django/Rails i18n, SwiftUI, Flutter ARB). Use i18n-patterns when you need language-agnostic architecture decisions (locale detection strategy, key naming conventions, RTL layout, pluralization rules).
When to Activate
- Setting up i18n in a React, Next.js, Django, Rails, iOS, Android, or Flutter app
- Migrating from a custom solution to a framework-standard approach
- Adding a new locale to an existing i18n setup
- Debugging missing translations or locale switching issues
- Choosing between i18next and next-intl for a new Next.js App Router project
- Configuring lazy-loaded translation namespaces to avoid bundling all locale strings upfront
- Setting up
makemessages and compilemessages workflows in a Django project with multiple languages
- Migrating an iOS app from Localizable.strings to the Xcode 15 String Catalogs format
i18next + react-i18next (React / Vite)
Setup
npm install i18next react-i18next i18next-http-backend i18next-browser-languagedetector
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
ns: ['common', 'auth', 'dashboard'],
defaultNS: 'common',
backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' },
detection: { order: ['querystring', 'localStorage', 'navigator'] },
interpolation: { escapeValue: false },
});
export default i18n;
public/locales/
├── en/
│ ├── common.json
│ ├── auth.json
│ └── dashboard.json
└── de/
├── common.json
├── auth.json
└── dashboard.json
Usage
import { useTranslation } from 'react-i18next';
function LoginButton() {
const { t, i18n } = useTranslation('auth');
return (
<div>
<button>{t('login.submit')}</button>
{/* Switch locale */}
<select
value={i18n.language}
onChange={(e) => i18n.changeLanguage(e.target.value)}
>
<option value="en">English</option>
<option value="de">Deutsch</option>
</select>
</div>
);
}
Translation file structure
{
"login": {
"title": "Sign in",
"submit": "Sign in",
"error": {
"invalid_credentials": "Invalid email or password",
"account_locked": "Account locked. Try again in {{minutes}} minutes."
}
}
}
next-intl (Next.js App Router)
Setup
npm install next-intl
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
locales: ['en', 'de', 'fr'],
defaultLocale: 'en',
});
export const config = { matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'] };
app/
└── [locale]/
├── layout.tsx
└── page.tsx
messages/
├── en.json
└── de.json
Usage (Server Components)
import { getTranslations } from 'next-intl/server';
export default async function HomePage({ params: { locale } }: { params: { locale: string } }) {
const t = await getTranslations({ locale, namespace: 'HomePage' });
return <h1>{t('title')}</h1>;
}
Usage (Client Components)
'use client';
import { useTranslations } from 'next-intl';
export function LoginButton() {
const t = useTranslations('Auth');
return <button>{t('submit')}</button>;
}
Messages file
{
"HomePage": {
"title": "Welcome"
},
"Auth": {
"submit": "Sign in",
"items": "{count, plural, one {# item} other {# items}}"
}
}
Django (Python)
Setup
LANGUAGE_CODE = 'en'
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = [
('en', 'English'),
('de', 'Deutsch'),
('ar', 'العربية'),
]
LOCALE_PATHS = [BASE_DIR / 'locale']
MIDDLEWARE = [
'django.middleware.locale.LocaleMiddleware',
]
locale/
├── de/
│ └── LC_MESSAGES/
│ ├── django.po # source
│ └── django.mo # compiled
└── ar/
└── LC_MESSAGES/
├── django.po
└── django.mo
Extracting and compiling
python manage.py makemessages -l de
python manage.py makemessages -l ar
python manage.py compilemessages
Usage in code
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext
class UserProfile(models.Model):
class Meta:
verbose_name = _('user profile')
verbose_name_plural = _('user profiles')
from django.utils.translation import gettext as _
def my_view(request):
message = _('Hello, %(name)s!') % {'name': request.user.first_name}
def items_message(count):
return ngettext(
'%(count)d item',
'%(count)d items',
count,
) % {'count': count}
Templates
{% load i18n %}
<h1>{% trans "Welcome" %}</h1>
{% blocktrans with name=user.first_name %}
Hello, {{ name }}!
{% endblocktrans %}
{% blocktrans count count=items|length %}
{{ count }} item
{% plural %}
{{ count }} items
{% endblocktrans %}
Rails (Ruby)
Setup
config.i18n.available_locales = [:en, :de, :fr]
config.i18n.default_locale = :en
config.i18n.fallbacks = [I18n.default_locale]
en:
auth:
login:
title: "Sign in"
submit: "Sign in"
error:
invalid: "Invalid email or password"
items:
one: "%{count} item"
other: "%{count} items"
Usage
I18n.t('auth.login.title')
I18n.t('items', count: @items.length)
class ApplicationController < ActionController::Base
before_action :set_locale
def set_locale
I18n.locale = extract_locale || I18n.default_locale
end
private
def extract_locale
parsed_locale = params[:locale]
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
end
<!-- Views -->
<h1><%= t('auth.login.title') %></h1>
<p><%= t('items', count: @items.length) %></p>
SwiftUI (iOS)
Localizable.strings (legacy — Xcode < 15)
// en.lproj/Localizable.strings
"login.submit" = "Sign in";
"login.error.invalid" = "Invalid email or password";
"items.count" = "%d items";
Text(String(localized: "login.submit"))
Text(String(localized: "items.count \(count)"))
String Catalogs (.xcstrings — Xcode 15+, recommended)
Xcode 15 introduces .xcstrings — a single JSON file managing all locales:
{
"sourceLanguage": "en",
"strings": {
"login.submit": {
"localizations": {
"en": { "stringUnit": { "state": "translated", "value": "Sign in" } },
"de": { "stringUnit": { "state": "translated", "value": "Anmelden" } }
}
}
}
}
Pluralization in Swift
let items = String(localized: "\(count) items", table: "Localizable")
Android (Kotlin)
Resource files
<resources>
<string name="login_submit">Sign in</string>
<string name="login_error_invalid">Invalid email or password</string>
<plurals name="items_count">
<item quantity="one">%d item</item>
<item quantity="other">%d items</item>
</plurals>
</resources>
<resources>
<string name="login_submit">Anmelden</string>
<string name="login_error_invalid">Ungültige E-Mail oder Passwort</string>
<plurals name="items_count">
<item quantity="one">%d Element</>
%d Elemente
getString(R.string.login_submit)
resources.getQuantityString(R.plurals.items_count, count, count)
<Button android:text="@string/login_submit" />
Checklist
| Framework | Key task |
|---|
| React + i18next | Namespaces split by feature; Backend plugin for lazy loading |
| Next.js App Router | next-intl middleware; getTranslations in Server Components |
| Django | LocaleMiddleware in middleware; makemessages + compilemessages |
| Rails | before_action :set_locale with I18n.available_locales check |
| SwiftUI | .xcstrings catalog (Xcode 15+); String(localized:) syntax |
| Android | values-<locale>/strings.xml; <plurals> for plural forms |