| name | stacks-i18n |
| description | 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. |
| license | MIT |
| compatibility | Bun >= 1.3.0, TypeScript |
| allowed-tools | Read Edit Write Bash Grep Glob |
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
type TranslationMessages = { [key: string]: string | TranslationMessages }
type Translations = { [locale: string]: TranslationMessages }
type InterpolationValues = Record<string, string | number | boolean | null | undefined>
interface I18nConfig {
locale: string
fallbackLocale: string
availableLocales?: string[]
messages?: Translations
missingHandler?: (locale: string, key: string) => string | undefined
warnMissing?: boolean
escapeValues?: boolean
keySeparator?: string
pluralSeparator?: string
dateTimeFormats?: DateTimeFormats
numberFormats?: NumberFormats
}
Translation API
Instance-Based
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' })
i18n.setLocale('de')
i18n.t('greeting', { name: 'Welt' })
Global API
import { t, tc, te, tm, trans, setLocale, getLocale, addTranslations, configure } from '@stacksjs/i18n'
t('greeting', { name: 'World' })
tc('items', 5, { count: 5 })
te('greeting')
tm('section')
trans('greeting')
setLocale('fr')
getLocale()
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:
addTranslations('en', { items: 'no items | one item | {count} items' })
tc('items', 0)
tc('items', 1)
tc('items', 5)
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 |
import { getPluralCategory, addPluralRule, hasPluralRule } from '@stacksjs/i18n'
getPluralCategory(1, 'en')
getPluralCategory(5, 'en')
Formatting
Date/Time
import { formatDate, formatTime, formatDateTime, formatRelativeTime } from '@stacksjs/i18n'
formatDate(new Date(), 'long')
formatDate(new Date(), 'short')
formatTime(new Date(), 'short')
formatDateTime(new Date(), 'medium', 'short')
formatRelativeTime(pastDate)
Numbers & Currency
import { formatNumber, formatCurrency, formatPercent, formatUnit, formatBytes } from '@stacksjs/i18n'
formatNumber(1234567)
formatNumber(1234567, { compact: true })
formatCurrency(29.99)
formatCurrency(29.99, 'EUR', {}, 'de')
formatPercent(0.85)
formatUnit(60, 'kilometer-per-hour', 'short')
formatBytes(1536)
Lists & Display Names
import { formatList, getLanguageName, getRegionName, isRTL } from '@stacksjs/i18n'
formatList(['Alice', 'Bob', 'Charlie'])
formatList(['A', 'B'], 'disjunction')
getLanguageName('de')
getRegionName('JP')
isRTL('ar')
String Collation
import { sortStrings, compareStrings } from '@stacksjs/i18n'
sortStrings(['ö', 'a', 'ü'], {}, 'de')
Translation File Loader
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
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.