Back to Skills

internationalization-i18n

aj-geddes
Updated Today
64 views
7
7
View on GitHub
Communicationgeneral

About

This Claude Skill provides comprehensive internationalization (i18n) and localization implementation for multi-language applications. It covers key capabilities like message extraction, translation catalogs, pluralization, and date/number formatting for different locales. Use it when building apps that require language switching, RTL support, or integration with libraries like i18next and gettext.

Documentation

Internationalization (i18n) & Localization

Overview

Comprehensive guide to implementing internationalization and localization in applications. Covers message translation, pluralization, date/time/number formatting, RTL languages, and integration with popular i18n libraries.

When to Use

  • Building multi-language applications
  • Supporting international users
  • Implementing language switching
  • Formatting dates, times, and numbers for different locales
  • Supporting RTL (right-to-left) languages
  • Extracting and managing translation strings
  • Implementing pluralization rules
  • Setting up translation workflows

Instructions

1. i18next (JavaScript/TypeScript)

Basic Setup

// i18n.ts
import i18next from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';

await i18next
  .use(Backend)
  .use(LanguageDetector)
  .init({
    fallbackLng: 'en',
    debug: process.env.NODE_ENV === 'development',

    interpolation: {
      escapeValue: false // React already escapes
    },

    backend: {
      loadPath: '/locales/{{lng}}/{{ns}}.json'
    },

    detection: {
      order: ['querystring', 'cookie', 'localStorage', 'navigator'],
      caches: ['localStorage', 'cookie']
    }
  });

export default i18next;

Translation Files

// locales/en/translation.json
{
  "welcome": "Welcome to our app",
  "greeting": "Hello, {{name}}!",
  "itemCount": "You have {{count}} item",
  "itemCount_plural": "You have {{count}} items",
  "user": {
    "profile": "User Profile",
    "settings": "Settings",
    "logout": "Log out"
  },
  "validation": {
    "required": "This field is required",
    "email": "Please enter a valid email",
    "minLength": "Must be at least {{min}} characters"
  }
}

// locales/es/translation.json
{
  "welcome": "Bienvenido a nuestra aplicación",
  "greeting": "¡Hola, {{name}}!",
  "itemCount": "Tienes {{count}} artículo",
  "itemCount_plural": "Tienes {{count}} artículos",
  "user": {
    "profile": "Perfil de Usuario",
    "settings": "Configuración",
    "logout": "Cerrar sesión"
  },
  "validation": {
    "required": "Este campo es obligatorio",
    "email": "Por favor ingrese un correo válido",
    "minLength": "Debe tener al menos {{min}} caracteres"
  }
}

// locales/fr/translation.json
{
  "welcome": "Bienvenue dans notre application",
  "greeting": "Bonjour, {{name}} !",
  "itemCount": "Vous avez {{count}} article",
  "itemCount_plural": "Vous avez {{count}} articles",
  "user": {
    "profile": "Profil utilisateur",
    "settings": "Paramètres",
    "logout": "Se déconnecter"
  }
}

React Integration

// App.tsx
import { useTranslation } from 'react-i18next';
import './i18n';

export function App() {
  const { t, i18n } = useTranslation();

  const changeLanguage = (lng: string) => {
    i18n.changeLanguage(lng);
  };

  return (
    <div>
      <h1>{t('welcome')}</h1>
      <p>{t('greeting', { name: 'John' })}</p>
      <p>{t('itemCount', { count: 5 })}</p>

      {/* Language switcher */}
      <select
        value={i18n.language}
        onChange={(e) => changeLanguage(e.target.value)}
      >
        <option value="en">English</option>
        <option value="es">Español</option>
        <option value="fr">Français</option>
      </select>
    </div>
  );
}

// Component with namespace
export function UserProfile() {
  const { t } = useTranslation('user');

  return (
    <div>
      <h2>{t('profile')}</h2>
      <button>{t('logout')}</button>
    </div>
  );
}

Node.js/Express Backend

// i18n-middleware.ts
import i18next from 'i18next';
import Backend from 'i18next-fs-backend';
import middleware from 'i18next-http-middleware';

i18next
  .use(Backend)
  .use(middleware.LanguageDetector)
  .init({
    fallbackLng: 'en',
    preload: ['en', 'es', 'fr'],
    backend: {
      loadPath: './locales/{{lng}}/{{ns}}.json'
    }
  });

export const i18nMiddleware = middleware.handle(i18next);

// app.ts
import express from 'express';
import { i18nMiddleware } from './i18n-middleware';

const app = express();
app.use(i18nMiddleware);

app.get('/api/welcome', (req, res) => {
  res.json({
    message: req.t('welcome'),
    greeting: req.t('greeting', { name: 'User' })
  });
});

2. React-Intl (Format.js)

// IntlProvider setup
import { IntlProvider } from 'react-intl';
import messages_en from './translations/en.json';
import messages_es from './translations/es.json';

const messages = {
  en: messages_en,
  es: messages_es
};

export function App() {
  const [locale, setLocale] = useState('en');

  return (
    <IntlProvider locale={locale} messages={messages[locale]}>
      <YourApp />
    </IntlProvider>
  );
}

// Using translations
import { FormattedMessage, useIntl } from 'react-intl';

export function Welcome() {
  const intl = useIntl();

  return (
    <div>
      {/* Basic translation */}
      <h1>
        <FormattedMessage id="welcome" defaultMessage="Welcome" />
      </h1>

      {/* With variables */}
      <p>
        <FormattedMessage
          id="greeting"
          defaultMessage="Hello, {name}!"
          values={{ name: 'John' }}
        />
      </p>

      {/* Pluralization */}
      <p>
        <FormattedMessage
          id="itemCount"
          defaultMessage="{count, plural, =0 {No items} one {# item} other {# items}}"
          values={{ count: 5 }}
        />
      </p>

      {/* In code */}
      <button title={intl.formatMessage({ id: 'submit' })}>
        {intl.formatMessage({ id: 'submit' })}
      </button>
    </div>
  );
}

3. Python i18n (gettext)

# i18n.py
import gettext
import os

class I18n:
    def __init__(self, locale='en', domain='messages'):
        self.locale = locale
        self.domain = domain
        self._translator = None
        self._load_translations()

    def _load_translations(self):
        locale_dir = os.path.join(os.path.dirname(__file__), 'locales')
        try:
            self._translator = gettext.translation(
                self.domain,
                localedir=locale_dir,
                languages=[self.locale]
            )
        except FileNotFoundError:
            # Fall back to NullTranslations (no translation)
            self._translator = gettext.NullTranslations()

    def t(self, message, **kwargs):
        """Translate message with optional variable substitution"""
        translated = self._translator.gettext(message)
        if kwargs:
            return translated.format(**kwargs)
        return translated

    def tn(self, singular, plural, n, **kwargs):
        """Translate with pluralization"""
        translated = self._translator.ngettext(singular, plural, n)
        if kwargs:
            return translated.format(n=n, **kwargs)
        return translated

# Usage
i18n = I18n(locale='es')

print(i18n.t("Welcome to our app"))
print(i18n.t("Hello, {name}!", name="Juan"))
print(i18n.tn("You have {n} item", "You have {n} items", 5))
# Extracting messages for translation
# Install: pip install Babel

# babel.cfg
[python: **.py]

# Extract messages
# pybabel extract -F babel.cfg -o locales/messages.pot .

# Initialize new language
# pybabel init -i locales/messages.pot -d locales -l es

# Compile translations
# pybabel compile -d locales

4. Date and Time Formatting

JavaScript (Intl API)

// date-formatter.ts
export class DateFormatter {
  constructor(private locale: string) {}

  // Format date
  formatDate(date: Date, options?: Intl.DateTimeFormatOptions): string {
    return new Intl.DateTimeFormat(this.locale, options).format(date);
  }

  // Predefined formats
  short(date: Date): string {
    return this.formatDate(date, {
      year: 'numeric',
      month: 'short',
      day: 'numeric'
    });
  }

  long(date: Date): string {
    return this.formatDate(date, {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
      weekday: 'long'
    });
  }

  time(date: Date): string {
    return this.formatDate(date, {
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit'
    });
  }

  relative(date: Date): string {
    const rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: 'auto' });
    const diff = date.getTime() - Date.now();
    const days = Math.round(diff / (1000 * 60 * 60 * 24));

    if (Math.abs(days) < 1) {
      const hours = Math.round(diff / (1000 * 60 * 60));
      return rtf.format(hours, 'hour');
    }

    return rtf.format(days, 'day');
  }
}

// Usage
const enFormatter = new DateFormatter('en-US');
const esFormatter = new DateFormatter('es-ES');
const jaFormatter = new DateFormatter('ja-JP');

const date = new Date('2024-01-15');

console.log(enFormatter.short(date));  // Jan 15, 2024
console.log(esFormatter.short(date));  // 15 ene 2024
console.log(jaFormatter.short(date));  // 2024年1月15日

console.log(enFormatter.relative(new Date(Date.now() - 86400000)));  // yesterday

React-Intl Date Formatting

import { FormattedDate, FormattedTime, FormattedRelativeTime } from 'react-intl';

export function DateDisplay() {
  const date = new Date();

  return (
    <div>
      {/* Date */}
      <FormattedDate
        value={date}
        year="numeric"
        month="long"
        day="numeric"
      />

      {/* Time */}
      <FormattedTime value={date} />

      {/* Relative time */}
      <FormattedRelativeTime
        value={-1}
        unit="day"
        updateIntervalInSeconds={60}
      />
    </div>
  );
}

5. Number and Currency Formatting

// number-formatter.ts
export class NumberFormatter {
  constructor(private locale: string) {}

  // Format number
  formatNumber(value: number, options?: Intl.NumberFormatOptions): string {
    return new Intl.NumberFormat(this.locale, options).format(value);
  }

  // Currency
  currency(value: number, currency: string): string {
    return this.formatNumber(value, {
      style: 'currency',
      currency
    });
  }

  // Percentage
  percent(value: number): string {
    return this.formatNumber(value, {
      style: 'percent',
      minimumFractionDigits: 0,
      maximumFractionDigits: 2
    });
  }

  // Decimal
  decimal(value: number, decimals: number = 2): string {
    return this.formatNumber(value, {
      minimumFractionDigits: decimals,
      maximumFractionDigits: decimals
    });
  }

  // Compact notation (1.2K, 1.5M)
  compact(value: number): string {
    return this.formatNumber(value, {
      notation: 'compact',
      compactDisplay: 'short'
    });
  }
}

// Usage
const enFormatter = new NumberFormatter('en-US');
const deFormatter = new NumberFormatter('de-DE');
const jaFormatter = new NumberFormatter('ja-JP');

console.log(enFormatter.currency(1234.56, 'USD'));  // $1,234.56
console.log(deFormatter.currency(1234.56, 'EUR'));  // 1.234,56 €
console.log(jaFormatter.currency(1234.56, 'JPY'));  // ¥1,235

console.log(enFormatter.percent(0.1234));  // 12.34%
console.log(enFormatter.compact(1234567));  // 1.2M

6. Pluralization Rules

// pluralization.ts
export class PluralRules {
  constructor(private locale: string) {}

  // Get plural category
  select(count: number): Intl.LDMLPluralRule {
    const pr = new Intl.PluralRules(this.locale);
    return pr.select(count);
  }

  // Format with pluralization
  format(count: number, forms: Record<Intl.LDMLPluralRule, string>): string {
    const rule = this.select(count);
    return forms[rule] || forms.other;
  }
}

// Usage
const enRules = new PluralRules('en');

console.log(enRules.format(0, {
  zero: 'No items',
  one: 'One item',
  other: '{{count}} items'
}));

console.log(enRules.format(1, {
  one: 'One item',
  other: '{{count}} items'
}));

// Different languages have different plural rules
const arRules = new PluralRules('ar'); // Arabic has 6 plural forms
const plRules = new PluralRules('pl'); // Polish has complex plural rules

ICU Message Format

// Using intl-messageformat
import IntlMessageFormat from 'intl-messageformat';

const message = new IntlMessageFormat(
  '{count, plural, =0 {No items} one {# item} other {# items}}',
  'en'
);

console.log(message.format({ count: 0 }));  // No items
console.log(message.format({ count: 1 }));  // 1 item
console.log(message.format({ count: 5 }));  // 5 items

// With gender
const genderMessage = new IntlMessageFormat(
  '{gender, select, male {He} female {She} other {They}} bought {count, plural, one {# item} other {# items}}',
  'en'
);

console.log(genderMessage.format({ gender: 'female', count: 2 }));
// She bought 2 items

7. RTL (Right-to-Left) Language Support

// rtl-utils.ts
const RTL_LANGUAGES = ['ar', 'he', 'fa', 'ur'];

export function isRTL(locale: string): boolean {
  const lang = locale.split('-')[0];
  return RTL_LANGUAGES.includes(lang);
}

export function getDirection(locale: string): 'ltr' | 'rtl' {
  return isRTL(locale) ? 'rtl' : 'ltr';
}
/* styles/rtl.css */
:root {
  --text-align-start: left;
  --text-align-end: right;
  --margin-start: margin-left;
  --margin-end: margin-right;
  --padding-start: padding-left;
  --padding-end: padding-right;
}

[dir="rtl"] {
  --text-align-start: right;
  --text-align-end: left;
  --margin-start: margin-right;
  --margin-end: margin-left;
  --padding-start: padding-right;
  --padding-end: padding-left;
}

.container {
  text-align: var(--text-align-start);
  margin-left: var(--margin-start);
  padding-right: var(--padding-end);
}

/* Or use logical properties (modern approach) */
.modern-container {
  text-align: start;
  margin-inline-start: 1rem;
  padding-inline-end: 2rem;
}
// RTL React component
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { isRTL, getDirection } from './rtl-utils';

export function App() {
  const { i18n } = useTranslation();

  useEffect(() => {
    const direction = getDirection(i18n.language);
    document.documentElement.setAttribute('dir', direction);
    document.documentElement.setAttribute('lang', i18n.language);
  }, [i18n.language]);

  return (
    <div className="app">
      {/* Your app content */}
    </div>
  );
}

8. Translation Management

Message Extraction

// extract-messages.ts
import { sync as globSync } from 'glob';
import fs from 'fs';

const TRANSLATION_PATTERN = /t\(['"]([^'"]+)['"]\)/g;

export function extractMessages(pattern: string): Set<string> {
  const messages = new Set<string>();
  const files = globSync(pattern);

  for (const file of files) {
    const content = fs.readFileSync(file, 'utf8');
    let match;

    while ((match = TRANSLATION_PATTERN.exec(content)) !== null) {
      messages.add(match[1]);
    }
  }

  return messages;
}

// Generate translation template
export function generateTemplate(messages: Set<string>): object {
  const template: Record<string, string> = {};

  for (const message of messages) {
    template[message] = message; // Default to English
  }

  return template;
}

// Usage
const messages = extractMessages('src/**/*.{ts,tsx}');
const template = generateTemplate(messages);

fs.writeFileSync(
  'locales/en/translation.json',
  JSON.stringify(template, null, 2)
);

Translation Status

// check-translations.ts
export function checkTranslationStatus(
  baseLocale: object,
  targetLocale: object
): {
  missing: string[];
  extra: string[];
  coverage: number;
} {
  const baseKeys = new Set(Object.keys(baseLocale));
  const targetKeys = new Set(Object.keys(targetLocale));

  const missing = [...baseKeys].filter(key => !targetKeys.has(key));
  const extra = [...targetKeys].filter(key => !baseKeys.has(key));

  const coverage = (targetKeys.size / baseKeys.size) * 100;

  return { missing, extra, coverage };
}

// Usage
const enMessages = require('./locales/en/translation.json');
const esMessages = require('./locales/es/translation.json');

const status = checkTranslationStatus(enMessages, esMessages);
console.log(`Spanish translation coverage: ${status.coverage.toFixed(2)}%`);
console.log(`Missing keys: ${status.missing.join(', ')}`);

9. Locale Detection

// locale-detector.ts
export class LocaleDetector {
  // Detect from browser
  static fromBrowser(): string {
    return navigator.language || navigator.languages[0] || 'en';
  }

  // Detect from URL
  static fromURL(): string | null {
    const params = new URLSearchParams(window.location.search);
    return params.get('lang') || params.get('locale');
  }

  // Detect from cookie
  static fromCookie(name: string = 'locale'): string | null {
    const match = document.cookie.match(new RegExp(`${name}=([^;]+)`));
    return match ? match[1] : null;
  }

  // Detect from localStorage
  static fromStorage(key: string = 'locale'): string | null {
    return localStorage.getItem(key);
  }

  // Detect with priority
  static detect(defaultLocale: string = 'en'): string {
    return (
      this.fromURL() ||
      this.fromStorage() ||
      this.fromCookie() ||
      this.fromBrowser() ||
      defaultLocale
    );
  }

  // Save locale
  static save(locale: string): void {
    localStorage.setItem('locale', locale);
    document.cookie = `locale=${locale}; path=/; max-age=31536000`;
  }
}

10. Server-Side i18n

// Next.js i18n configuration
// next.config.js
module.exports = {
  i18n: {
    locales: ['en', 'es', 'fr', 'de', 'ja'],
    defaultLocale: 'en',
    localeDetection: true
  }
};

// pages/index.tsx
import { GetStaticProps } from 'next';
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

export default function Home() {
  const { t } = useTranslation('common');

  return (
    <div>
      <h1>{t('welcome')}</h1>
    </div>
  );
}

export const getStaticProps: GetStaticProps = async ({ locale }) => {
  return {
    props: {
      ...(await serverSideTranslations(locale ?? 'en', ['common']))
    }
  };
};

Best Practices

✅ DO

  • Extract all user-facing strings to translation files
  • Use ICU message format for complex messages
  • Support pluralization correctly for each language
  • Use locale-aware date/time/number formatting
  • Implement RTL support for Arabic, Hebrew, etc.
  • Provide fallback language (usually English)
  • Use namespaces to organize translations
  • Test with pseudo-localization (ääçćëńţś)
  • Store locale preference (cookie, localStorage)
  • Use professional translators for production
  • Implement translation management workflow
  • Support dynamic locale switching
  • Use translation memory tools

❌ DON'T

  • Hardcode user-facing strings in code
  • Concatenate translated strings
  • Assume English grammar rules apply to all languages
  • Use generic plural forms (one/many) for all languages
  • Forget about text expansion (German is ~30% longer)
  • Store dates/times in locale-specific formats
  • Use flags to represent languages (flag ≠ language)
  • Translate technical terms without context
  • Mix translation keys with UI strings
  • Forget to translate alt text, titles, placeholders
  • Assume left-to-right layout

Common Patterns

Pattern 1: Translation Hook

export function useLocale() {
  const { i18n } = useTranslation();

  return {
    locale: i18n.language,
    changeLocale: (lng: string) => i18n.changeLanguage(lng),
    t: i18n.t,
    formatDate: (date: Date) => new DateFormatter(i18n.language).short(date),
    formatNumber: (num: number) => new NumberFormatter(i18n.language).formatNumber(num),
    formatCurrency: (amount: number, currency: string) =>
      new NumberFormatter(i18n.language).currency(amount, currency)
  };
}

Pattern 2: Language Switcher Component

export function LanguageSwitcher() {
  const { locale, changeLocale } = useLocale();

  const languages = [
    { code: 'en', name: 'English', nativeName: 'English' },
    { code: 'es', name: 'Spanish', nativeName: 'Español' },
    { code: 'fr', name: 'French', nativeName: 'Français' },
    { code: 'de', name: 'German', nativeName: 'Deutsch' }
  ];

  return (
    <select value={locale} onChange={(e) => changeLocale(e.target.value)}>
      {languages.map((lang) => (
        <option key={lang.code} value={lang.code}>
          {lang.nativeName}
        </option>
      ))}
    </select>
  );
}

Tools & Resources

  • i18next: Comprehensive i18n framework
  • react-intl (Format.js): React i18n library
  • LinguiJS: Developer-friendly i18n
  • vue-i18n: Vue.js i18n plugin
  • Crowdin: Translation management platform
  • Lokalise: Localization management
  • Phrase: Localization platform
  • POEditor: Translation management
  • BabelEdit: Translation editor
  • Pseudolocalization: Testing tool

Quick Install

/plugin add https://github.com/aj-geddes/useful-ai-prompts/tree/main/internationalization-i18n

Copy and paste this command in Claude Code to install this skill

GitHub 仓库

aj-geddes/useful-ai-prompts
Path: skills/internationalization-i18n

Related Skills

subagent-driven-development

Development

This skill executes implementation plans by dispatching a fresh subagent for each independent task, with code review between tasks. It enables fast iteration while maintaining quality gates through this review process. Use it when working on mostly independent tasks within the same session to ensure continuous progress with built-in quality checks.

View skill

algorithmic-art

Meta

This Claude Skill creates original algorithmic art using p5.js with seeded randomness and interactive parameters. It generates .md files for algorithmic philosophies, plus .html and .js files for interactive generative art implementations. Use it when developers need to create flow fields, particle systems, or other computational art while avoiding copyright issues.

View skill

executing-plans

Design

Use the executing-plans skill when you have a complete implementation plan to execute in controlled batches with review checkpoints. It loads and critically reviews the plan, then executes tasks in small batches (default 3 tasks) while reporting progress between each batch for architect review. This ensures systematic implementation with built-in quality control checkpoints.

View skill

cost-optimization

Other

This Claude Skill helps developers optimize cloud costs through resource rightsizing, tagging strategies, and spending analysis. It provides a framework for reducing cloud expenses and implementing cost governance across AWS, Azure, and GCP. Use it when you need to analyze infrastructure costs, right-size resources, or meet budget constraints.

View skill