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.
From clarcnpx claudepluginhub marvinrichter/clarc --plugin clarcThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
vs
i18n-patterns: This skill covers framework-specific setup code (react-i18next, next-intl, Django/Rails i18n, SwiftUI, Flutter ARB). Usei18n-patternswhen you need language-agnostic architecture decisions (locale detection strategy, key naming conventions, RTL layout, pluralization rules).
makemessages and compilemessages workflows in a Django project with multiple languagesnpm install i18next react-i18next i18next-http-backend i18next-browser-languagedetector
// src/i18n.ts
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'], // namespaces = separate JSON files
defaultNS: 'common',
backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' },
detection: { order: ['querystring', 'localStorage', 'navigator'] },
interpolation: { escapeValue: false }, // React already escapes
});
export default i18n;
public/locales/
├── en/
│ ├── common.json
│ ├── auth.json
│ └── dashboard.json
└── de/
├── common.json
├── auth.json
└── dashboard.json
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>
);
}
// public/locales/en/auth.json
{
"login": {
"title": "Sign in",
"submit": "Sign in",
"error": {
"invalid_credentials": "Invalid email or password",
"account_locked": "Account locked. Try again in {{minutes}} minutes."
}
}
}
npm install next-intl
// middleware.ts — locale routing
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
// app/[locale]/page.tsx
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>;
}
'use client';
import { useTranslations } from 'next-intl';
export function LoginButton() {
const t = useTranslations('Auth');
return <button>{t('submit')}</button>;
}
// messages/en.json
{
"HomePage": {
"title": "Welcome"
},
"Auth": {
"submit": "Sign in",
"items": "{count, plural, one {# item} other {# items}}"
}
}
# settings.py
LANGUAGE_CODE = 'en'
USE_I18N = True
USE_L10N = True # locale-aware formatting
USE_TZ = True
LANGUAGES = [
('en', 'English'),
('de', 'Deutsch'),
('ar', 'العربية'),
]
LOCALE_PATHS = [BASE_DIR / 'locale']
MIDDLEWARE = [
# ...
'django.middleware.locale.LocaleMiddleware', # after SessionMiddleware, before CommonMiddleware
# ...
]
locale/
├── de/
│ └── LC_MESSAGES/
│ ├── django.po # source
│ └── django.mo # compiled
└── ar/
└── LC_MESSAGES/
├── django.po
└── django.mo
# Extract all strings marked with gettext
python manage.py makemessages -l de
python manage.py makemessages -l ar
# Compile after translating .po files
python manage.py compilemessages
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext
# In models, forms — use gettext_lazy (evaluated at render time)
class UserProfile(models.Model):
class Meta:
verbose_name = _('user profile')
verbose_name_plural = _('user profiles')
# In views — use gettext (evaluated immediately)
from django.utils.translation import gettext as _
def my_view(request):
message = _('Hello, %(name)s!') % {'name': request.user.first_name}
# Pluralization
def items_message(count):
return ngettext(
'%(count)d item',
'%(count)d items',
count,
) % {'count': count}
{% load i18n %}
<h1>{% trans "Welcome" %}</h1>
<!-- With variable -->
{% blocktrans with name=user.first_name %}
Hello, {{ name }}!
{% endblocktrans %}
<!-- Plural -->
{% blocktrans count count=items|length %}
{{ count }} item
{% plural %}
{{ count }} items
{% endblocktrans %}
# config/application.rb
config.i18n.available_locales = [:en, :de, :fr]
config.i18n.default_locale = :en
config.i18n.fallbacks = [I18n.default_locale]
# config/locales/en.yml
en:
auth:
login:
title: "Sign in"
submit: "Sign in"
error:
invalid: "Invalid email or password"
items:
one: "%{count} item"
other: "%{count} items"
# In controllers / models
I18n.t('auth.login.title')
I18n.t('items', count: @items.length)
# Locale detection in ApplicationController
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>
// en.lproj/Localizable.strings
"login.submit" = "Sign in";
"login.error.invalid" = "Invalid email or password";
"items.count" = "%d items";
// Usage
Text(String(localized: "login.submit"))
// With arguments
Text(String(localized: "items.count \(count)"))
Xcode 15 introduces .xcstrings — a single JSON file managing all locales:
// Localizable.xcstrings (managed by Xcode UI, but here's the format)
{
"sourceLanguage": "en",
"strings": {
"login.submit": {
"localizations": {
"en": { "stringUnit": { "state": "translated", "value": "Sign in" } },
"de": { "stringUnit": { "state": "translated", "value": "Anmelden" } }
}
}
}
}
// en.stringsdict (for plurals with Localizable.strings)
// Or use String(localized:) with pluralization rules in .xcstrings
let items = String(localized: "\(count) items", table: "Localizable")
<!-- res/values/strings.xml (default / English) -->
<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>
<!-- res/values-de/strings.xml (German) -->
<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</item>
<item quantity="other">%d Elemente</item>
</plurals>
</resources>
// Kotlin usage
getString(R.string.login_submit)
resources.getQuantityString(R.plurals.items_count, count, count)
<!-- XML layout -->
<Button android:text="@string/login_submit" />
| 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 |