# Stacks I18N

> Use when working with internationalization in a Stacks application - translations, locale management, pluralization, date/number/currency formatting, or loading translation files. Covers @stacksjs/i18n with translator, formatter, loader, and pluralization modules.

- Skill: `stacksjs/stacks-i18n` (Agent Skill)
- Install (CLI): `npx skillmds@latest add stacksjs/stacks-i18n`
- Raw SKILL.md: https://api.skillmd.com/api/skills/stacksjs/stacks-i18n/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: stacksjs (https://skillmd.com/u/stacksjs)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/stacksjs/stacks-i18n

---


# Stacks i18n

## Key Paths
- Core package: `storage/framework/core/i18n/src/`
- Locale files: `locales/` (en.yml, de.yml)
- Package: `@stacksjs/i18n`

## Source Files
```
i18n/src/
├── index.ts              # Main entry point and exports
├── types.ts              # All type definitions
├── translator.ts         # I18n class, global API (t, tc, te, tm)
├── formatter.ts          # Locale-aware formatting (dates, numbers, currency, lists)
├── loader.ts             # File-based translation loader (JSON, YAML, JS/TS)
└── pluralization.ts      # CLDR plural rules for 25+ languages
```

## Core Types

```typescript
type TranslationMessages = { [key: string]: string | TranslationMessages }
type Translations = { [locale: string]: TranslationMessages }
type InterpolationValues = Record<string, string | number | boolean | null | undefined>

interface I18nConfig {
  locale: string                    // default: 'en'
  fallbackLocale: string            // default: 'en'
  availableLocales?: string[]
  messages?: Translations
  missingHandler?: (locale: string, key: string) => string | undefined
  warnMissing?: boolean             // default: true
  escapeValues?: boolean            // default: false
  keySeparator?: string             // default: '.'
  pluralSeparator?: string          // default: '|'
  dateTimeFormats?: DateTimeFormats
  numberFormats?: NumberFormats
}
```

## Translation API

### Instance-Based
```typescript
import { createI18n, useI18n } from '@stacksjs/i18n'

const i18n = createI18n({
  locale: 'en',
  fallbackLocale: 'en',
  messages: {
    en: { greeting: 'Hello, {name}!' },
    de: { greeting: 'Hallo, {name}!' },
  },
})

i18n.t('greeting', { name: 'World' })  // 'Hello, World!'
i18n.setLocale('de')
i18n.t('greeting', { name: 'Welt' })   // 'Hallo, Welt!'
```

### Global API
```typescript
import { t, tc, te, tm, trans, setLocale, getLocale, addTranslations, configure } from '@stacksjs/i18n'

t('greeting', { name: 'World' })         // Translate with interpolation
tc('items', 5, { count: 5 })             // Plural translation
te('greeting')                            // Key exists? → boolean
tm('section')                             // Raw message object
trans('greeting')                         // Alias for t()

setLocale('fr')
getLocale()                               // 'fr'
addTranslations('fr', { greeting: 'Bonjour, {name}!' })
```

### Lookup Flow
```
1. Look up key in target locale
2. If not found → try fallback locale
3. If still not found → call missingHandler → warn → return key string
```

## Pluralization

CLDR plural rules, pipe-separated forms:

```typescript
addTranslations('en', { items: 'no items | one item | {count} items' })
tc('items', 0)    // 'no items'
tc('items', 1)    // 'one item'
tc('items', 5)    // '5 items'
```

### Supported Languages

| Rule | Languages |
|------|-----------|
| One/Other | en, de, nl, es, it, pt, sv, da, no, fi, el, he, hu, tr |
| French (0-1=one) | fr |
| No plural | zh, ja, ko, vi, th, id, ms |
| One/Few/Many/Other | ru, uk, pl |
| One/Few/Other | cs, sk |
| Full (zero/one/two/few/many/other) | ar, cy |

```typescript
import { getPluralCategory, addPluralRule, hasPluralRule } from '@stacksjs/i18n'
getPluralCategory(1, 'en')     // 'one'
getPluralCategory(5, 'en')     // 'other'
```

## Formatting

### Date/Time
```typescript
import { formatDate, formatTime, formatDateTime, formatRelativeTime } from '@stacksjs/i18n'

formatDate(new Date(), 'long')                    // 'March 23, 2026'
formatDate(new Date(), 'short')                   // '3/23/26'
formatTime(new Date(), 'short')                   // '2:30 PM'
formatDateTime(new Date(), 'medium', 'short')     // 'Mar 23, 2026, 2:30 PM'
formatRelativeTime(pastDate)                      // '3 hours ago'
```

### Numbers & Currency
```typescript
import { formatNumber, formatCurrency, formatPercent, formatUnit, formatBytes } from '@stacksjs/i18n'

formatNumber(1234567)                              // '1,234,567'
formatNumber(1234567, { compact: true })           // '1.2M'
formatCurrency(29.99)                              // '$29.99'
formatCurrency(29.99, 'EUR', {}, 'de')             // '29,99 €'
formatPercent(0.85)                                // '85%'
formatUnit(60, 'kilometer-per-hour', 'short')      // '60 km/h'
formatBytes(1536)                                  // '1.50 KB'
```

### Lists & Display Names
```typescript
import { formatList, getLanguageName, getRegionName, isRTL } from '@stacksjs/i18n'

formatList(['Alice', 'Bob', 'Charlie'])            // 'Alice, Bob, and Charlie'
formatList(['A', 'B'], 'disjunction')              // 'A or B'
getLanguageName('de')                               // 'German'
getRegionName('JP')                                 // 'Japan'
isRTL('ar')                                         // true
```

### String Collation
```typescript
import { sortStrings, compareStrings } from '@stacksjs/i18n'
sortStrings(['ö', 'a', 'ü'], {}, 'de')            // Locale-aware sort
```

## Translation File Loader

```typescript
import { loadFromDirectory, loadFile, createLoader } from '@stacksjs/i18n'

const translations = await loadFromDirectory({
  directory: './locales',
  extensions: ['.json', '.yaml', '.yml', '.ts', '.js'],
  recursive: false,
})

const loader = createLoader('./locales')
const all = await loader.load()
const fr = await loader.loadLocale('fr', 'messages.yml')
```

## Locale Files

```yaml
# locales/en.yml
default:
  button:
    about: About
    back: Back
  intro:
    desc: Rapid Application Development
    hi: 'Hi, {name}!'
```

## Gotchas
- **Dual API** — instance-based (`createI18n()`) and global (`t()`) both exist. Global uses a singleton
- **YAML parsed with Bun.YAML** — not a third-party parser
- **Missing keys return the key itself** — not an error, not empty string
- **Pluralization uses `|` separator** — not ICU MessageFormat
- **CLDR rules are built-in** — no external data needed, 25+ languages hardcoded
- **HTML escaping is opt-in** — `escapeValues: true` in config
- **Formatting uses Intl APIs** — delegates to `Intl.DateTimeFormat`, `Intl.NumberFormat`, etc.

