Claude-skill-registry i18n (Internationalization) Setup

Designing software applications to adapt to different languages and regions without requiring engineering changes, using translation keys, pluralization, and locale-aware formatting.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/i18n-setup" ~/.claude/skills/majiayu000-claude-skill-registry-i18n-internationalization-setup && rm -rf "$T"
manifest: skills/data/i18n-setup/SKILL.md
source content

i18n (Internationalization) Setup

Current Level: Intermediate
Domain: Internationalization / Frontend


Overview

Internationalization (i18n) is the process of designing software applications to adapt to different languages and regions without requiring engineering changes. Effective i18n uses translation keys, pluralization rules, date/number formatting, and RTL support to create applications that work globally.

What is i18n

i18n vs l10n

Aspecti18nl10n
Full NameInternationalizationLocalization
FocusMaking app translatableTranslating to specific language
WhenDuring developmentAfter i18n is complete
WhoDevelopersTranslators
OutputCode with translation keysTranslated content

i18n Architecture

┌─────────────────────────────────────────────────────────────────┐
│                      i18n Architecture                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐   │
│  │   Source    │───▶│ Translation │───▶│   Display   │   │
│  │    Code     │    │    Files    │    │   Layer     │   │
│  │             │    │             │    │             │   │
│  │ t('key')    │    │ { "key":    │    │ Hello World │   │
│  │             │    │   "value"  │    │             │   │
│  │             │    │ }           │    │             │   │
│  └─────────────┘    └─────────────┘    └─────────────┘   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Locale

Locale Format

language-COUNTRY

LocaleLanguageCountry
en-US
EnglishUnited States
en-GB
EnglishUnited Kingdom
th-TH
ThaiThailand
pt-BR
PortugueseBrazil
ja-JP
JapaneseJapan

Supported Locales

const SUPPORTED_LOCALES = [
    'en-US',
    'th-TH',
    'ja-JP',
    'pt-BR',
    'de-DE',
    'fr-FR',
    'es-ES',
    'zh-CN'
];

Translation File Structure

JSON Format

{
  "common": {
    "welcome": "Welcome",
    "login": "Login",
    "logout": "Logout"
  },
  "auth": {
    "email": "Email",
    "password": "Password",
    "forgotPassword": "Forgot Password?",
    "signup": "Sign Up"
  },
  "errors": {
    "required": "This field is required",
    "invalidEmail": "Invalid email address",
    "wrongPassword": "Incorrect password"
  }
}

Nested Keys

{
  "user": {
    "profile": {
      "title": "Profile",
      "name": "Name",
      "email": "Email"
    },
    "settings": {
      "title": "Settings",
      "language": "Language",
      "theme": "Theme"
    }
  }
}

Pluralization

{
  "items": {
    "zero": "No items",
    "one": "One item",
    "other": "{{count}} items"
  }
}

i18n Libraries

React

LibraryDescription
react-i18nextMost popular, feature-rich
react-intlFormat.js-based, good for complex formatting
react-intl-universalLightweight, simple

Vue.js

LibraryDescription
vue-i18nOfficial Vue plugin
vue-intlAlternative plugin

Angular

LibraryDescription
@angular/localizeOfficial Angular i18n
ngx-translateCommunity favorite

Node.js

LibraryDescription
i18nextFramework-agnostic
i18n-nodeSimple Node.js i18n

Python

LibraryDescription
BabelPython i18n library
gettextGNU gettext for Python

Java

LibraryDescription
ResourceBundleBuilt-in Java i18n
MessageFormatICU MessageFormat

react-i18next Setup

Installation

npm install i18next react-i18next

Configuration

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

const resources = {
    en: {
        translation: {
            welcome: 'Welcome',
            login: 'Login'
        }
    },
    th: {
        translation: {
            welcome: 'ยินดีต้อนรับ',
            login: 'เข้าสู่ระบบ'
        }
    }
};

i18n.use(initReactI18next({
    resources,
    lng: 'en', // Default language
    fallbackLng: 'en',
    interpolation: { escapeValue: false },
    react: {
        useSuspense: false
    }
}));

export default i18n;

Using Translations

import { useTranslation } from 'react-i18next';

function Welcome() {
    const { t } = useTranslation();
    
    return (
        <div>
            <h1>{t('welcome')}</h1>
            <button>{t('login')}</button>
        </div>
    );
}

Dynamic Content

const { t } = useTranslation();

function Greeting({ name }) {
    return <p>{t('greeting', { name })}</p>;
}

// Translation: "greeting": "Hello, {{name}}"

Pluralization

const { t } = useTranslation();

function ItemCount({ count }) {
    return <p>{t('items', { count })}</p>;
}

// Translation:
// "items_zero": "No items",
// "items_one": "One item",
// "items_other": "{{count}} items"

Translation Keys

Naming Conventions

ConventionExampleWhy
Descriptive
user.profile.title
Self-documenting
Hierarchical
auth.login.email
Organized
Consistent
button.submit
Predictable
Avoid
btn1
,
txt2
Unclear

Namespace Organization

{
  "common": {
    "actions": {
      "save": "Save",
      "cancel": "Cancel",
      "delete": "Delete"
    }
  },
  "auth": {
    "login": {
      "title": "Login",
      "email": "Email",
      "password": "Password",
      "button": "Login"
    },
    "signup": {
      "title": "Sign Up",
      "name": "Name",
      "email": "Email",
      "password": "Password",
      "button": "Sign Up"
    }
  },
  "dashboard": {
    "title": "Dashboard",
    "overview": "Overview",
    "analytics": "Analytics",
    "settings": "Settings"
  }
}

Default Values

const { t } = useTranslation();

function Welcome() {
    return (
        <div>
            <h1>{t('welcome', { defaultValue: 'Welcome' })}</h1>
        </div>
    );
}

Language Detection

Browser Language

function detectBrowserLanguage() {
    return navigator.language || navigator.userLanguage;
}

// Returns: "en-US", "th-TH", etc.

User Preference

// Get from localStorage
function getUserLanguage() {
    return localStorage.getItem('userLanguage') || 'en-US';
}

// Set user preference
function setUserLanguage(language) {
    localStorage.setItem('userLanguage', language);
    i18n.changeLanguage(language);
}

URL Parameter

function getLanguageFromURL() {
    const params = new URLSearchParams(window.location.search);
    return params.get('lang') || getUserLanguage();
}

// Usage: ?lang=th-TH

Subdomain

function getLanguageFromSubdomain() {
    const subdomain = window.location.hostname.split('.')[0];
    const languageMap = {
        'en': 'en-US',
        'th': 'th-TH',
        'ja': 'ja-JP'
    };
    return languageMap[subdomain] || 'en-US';
}

// Usage: en.example.com, th.example.com

Detection Priority

URL Parameter → User Preference → Browser Language → Default
function getLanguage() {
    // 1. Check URL parameter
    const urlLang = getLanguageFromURL();
    if (urlLang && SUPPORTED_LOCALES.includes(urlLang)) {
        return urlLang;
    }
    
    // 2. Check user preference
    const userLang = getUserLanguage();
    if (userLang && SUPPORTED_LOCALES.includes(userLang)) {
        return userLang;
    }
    
    // 3. Check browser language
    const browserLang = detectBrowserLanguage();
    if (browserLang && SUPPORTED_LOCALES.includes(browserLang)) {
        return browserLang;
    }
    
    // 4. Default
    return 'en-US';
}

Language Switching

Language Switcher Component

import { useTranslation } from 'react-i18next';

function LanguageSwitcher() {
    const { i18n } = useTranslation();
    const { changeLanguage } = i18n;
    
    const handleLanguageChange = (language) => {
        changeLanguage(language);
        setUserLanguage(language);
    };
    
    return (
        <select value={i18n.language} onChange={(e) => handleLanguageChange(e.target.value)}>
            <option value="en-US">English</option>
            <option value="th-TH">ไทย</option>
            <option value="ja-JP">日本語</option>
            <option value="pt-BR">Português</option>
        </select>
    );
}

Persist Preference

function setUserLanguage(language) {
    localStorage.setItem('userLanguage', language);
    
    // Also send to server if user is logged in
    if (isLoggedIn()) {
        fetch('/api/user/language', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ language })
        });
    }
}

Reload Content

function changeLanguage(language) {
    i18n.changeLanguage(language);
    setUserLanguage(language);
    
    // Optional: Reload page to apply new language
    // window.location.reload();
}

RTL (Right-to-Left) Support

RTL Languages

LanguageDirection
ArabicRTL
HebrewRTL
PersianRTL
UrduRTL

Setting Direction

import { useTranslation } from 'react-i18next';

function App() {
    const { i18n } = useTranslation();
    const isRTL = ['ar', 'he', 'fa', 'ur'].includes(i18n.language.split('-')[0]);
    
    return (
        <html lang={i18n.language} dir={isRTL ? 'rtl' : 'ltr'}>
            {/* App content */}
        </html>
    );
}

CSS Logical Properties

/* Logical properties instead of physical properties */
.container {
    margin-inline-start: 20px;  /* margin-left for LTR, margin-right for RTL */
    padding-inline-end: 20px;   /* padding-right for LTR, padding-left for RTL */
    text-align: start;            /* text-align-left for LTR, text-align-right for RTL */
}

/* Flexbox */
.flex-container {
    flex-direction: row;  /* Works with logical properties */
    gap: 20px;
}

Flipping Icons

/* Flip icons for RTL */
[dir="rtl"] .icon-arrow {
    transform: scaleX(-1);
}

Date and Number Formatting

Intl.DateTimeFormat

function formatDate(date, locale) {
    return new Intl.DateTimeFormat(locale, {
        year: 'numeric',
        month: 'long',
        day: 'numeric'
    }).format(date);
}

// Usage
const date = new Date('2024-01-15');
console.log(formatDate(date, 'en-US')); // January 15, 2024
console.log(formatDate(date, 'th-TH')); // 15 มกราคม 2567

Intl.NumberFormat

function formatNumber(number, locale) {
    return new Intl.NumberFormat(locale).format(number);
}

// Usage
console.log(formatNumber(1234.56, 'en-US')); // 1,234.56
console.log(formatNumber(1234.56, 'th-TH')); // 1,234.56
console.log(formatNumber(1234.56, 'de-DE')); // 1.234,56

Currency Formatting

function formatCurrency(amount, currency, locale) {
    return new Intl.NumberFormat(locale, {
        style: 'currency',
        currency: currency
    }).format(amount);
}

// Usage
console.log(formatCurrency(99.99, 'USD', 'en-US')); // $99.99
console.log(formatCurrency(99.99, 'THB', 'th-TH')); // ฿99.99
console.log(formatCurrency(99.99, 'EUR', 'de-DE')); // 99,99 €

Translation Workflow

Export for Translation

// Extract translation keys
const translations = {
    en: require('./locales/en.json'),
    th: require('./locales/th.json')
};

// Export to CSV
function exportToCSV(translations, language) {
    const keys = Object.keys(translations[language].translation);
    const values = keys.map(key => translations[language].translation[key]);
    
    const csv = [
        keys.join(','),
        values.join(',')
    ].join('\n');
    
    // Download CSV
    const blob = new Blob([csv], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `${language}.csv`;
    a.click();
}

Translation Management Platforms

PlatformFeaturesPricing
LokaliseCrowdsourced, version control$$
CrowdinCrowdsourced, integrations$$
PhraseIn-context editor, integrations$$$
POEditorSimple, affordable$

Git-Based Workflow

┌─────────────────────────────────────────────────────────────────┐
│  Git-Based Translation Workflow                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. Developer ──▶ 2. Export Keys ──▶ 3. Upload to  │
│     Extracts       to CSV        Platform      │
│     Keys                                       │
│                                                  │
│  4. Translator ──▶ 5. Download       │
│     Translates      Translations    │
│                                                  │
│  6. Developer ──▶ 7. Import to     │
│     Imports         Project        │
│                                                  │
│  8. Commit ──▶ 9. Deploy        │
│     Changes        Translations   │
└─────────────────────────────────────────────────────────────────┘

Missing Translations

Show Key (Development)

const { t } = useTranslation();

function Welcome() {
    return (
        <div>
            <h1>{t('welcome', { defaultValue: 'Welcome' })}</h1>
            {/* Shows 'welcome' if translation missing */}
        </div>
    );
}

Show Default Language (Production)

const { t } = useTranslation();

function Welcome() {
    return (
        <div>
            <h1>{t('welcome', { defaultValue: 'Welcome' })}</h1>
            {/* Shows 'welcome' if translation missing */}
        </div>
    );
}

// Configure fallback language
i18n.use(initReactI18next({
    fallbackLng: 'en', // Fallback to English
    // ... other config
}));

Log Missing Keys

function missingKeyHandler(lng, ns, key) {
    console.warn(`Missing translation: ${lng}.${ns}.${key}`);
    
    // Send to analytics
    if (window.analytics) {
        window.analytics.track('missing_translation', {
            language: lng,
            namespace: ns,
            key: key
        });
    }
    
    return key; // Return key as fallback
}

i18n.use(initReactI18next({
    missingKeyHandler,
    // ... other config
}));

SEO Considerations

Separate URLs per Language

/en/         (English)
/th/          (Thai)
/ja/          (Japanese)
/pt-BR/       (Portuguese)

Hreflang Tags

<link rel="alternate" hreflang="en" href="https://example.com/en/" />
<link rel="alternate" hreflang="th" href="https://example.com/th/" />
<link rel="alternate" hreflang="ja" href="https://example.com/ja/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/" />

Language Selector

function LanguageSelector() {
    const languages = [
        { code: 'en', name: 'English', url: '/en/' },
        { code: 'th', name: 'ไทย', url: '/th/' },
        { code: 'ja', name: '日本語', url: '/ja/' }
    ];
    
    return (
        <nav aria-label="Language">
            {languages.map(lang => (
                <a key={lang.code} href={lang.url}>
                    {lang.name}
                </a>
            ))}
        </nav>
    );
}

Translated Meta Tags

import { useTranslation } from 'react-i18next';

function MetaTags() {
    const { t } = useTranslation();
    
    return (
        <Helmet>
            <title>{t('meta.title')}</title>
            <meta name="description" content={t('meta.description')} />
            <meta name="keywords" content={t('meta.keywords')} />
        </Helmet>
    );
}

Performance

Lazy Load Translations

import { useTranslation } from 'react-i18next';

function LazyComponent() {
    const { t, ready } = useTranslation('common', { useSuspense: false });
    
    if (!ready) {
        return <div>Loading...</div>;
    }
    
    return <div>{t('welcome')}</div>;
}

Bundle Only Needed Languages

// webpack.config.js
module.exports = {
    // ...
    plugins: [
        new i18nextWebpackPlugin({
            languages: ['en', 'th', 'ja'], // Only bundle needed languages
            defaultLanguage: 'en'
        })
    ]
};

Cache Translations

// Simple in-memory cache
const translationCache = new Map();

function getTranslation(language, key) {
    const cacheKey = `${language}:${key}`;
    
    if (translationCache.has(cacheKey)) {
        return translationCache.get(cacheKey);
    }
    
    const translation = loadTranslation(language, key);
    translationCache.set(cacheKey, translation);
    return translation;
}

Testing

Test All Languages

describe('i18n', () => {
    const languages = ['en', 'th', 'ja'];
    
    languages.forEach(language => {
        i18n.changeLanguage(language);
        
        test('welcome message', () => {
            const { t } = renderHook();
            expect(t('welcome')).toBeDefined();
        });
        
        test('pluralization', () => {
            const { t } = renderHook();
            expect(t('items', { count: 1 })).toBeDefined();
            expect(t('items', { count: 5 })).toBeDefined();
        });
    });
});

Screenshot Testing

# Take screenshots in different languages
npm run test:visual --locale=en
npm run test:visual --locale=th
npm run test:visual --locale=ja

Pseudo-Localization

// Test string expansion
function pseudoLocalize(text) {
    return text.split('').join(' ');
}

// "Hello" → "H e l l o "

Common Mistakes

Hardcoded Strings

// Bad: Hardcoded string
<h1>Welcome</h1>

// Good: Translated string
<h1>{t('welcome')}</h1>

Concatenating Translations

// Bad: Concatenation
<p>{t('hello')} {name}!</p>

// Good: Use interpolation
<p>{t('greeting', { name })}</p>

Not Handling Pluralization

// Bad: No pluralization
<p>You have {count} items</p>

// Good: With pluralization
<p>{t('items', { count })}</p>

Not Supporting RTL

// Bad: No RTL support
<div className="container">

// Good: RTL support
<div className="container" dir={isRTL ? 'rtl' : 'ltr'}>

Implementation Examples

React i18next Setup

// i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import HttpApi from 'i18next-http-backend';

const resources = {
    en: {
        translation: {
            welcome: 'Welcome',
            login: 'Login',
            logout: 'Logout'
        }
    },
    th: {
        translation: {
            welcome: 'ยินดีต้อนรับ',
            login: 'เข้าสู่ระบบ',
            logout: 'ออกจากระบบ'
        }
    }
};

i18n
    .use(HttpApi) // Load translations from API
    .use(LanguageDetector) // Detect user language
    .use(initReactI18next({
        resources,
        fallbackLng: 'en',
        interpolation: { escapeValue: false },
        react: {
            useSuspense: false
        }
    }))
    .init();

export default i18n;

Translation File Example

// locales/en.json
{
  "common": {
    "welcome": "Welcome",
    "login": "Login",
    "logout": "Logout",
    "save": "Save",
    "cancel": "Cancel"
  },
  "auth": {
    "login": {
      "title": "Login to Your Account",
      "email": "Email Address",
      "password": "Password",
      "button": "Login",
      "forgot": "Forgot Password?",
      "signup": "Don't have an account? Sign up"
    },
    "signup": {
      "title": "Create Your Account",
      "name": "Full Name",
      "email": "Email Address",
      "password": "Password",
      "confirmPassword": "Confirm Password",
      "button": "Sign Up",
      "login": "Already have an account? Login"
    }
  },
  "dashboard": {
    "title": "Dashboard",
    "overview": "Overview",
    "analytics": "Analytics",
    "settings": "Settings",
    "logout": "Logout"
  }
}
// locales/th.json
{
  "common": {
    "welcome": "ยินดีต้อนรับ",
    "login": "เข้าสู่ระบบ",
    "logout": "ออกจากระบบ",
    "save": "บันทึก",
    "cancel": "ยกเลิก"
  },
  "auth": {
    "login": {
      "title": "เข้าสู่ระบบของคุณ",
      "email": "อีเมล",
      "password": "รหัสผ่าน",
      "button": "เข้าสู่ระบบ",
      "forgot": "ลืมรหัสผ่าน?",
      "signup": "ยังไม่มีบัญชี? ลงทะเบียน"
    },
    "signup": {
      "title": "สร้างบัญชีของคุณ",
      "name": "ชื่อเต็ม",
      "email": "อีเมล",
      "password": "รหัสผ่าน",
      "confirmPassword": "ยืนยันรหัสผ่าน",
      "button": "ลงทะเบียน",
      "login": "มีบัญชีอยู่? เข้าสู่ระบบ"
    }
  },
  "dashboard": {
    "title": "แดชบอร์ด",
    "overview": "ภาพระกับ",
    "analytics": "การวิเคราะห์",
    "settings": "การตั้งค่า",
    "logout": "ออกจากระบบ"
  }
}

Language Switcher Component

import { useTranslation } from 'react-i18next';

function LanguageSwitcher() {
    const { i18n } = useTranslation();
    const { changeLanguage } = i18n;
    
    const languages = [
        { code: 'en-US', name: 'English', flag: '🇺🇸' },
        { code: 'th-TH', name: 'ไทย', flag: '🇹🇭' },
        { code: 'ja-JP', name: '日本語', flag: '🇯🇵' },
        { code: 'pt-BR', name: 'Português', flag: '🇧🇷' }
    ];
    
    const handleLanguageChange = (language) => {
        changeLanguage(language);
        localStorage.setItem('userLanguage', language);
    };
    
    return (
        <div className="language-switcher">
            {languages.map(lang => (
                <button
                    key={lang.code}
                    onClick={() => handleLanguageChange(lang.code)}
                    className={i18n.language === lang.code ? 'active' : ''}
                >
                    <span className="flag">{lang.flag}</span>
                    <span className="name">{lang.name}</span>
                </button>
            ))}
        </div>
    );
}

Summary Checklist

Setup

  • i18n library installed
  • Translation file structure defined
  • Default language configured
  • Language detection implemented
  • Language switcher created

Content

  • All strings extracted to translation files
  • Translation keys are descriptive
  • Namespaces organized
  • Pluralization handled
  • Variables supported

Features

  • RTL support implemented
  • Date/time formatting
  • Number/currency formatting
  • Testing completed

---

## Quick Start

### React i18next Setup

```bash
npm install i18next react-i18next i18next-browser-languagedetector
// i18n.js
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources: {
      en: {
        translation: {
          welcome: 'Welcome',
          goodbye: 'Goodbye'
        }
      },
      th: {
        translation: {
          welcome: 'ยินดีต้อนรับ',
          goodbye: 'ลาก่อน'
        }
      }
    },
    fallbackLng: 'en',
    interpolation: {
      escapeValue: false
    }
  })

Usage in Components

import { useTranslation } from 'react-i18next'

function App() {
  const { t, i18n } = useTranslation()
  
  return (
    <div>
      <h1>{t('welcome')}</h1>
      <button onClick={() => i18n.changeLanguage('th')}>
        Switch to Thai
      </button>
    </div>
  )
}

Production Checklist

  • i18n Library: Choose and configure i18n library
  • Translation Files: Organize translation files by namespace
  • Translation Keys: Use descriptive, hierarchical keys
  • Pluralization: Handle plural forms correctly
  • Variables: Support variables in translations
  • Date/Time: Format dates and times per locale
  • Numbers: Format numbers and currency per locale
  • RTL Support: Support right-to-left languages
  • Language Detection: Auto-detect user language
  • Language Switcher: UI for language selection
  • Testing: Test all languages and locales
  • Fallback: Fallback language configured

Anti-patterns

❌ Don't: Hardcoded Strings

// ❌ Bad - Hardcoded
<h1>Welcome</h1>
<p>Hello, user!</p>
// ✅ Good - Translation keys
<h1>{t('welcome')}</h1>
<p>{t('greeting', { name: user.name })}</p>

❌ Don't: No Pluralization

// ❌ Bad - No pluralization
{t('item', { count: items.length })}  // Always "item"
// ✅ Good - Pluralization
{t('item', { count: items.length })}  // "item" or "items"

❌ Don't: Ignore RTL

/* ❌ Bad - Left-aligned only */
.text {
  text-align: left;
}
/* ✅ Good - RTL-aware */
.text {
  text-align: start;  /* Adapts to direction */
}

Integration Points

  • Localization (
    25-internationalization/localization/
    ) - Content translation
  • Multi-language (
    25-internationalization/multi-language/
    ) - Multi-language support
  • RTL Support (
    25-internationalization/rtl-support/
    ) - Right-to-left languages

Further Reading

Testing

  • All languages tested
  • Screenshot testing completed
  • Pseudo-localization tested
  • RTL layouts tested
  • SEO tags tested