🎯

i18n-localization

🎯Skill

from travisjneuman/.claude

VibeIndex|
What it does

Enables seamless internationalization by externalizing strings, supporting multi-language translations, and handling complex locale-specific formatting rules.

πŸ“¦

Part of

travisjneuman/.claude(62 items)

i18n-localization

Installation

git cloneClone repository
git clone https://github.com/travisjneuman/.claude.git ~/.claude
Install ScriptRun install script
curl -fsSL https://raw.githubusercontent.com/travisjneuman/.claude/master/scripts/install.sh | bash
git cloneClone repository
git clone --recurse-submodules https://github.com/travisjneuman/.claude.git ~/.claude
npxRun with npx
npx vite-bundle-visualizer
npxRun with npx
npx knip
Server ConfigurationMCP server configuration block
{ "mcpServers": { // ─────────────────────────────────────────────────────...
πŸ“– Extracted from docs: travisjneuman/.claude
3Installs
-
AddedFeb 4, 2026

Skill Details

SKILL.md

Internationalization and localization for global applications. Use when adding multi-language support, handling regional formats, or preparing apps for global markets.

Overview

# 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 rules

```

What 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

```typescript

// 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

```typescript

// 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 API

```

---

Date & Time

JavaScript Intl API

```typescript

// 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

```typescript

// 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

```typescript

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

```typescript

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

```typescript

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

```typescript

// 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-side

```

---

RTL (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 Layout

```

CSS for RTL

```css

/ 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

```html

Ω…Ψ±Ψ­Ψ¨Ψ§ Ψ¨Ψ§Ω„ΨΉΨ§Ω„Ω…

The word Ω…Ψ±Ψ­Ψ¨Ψ§ means "hello".

```

---

Implementation (React)

react-intl Setup

```typescript

// 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 (

);

}

```

Using Translations

```tsx

import { FormattedMessage, useIntl } from "react-intl";

function ProductCard({ product }) {

const intl = useIntl();

return (

{/ Component-based /}

{/ Hook-based (for attributes, etc.) /}

src={product.image}

alt={intl.formatMessage({ id: "product.image_alt" })}

/>

{/ Numbers /}

value={product.price}

style="currency"

currency="USD"

/>

{/ Dates /}

{/ Plurals /}

id="product.reviews"

values={{ count: product.reviewCount }}

/>

);

}

```

Translation Files

```json

// 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

```typescript

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

```typescript

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 truth

```

Key Naming Convention

```json

{

// 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 review

```

---

Testing

Pseudo-Localization

```typescript

// 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 = {

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 appropriately

```

---

Best 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