Agent skill
i18n-localization
Quellcode ansehen: travisjneuman/.claude
Installation
npx skills add travisjneuman/.claude --skill i18n-localization130
Installationen
EU-hosted inference API
Power your AI agent skills with open-source models.
Drop-in OpenAI-compatible API. No data leaves Europe.
MiniMax
MiniMax M3
$0.40 / $1.40
per M tokens
Z.ai
GLM 5.3 Flash
$0.20 / $0.60
per M tokens
MoonshotAI
Kimi K3
$4.00 / $18.00
per M tokens
DeepSeek
DeepSeek V4 Pro
$1.80 / $3.60
per M tokens
Internationalization & Localization
Comprehensive guide for building globally-ready applications.
Key Concepts
Terminology
i18n (Internationalization):
- Engineering for multiple languages/regions
- Building the infrastructure
- Happens once, during development
L10n (Localization):
- Adapting for specific locale
- Translation, formatting, content
- Happens per locale, ongoing
Locale:
- Language + Region combination
- Format: language-REGION (e.g., en-US, pt-BR)
- Determines formatting rulesWhat Needs Localization?
| Category | Examples |
|---|---|
| Text | UI labels, messages, errors |
| Numbers | 1,234.56 vs 1.234,56 |
| Dates | MM/DD/YYYY vs DD/MM/YYYY |
| Times | 12-hour vs 24-hour |
| Currency | $1,234.56 vs €1.234,56 |
| Plurals | 1 item vs 2 items vs 5 items |
| Direction | LTR vs RTL |
| Images | Cultural appropriateness |
| Colors | Cultural significance |
| Names | Order, formality |
Text & Messages
Externalize All Strings
// DON'T: Hardcoded strings
const message = "Welcome back, " + username;
// DO: Externalized, translatable
const message = t('welcome_back', { name: username });
// Translation file (en.json)
{
"welcome_back": "Welcome back, {{name}}"
}
// Translation file (es.json)
{
"welcome_back": "Bienvenido de nuevo, {{name}}"
}Message Format
// ICU Message Format (recommended)
// Handles plurals, select, dates, numbers
// Simple
"greeting": "Hello, {name}!"
// Plural
"items_count": "{count, plural, =0 {No items} one {# item} other {# items}}"
// Select (gender, etc.)
"notification": "{gender, select, male {He} female {She} other {They}} liked your post"
// Nested
"cart": "{itemCount, plural,
=0 {Your cart is empty}
one {You have # item in your cart}
other {You have # items in your cart}
}"Pluralization
Languages have different plural rules:
English: 1 (one), 2+ (other)
French: 0-1 (one), 2+ (other)
Russian: Complex rules for 1, 2-4, 5-20, 21, etc.
Arabic: 6 plural forms!
Chinese: No plural forms
Always use library pluralization, never DIY:
- react-intl / formatjs
- i18next
- Intl.PluralRules APIDate & Time
JavaScript Intl API
// Date formatting
const date = new Date("2024-03-15");
new Intl.DateTimeFormat("en-US").format(date);
// "3/15/2024"
new Intl.DateTimeFormat("de-DE").format(date);
// "15.3.2024"
new Intl.DateTimeFormat("ja-JP").format(date);
// "2024/3/15"
// With options
new Intl.DateTimeFormat("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
}).format(date);
// "Friday, March 15, 2024"Time Zones
// Always store in UTC
const utcDate = new Date().toISOString();
// "2024-03-15T14:30:00.000Z"
// Display in user's timezone
new Intl.DateTimeFormat("en-US", {
timeZone: "America/New_York",
dateStyle: "full",
timeStyle: "long",
}).format(new Date(utcDate));
// "Friday, March 15, 2024 at 10:30:00 AM EDT"
// Get user's timezone
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;Relative Time
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
rtf.format(-1, "day"); // "yesterday"
rtf.format(1, "day"); // "tomorrow"
rtf.format(-3, "hour"); // "3 hours ago"
rtf.format(2, "week"); // "in 2 weeks"
// For automatic unit selection, use a library like date-fns
import { formatDistanceToNow } from "date-fns";
formatDistanceToNow(date, { addSuffix: true });
// "3 days ago"Numbers & Currency
Number Formatting
const number = 1234567.89;
new Intl.NumberFormat("en-US").format(number);
// "1,234,567.89"
new Intl.NumberFormat("de-DE").format(number);
// "1.234.567,89"
new Intl.NumberFormat("fr-FR").format(number);
// "1 234 567,89"
// Percentages
new Intl.NumberFormat("en-US", {
style: "percent",
minimumFractionDigits: 1,
}).format(0.256);
// "25.6%"Currency Formatting
const amount = 1234.56;
new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount);
// "$1,234.56"
new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR",
}).format(amount);
// "1.234,56 €"
new Intl.NumberFormat("ja-JP", {
style: "currency",
currency: "JPY",
}).format(amount);
// "¥1,235" (no decimal for JPY)Currency Best Practices
// Store amount + currency code
interface Money {
amount: number; // In smallest unit (cents)
currency: string; // ISO 4217 code (USD, EUR, etc.)
}
// Format for display
function formatMoney(money: Money, locale: string): string {
return new Intl.NumberFormat(locale, {
style: "currency",
currency: money.currency,
}).format(money.amount / 100); // Convert cents to units
}
// Handle exchange rates on backend
// Never convert currencies client-sideRTL (Right-to-Left) Support
RTL Languages
RTL Languages:
- Arabic (ar)
- Hebrew (he)
- Persian/Farsi (fa)
- Urdu (ur)
Layout considerations:
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ Logo Menu Settings │ │ Settings Menu Logo │
│ ← Back │ │ Back → │
│ [Icon] Label │ │ Label [Icon] │
│ Next → │ │ ← Next │
└─────────────────────────────┘ └─────────────────────────────┘
LTR Layout RTL LayoutCSS for RTL
/* Use logical properties */
/* DON'T: Physical properties */
.element {
margin-left: 10px;
padding-right: 20px;
text-align: left;
float: left;
}
/* DO: Logical properties */
.element {
margin-inline-start: 10px;
padding-inline-end: 20px;
text-align: start;
float: inline-start;
}
/* Set direction based on locale */
html[dir="rtl"] {
direction: rtl;
}
/* Or use :dir() pseudo-class */
.icon:dir(rtl) {
transform: scaleX(-1); /* Mirror icons */
}
/* Flexbox auto-reverses in RTL */
.container {
display: flex;
/* No need to change for RTL */
}HTML Direction
<!-- Set at document level -->
<html lang="ar" dir="rtl">
<!-- Or dynamically -->
<div dir="auto">
<!-- Auto-detects text direction -->
مرحبا بالعالم
</div>
<!-- Isolate bidirectional text -->
<p>The word <bdi>مرحبا</bdi> means "hello".</p>
</html>Implementation (React)
react-intl Setup
// src/i18n/index.ts
import { createIntl, createIntlCache } from '@formatjs/intl';
const cache = createIntlCache();
const messages = {
en: () => import('./locales/en.json'),
es: () => import('./locales/es.json'),
fr: () => import('./locales/fr.json'),
};
export async function loadMessages(locale: string) {
const loader = messages[locale] || messages.en;
return (await loader()).default;
}
// App.tsx
import { IntlProvider } from 'react-intl';
function App() {
const [locale, setLocale] = useState('en');
const [messages, setMessages] = useState({});
useEffect(() => {
loadMessages(locale).then(setMessages);
}, [locale]);
return (
<IntlProvider locale={locale} messages={messages}>
<MainApp />
</IntlProvider>
);
}Using Translations
import { FormattedMessage, useIntl } from "react-intl";
function ProductCard({ product }) {
const intl = useIntl();
return (
<div>
{/* Component-based */}
<h2>
<FormattedMessage id="product.title" values={{ name: product.name }} />
</h2>
{/* Hook-based (for attributes, etc.) */}
<img
src={product.image}
alt={intl.formatMessage({ id: "product.image_alt" })}
/>
{/* Numbers */}
<p>
<FormattedNumber
value={product.price}
style="currency"
currency="USD"
/>
</p>
{/* Dates */}
<p>
<FormattedDate value={product.createdAt} dateStyle="medium" />
</p>
{/* Plurals */}
<p>
<FormattedMessage
id="product.reviews"
values={{ count: product.reviewCount }}
/>
</p>
</div>
);
}Translation Files
// locales/en.json
{
"product.title": "{name}",
"product.image_alt": "Photo of {name}",
"product.reviews": "{count, plural, =0 {No reviews} one {# review} other {# reviews}}",
"cart.empty": "Your cart is empty",
"cart.checkout": "Proceed to checkout"
}
// locales/es.json
{
"product.title": "{name}",
"product.image_alt": "Foto de {name}",
"product.reviews": "{count, plural, =0 {Sin reseñas} one {# reseña} other {# reseñas}}",
"cart.empty": "Tu carrito está vacío",
"cart.checkout": "Proceder al pago"
}Implementation (Node.js)
i18next Setup
import i18next from "i18next";
import Backend from "i18next-fs-backend";
await i18next.use(Backend).init({
lng: "en",
fallbackLng: "en",
supportedLngs: ["en", "es", "fr", "de"],
backend: {
loadPath: "./locales/{{lng}}/{{ns}}.json",
},
interpolation: {
escapeValue: false,
},
});
// Usage
const t = i18next.t;
t("welcome", { name: "John" });
// "Welcome, John!"
// Change language
await i18next.changeLanguage("es");Express Middleware
import { Request, Response, NextFunction } from "express";
function detectLocale(req: Request, res: Response, next: NextFunction) {
// Priority: Query param > Cookie > Accept-Language header > Default
const locale =
req.query.locale ||
req.cookies.locale ||
req.acceptsLanguages(["en", "es", "fr"]) ||
"en";
req.locale = locale;
req.t = i18next.getFixedT(locale);
next();
}
app.use(detectLocale);
app.get("/api/greeting", (req, res) => {
res.json({
message: req.t("greeting", { name: req.user.name }),
});
});Translation Workflow
File Structure
locales/
├── en/
│ ├── common.json # Shared strings
│ ├── auth.json # Auth-related
│ ├── products.json # Product-related
│ └── errors.json # Error messages
├── es/
│ └── ...
├── fr/
│ └── ...
└── _source/
└── en.json # Source of truthKey Naming Convention
{
// Feature.element.description
"auth.login.button": "Sign In",
"auth.login.error.invalid_credentials": "Invalid email or password",
// Or flat with prefixes
"btn_login": "Sign In",
"err_invalid_credentials": "Invalid email or password"
// Consistent, descriptive, unique
}Translation Management
TOOLS:
- Lokalise, Crowdin, Phrase (SaaS platforms)
- Weblate (open source)
- POEditor, Transifex
WORKFLOW:
1. Extract strings from code
2. Upload to translation platform
3. Translators work in context
4. Review translations
5. Download and integrate
6. Test in app
7. Repeat for changes
AUTOMATION:
- CI/CD integration
- Automatic string extraction
- Translation memory
- Machine translation + human reviewTesting
Pseudo-Localization
// Transforms: "Hello" → "[Ħëľľö!!!]"
// Helps identify:
// - Hardcoded strings
// - Text truncation issues
// - Character encoding problems
// - Layout issues with longer text
function pseudoLocalize(text: string): string {
const charMap: Record<string, string> = {
a: "ä",
b: "β",
c: "ç",
d: "δ",
e: "ë",
// ... etc
};
const transformed = text
.split("")
.map((c) => charMap[c.toLowerCase()] || c)
.join("");
// Add padding (most translations are 30% longer)
const padding = "!".repeat(Math.ceil(text.length * 0.3));
return `[${transformed}${padding}]`;
}Locale Testing Checklist
FOR EACH LOCALE:
□ All strings translated
□ No hardcoded text visible
□ Dates format correctly
□ Numbers format correctly
□ Currency displays properly
□ Plurals work for all cases
□ UI accommodates text length
□ RTL layout correct (if applicable)
□ Images are appropriate
□ No encoding issues
□ Forms validate appropriatelyBest Practices
DO:
- Externalize ALL user-facing strings
- Use ICU message format for complex strings
- Support locale switching without reload
- Test with pseudo-localization
- Store dates in UTC, display in local time
- Use native Intl APIs when possible
- Plan for text expansion (30-50% longer)
- Support RTL from the start
DON'T:
- Hardcode any user-facing text
- Concatenate strings for messages
- Assume date/number formats
- Handle plurals manually
- Forget about cultural context
- Translate in code (use translation files)
- Ignore bidirectional text issues
- Skip testing with real languages
Checklist
Initial Setup
- Choose i18n library
- Set up translation file structure
- Configure locale detection
- Add locale switcher UI
- Set up RTL support
Per Feature
- Extract all strings to translation files
- Use message format for variables
- Handle plurals correctly
- Format dates/numbers/currency
- Test with pseudo-localization
- Review text in context
Launch
- Professional translations complete
- All locales tested
- RTL layouts verified
- SEO for multi-language (hreflang)
- Locale-specific content reviewed
Installationen
Sicherheitsprüfung
Quellcode ansehen
travisjneuman/.claude
Mehr aus dieser Quelle
Give agents clean web context
Search and extract the public web as Markdown or structured JSON through one API or hosted MCP server.
Verwandte Skills
Mehr aus dieser Quelle: travisjneuman/.claude
So verwenden Sie diesen Skill
Install i18n-localization by running npx skills add travisjneuman/.claude --skill i18n-localization in your project directory. Führen Sie den obigen Installationsbefehl in Ihrem Projektverzeichnis aus. Die Skill-Datei wird von GitHub heruntergeladen und in Ihrem Projekt platziert.
Keine Konfiguration erforderlich. Ihr KI-Agent (Claude Code, Cursor, Windsurf usw.) erkennt installierte Skills automatisch und nutzt sie als Kontext bei der Code-Generierung.
Der Skill verbessert das Verständnis Ihres Agenten für i18n-localization, und hilft ihm, etablierte Muster zu befolgen, häufige Fehler zu vermeiden und produktionsreifen Code zu erzeugen.
Was Sie erhalten
Skills sind Klartext-Anweisungsdateien — kein ausführbarer Code. Sie kodieren Expertenwissen über Frameworks, Sprachen oder Tools, das Ihr KI-Agent liest, um seine Ausgabe zu verbessern. Das bedeutet null Laufzeit-Overhead, keine Abhängigkeitskonflikte und volle Transparenz: Sie können jede Anweisung vor der Installation lesen und prüfen.
Kompatibilität
Dieser Skill funktioniert mit jedem KI-Coding-Agenten, der das skills.sh-Format unterstützt, einschließlich Claude Code (Anthropic), Cursor, Windsurf, Cline, Aider und anderen Tools, die projektbezogene Kontextdateien lesen. Skills sind auf Transportebene framework-agnostisch — der Inhalt bestimmt, für welche Sprache oder welches Framework er gilt.