inline-i18n-multi
v0.14.0
Published
Inline i18n - write translations inline, support multiple languages
Maintainers
Readme
inline-i18n-multi
Write translations inline. Find them instantly.
For complete documentation, examples, and best practices, please read the full documentation on GitHub.
The Problem
Traditional i18n libraries separate translations from code:
// Component.tsx
<p>{t('greeting.hello')}</p>
// en.json
{ "greeting": { "hello": "Hello" } }
// ko.json
{ "greeting": { "hello": "안녕하세요" } }When you see "Hello" in your app and want to find it in the code, you have to:
- Search for "Hello" in JSON files
- Find the key
greeting.hello - Search for that key in your code
- Finally find
t('greeting.hello')
This is slow and frustrating.
The Solution
With inline-i18n-multi, translations live in your code:
<p>{it('안녕하세요', 'Hello')}</p>See "Hello" in your app? Just search for "Hello" in your codebase. Done.
Features
- Inline translations - Write translations right where you use them
- Instant search - Find any text in your codebase immediately
- Type-safe - Full TypeScript support with variable type checking
- Multiple languages - Support for any number of locales
- i18n compatible - Support for traditional key-based translations with JSON dictionaries
- ICU Message Format - Plural, select, date, number, time, relative time, and list formatting
- Variable interpolation -
{name}syntax for dynamic values - Locale Fallback Chain - BCP 47 parent locale support (
zh-TW→zh→en) - Missing Translation Warning - Development-time diagnostics with customizable handlers
- Namespace Support - Organize translations for large apps (
t('common:greeting')) - Debug Mode - Visual indicators for missing/fallback translations
- Currency Formatting - Locale-aware currency display (
{price, currency, USD}) - Compact Number Formatting - Short number display (
{count, number, compact}) - Rich Text Interpolation - Embed React components in translations (
<link>text</link>) - Lazy Loading - Async dictionary loading on demand (
loadAsync()) - Custom Formatter Registry - Register custom ICU-style formatters (
registerFormatter('phone', fn)) - Interpolation Guards - Handle missing variables gracefully (
missingVarHandler) - Locale Detection - Auto-detect user locale from navigator, cookie, URL, or header (
detectLocale()) - Selectordinal - Ordinal plural formatting (
{rank, selectordinal, one {#st} two {#nd} ...}) - ICU Message Cache - Memoize parsed ICU ASTs for performance (
icuCacheSize,clearICUCache()) - Plural Shorthand - Concise plural syntax (
{count, p, item|items}) - Locale Persistence - Auto-save/restore locale to cookie or localStorage (
persistLocale,restoreLocale()) - Translation Scope - Namespace scoping with
createScope(createScope('common')returns a scopedt()) - Unused Key Detection - CLI
--unusedflag to detect unused translation keys - TypeScript Type Generation -
typegencommand for auto-generating translation key type definitions - Context System - Contextual translation disambiguation (
t('greeting', { _context: 'formal' })withkey#contextdictionary keys) - Translation Extraction - Extract inline translations to JSON files (
npx inline-i18n extract) - CLI Watch Mode -
--watchflag forvalidateandtypegencommands - Fallback Value - Custom fallback text when translation is missing (
t('key', { _fallback: 'Default' })) - Diff Command - Compare translations between two locales (
npx inline-i18n diff en ko) - Stats Command - Translation statistics dashboard (
npx inline-i18n stats) - Locale Display Names - Get human-readable locale names using
Intl.DisplayNames(getLocaleDisplayName('ko', 'en')→"Korean") - Translation Key Listing -
getTranslationKeys(locale?, namespace?)returns all loaded translation keys - Missing Translation Tracker - Runtime collection of missing translation keys (
trackMissingKeys(true/false),getMissingKeys(),clearMissingKeys()) - Locale Change Event — Subscribe to locale changes with
onLocaleChange()(v0.12.0) - Formatting Utilities — Locale-aware
formatNumber,formatDate,formatListpowered byIntlAPIs (v0.13.0) - Raw Template Access --
tRaw(key, locale?)returns raw template string without interpolation (v0.14.0)
Installation
# npm
npm install inline-i18n-multi
# yarn
yarn add inline-i18n-multi
# pnpm
pnpm add inline-i18n-multiQuick Start
import { it, setLocale } from 'inline-i18n-multi'
// Set current locale
setLocale('en')
// Shorthand syntax (Korean + English)
it('안녕하세요', 'Hello') // → "Hello"
// Object syntax (multiple languages)
it({ ko: '안녕하세요', en: 'Hello', ja: 'こんにちは' }) // → "Hello"
// With variables
it('안녕, {name}님', 'Hello, {name}', { name: 'John' }) // → "Hello, John"Key-Based Translations (i18n Compatible)
For projects that already use JSON translation files, or when you need traditional key-based translations:
import { t, loadDictionaries } from 'inline-i18n-multi'
// Load translation dictionaries
loadDictionaries({
en: {
greeting: { hello: 'Hello', goodbye: 'Goodbye' },
items: { count_one: '{count} item', count_other: '{count} items' },
welcome: 'Welcome, {name}!'
},
ko: {
greeting: { hello: '안녕하세요', goodbye: '안녕히 가세요' },
items: { count_other: '{count}개 항목' },
welcome: '환영합니다, {name}님!'
}
})
// Basic key-based translation
t('greeting.hello') // → "Hello" (when locale is 'en')
// With variables
t('welcome', { name: 'John' }) // → "Welcome, John!"
// Plural support (uses Intl.PluralRules)
t('items.count', { count: 1 }) // → "1 item"
t('items.count', { count: 5 }) // → "5 items"
// Override locale
t('greeting.hello', undefined, 'ko') // → "안녕하세요"Utility Functions
import { hasTranslation, getLoadedLocales, getDictionary } from 'inline-i18n-multi'
// Check if translation exists
hasTranslation('greeting.hello') // → true
hasTranslation('missing.key') // → false
// Get loaded locales
getLoadedLocales() // → ['en', 'ko']
// Get dictionary for a locale
getDictionary('en') // → { greeting: { hello: 'Hello', ... }, ... }ICU Message Format
For complex translations with plurals and conditional text:
import { it, setLocale } from 'inline-i18n-multi'
setLocale('en')
// Plural
it({
ko: '{count, plural, =0 {항목 없음} other {# 개}}',
en: '{count, plural, =0 {No items} one {# item} other {# items}}'
}, { count: 0 }) // → "No items"
it({
ko: '{count, plural, =0 {항목 없음} other {# 개}}',
en: '{count, plural, =0 {No items} one {# item} other {# items}}'
}, { count: 1 }) // → "1 item"
it({
ko: '{count, plural, =0 {항목 없음} other {# 개}}',
en: '{count, plural, =0 {No items} one {# item} other {# items}}'
}, { count: 5 }) // → "5 items"
// Select
it({
ko: '{gender, select, male {그} female {그녀} other {그들}}',
en: '{gender, select, male {He} female {She} other {They}}'
}, { gender: 'female' }) // → "She"
// Combined with text
it({
ko: '{name}님이 {count, plural, =0 {메시지가 없습니다} other {# 개의 메시지가 있습니다}}',
en: '{name} has {count, plural, =0 {no messages} one {# message} other {# messages}}'
}, { name: 'John', count: 3 }) // → "John has 3 messages"
// Date formatting
it({
en: 'Created: {date, date, long}',
ko: '생성일: {date, date, long}'
}, { date: new Date() }) // → "Created: January 15, 2024"
// Number formatting
it({
en: 'Price: {price, number}',
ko: '가격: {price, number}'
}, { price: 1234.56 }) // → "Price: 1,234.56"Supported ICU types:
plural:zero,one,two,few,many,other(and exact matches like=0,=1)select: match on string valuesselectordinal: ordinal plural categories (one,two,few,other)number:decimal,percent,integer,currency,compact,compactLongdate:short,medium,long,fulltime:short,medium,long,full
Relative Time Formatting
it({
en: 'Updated {time, relativeTime}',
ko: '{time, relativeTime} 업데이트됨'
}, { time: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000) })
// → "Updated 3 days ago"
// Styles: long (default), short, narrow
it({ en: '{time, relativeTime, short}' }, { time: pastDate })List Formatting
it({
en: 'Invited: {names, list}',
ko: '초대됨: {names, list}'
}, { names: ['Alice', 'Bob', 'Charlie'] })
// → "Invited: Alice, Bob, and Charlie"
// Types: conjunction (and), disjunction (or), unit
it({ en: '{options, list, disjunction}' }, { options: ['A', 'B'] })
// → "A or B"Currency Formatting
it({
en: 'Total: {price, currency, USD}',
ko: '합계: {price, currency, KRW}'
}, { price: 42000 })
// en → "Total: $42,000.00" / ko → "합계: ₩42,000"
// Defaults to USD when currency code omitted
it({ en: '{price, currency}' }, { price: 100 })
// → "$100.00"Compact Number Formatting
it({
en: '{count, number, compact} views',
ko: '{count, number, compact} 조회'
}, { count: 1500000 })
// en → "1.5M views" / ko → "150만 조회"
it({ en: '{count, number, compactLong}' }, { count: 1500000 })
// → "1.5 million"Namespace Support
Organize translations for large applications:
import { loadDictionaries, t, getLoadedNamespaces, clearDictionaries } from 'inline-i18n-multi'
// Load with namespace
loadDictionaries({
en: { hello: 'Hello' },
ko: { hello: '안녕하세요' }
}, 'common')
// Use with namespace prefix
t('common:hello') // → "Hello"
// Without namespace = 'default' (backward compatible)
loadDictionaries({ en: { greeting: 'Hi' } })
t('greeting') // → "Hi"
getLoadedNamespaces() // → ['common', 'default']
clearDictionaries('common') // Clear specific namespaceDebug Mode
Visual indicators for debugging:
import { configure, setLocale, it, t } from 'inline-i18n-multi'
configure({ debugMode: true })
setLocale('fr')
it({ en: 'Hello', ko: '안녕하세요' }) // → "[fr -> en] Hello"
t('missing.key') // → "[MISSING: fr] missing.key"Rich Text Interpolation
Embed React components within translations:
import { RichText, useRichText } from 'inline-i18n-multi-react'
// Component syntax
<RichText
translations={{
en: 'Read <link>terms</link> and <bold>agree</bold>',
ko: '<link>약관</link>을 읽고 <bold>동의</bold>해주세요'
}}
components={{
link: (text) => <a href="/terms">{text}</a>,
bold: (text) => <strong>{text}</strong>
}}
/>
// Hook syntax
const richT = useRichText({
link: (text) => <a href="/terms">{text}</a>,
bold: (text) => <strong>{text}</strong>
})
richT({ en: 'Click <link>here</link>', ko: '<link>여기</link> 클릭' })Lazy Loading
Load dictionaries asynchronously on demand:
import { configure, loadAsync, isLoaded, t } from 'inline-i18n-multi'
configure({
loader: (locale, namespace) => import(`./locales/${locale}/${namespace}.json`)
})
await loadAsync('ko', 'dashboard')
t('dashboard:title')
isLoaded('ko', 'dashboard') // → trueReact Hook
import { useLoadDictionaries } from 'inline-i18n-multi-react'
function Dashboard() {
const { isLoading, error } = useLoadDictionaries('ko', 'dashboard')
if (isLoading) return <Spinner />
if (error) return <Error message={error.message} />
return <Content />
}Custom Formatter Registry
Register custom ICU-style formatters for domain-specific formatting:
import { registerFormatter, clearFormatters, it, setLocale } from 'inline-i18n-multi'
setLocale('en')
// Register a phone number formatter
registerFormatter('phone', (value, locale, style?) => {
const s = String(value)
if (locale === 'ko') return `${s.slice(0, 3)}-${s.slice(3, 7)}-${s.slice(7)}`
return `(${s.slice(0, 3)}) ${s.slice(3, 6)}-${s.slice(6)}`
})
// Use in translations
it({
en: 'Call {num, phone}',
ko: '전화: {num, phone}'
}, { num: '2125551234' })
// → "Call (212) 555-1234"
// Register a formatter with style support
registerFormatter('mask', (value, locale, style?) => {
const s = String(value)
if (style === 'email') {
const [user, domain] = s.split('@')
return `${user[0]}***@${domain}`
}
return s.slice(0, 2) + '***' + s.slice(-2)
})
it({ en: 'Email: {email, mask, email}' }, { email: '[email protected]' })
// → "Email: j***@example.com"
// Clear all custom formatters
clearFormatters()Reserved names (plural, select, selectordinal, number, date, time, relativeTime, list, currency) cannot be used as custom formatter names and will throw an error.
Interpolation Guards
Handle missing interpolation variables gracefully with a custom handler:
import { configure, it, setLocale } from 'inline-i18n-multi'
setLocale('en')
// Configure a missing variable handler
configure({
missingVarHandler: (varName, locale) => {
console.warn(`Missing variable "${varName}" for locale "${locale}"`)
return `[${varName}]`
}
})
// When a variable is missing, the handler is called instead of leaving {varName}
it({ en: 'Hello, {name}!' })
// logs: Missing variable "name" for locale "en"
// → "Hello, [name]!"
// Works with ICU patterns too
it({ en: '{count, plural, one {# item} other {# items}}' })
// logs: Missing variable "count" for locale "en"
// → "{count}"
// Without a handler, missing variables are left as-is: {varName}Locale Detection
Auto-detect the user's locale from multiple sources:
import { detectLocale, setLocale } from 'inline-i18n-multi'
// Basic detection from browser navigator
const locale = detectLocale({
supportedLocales: ['en', 'ko', 'ja'],
defaultLocale: 'en',
})
setLocale(locale)
// Multiple sources in priority order
const detected = detectLocale({
supportedLocales: ['en', 'ko', 'ja'],
defaultLocale: 'en',
sources: ['cookie', 'url', 'navigator'],
cookieName: 'NEXT_LOCALE', // default
})
// Server-side detection from Accept-Language header
const ssrLocale = detectLocale({
supportedLocales: ['en', 'ko', 'ja'],
defaultLocale: 'en',
sources: ['header'],
headerValue: 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
})
// → "ko"
// BCP 47 parent matching (en-US matches en)
const matched = detectLocale({
supportedLocales: ['en', 'ko'],
defaultLocale: 'en',
sources: ['navigator'],
})
// Browser reports "en-US" → matches "en"Detection sources:
| Source | Description |
|--------|-------------|
| navigator | Browser navigator.languages / navigator.language |
| cookie | Reads locale from document.cookie (configurable name) |
| url | First path segment (e.g., /ko/about matches ko) |
| header | Parses Accept-Language header value (for SSR) |
Sources are tried in order; the first match wins. If no source matches, defaultLocale is returned.
Selectordinal
Ordinal plural formatting for ranking and ordering (e.g., 1st, 2nd, 3rd):
import { it, setLocale } from 'inline-i18n-multi'
setLocale('en')
// Ordinal suffixes
it({
en: '{rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
}, { rank: 1 }) // → "1st"
it({
en: '{rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
}, { rank: 2 }) // → "2nd"
it({
en: '{rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
}, { rank: 3 }) // → "3rd"
it({
en: '{rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
}, { rank: 4 }) // → "4th"
// Handles English irregulars correctly
it({
en: '{rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
}, { rank: 11 }) // → "11th" (not "11st")
it({
en: '{rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
}, { rank: 21 }) // → "21st"
// Combined with text
it({
en: 'You finished {rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} place!'
}, { rank: 3 }) // → "You finished 3rd place!"Uses Intl.PluralRules with { type: 'ordinal' } for locale-aware ordinal categories.
ICU Message Cache
Memoize parsed ICU ASTs to avoid re-parsing the same message patterns:
import { configure, clearICUCache } from 'inline-i18n-multi'
// Enable caching (default: 500 entries)
configure({ icuCacheSize: 500 })
// Disable caching
configure({ icuCacheSize: 0 })
// Manually clear the cache
clearICUCache()The cache uses FIFO (First-In, First-Out) eviction when the maximum size is reached. Repeated calls to it() or t() with the same ICU pattern will reuse the cached AST instead of re-parsing.
Plural Shorthand
Concise syntax for common plural patterns using p as a short type name:
import { it, setLocale } from 'inline-i18n-multi'
setLocale('en')
// 2-part: singular|plural (value is prepended automatically)
it({
en: '{count, p, item|items}',
ko: '{count, p, 개|개}'
}, { count: 1 }) // → "1 item"
it({
en: '{count, p, item|items}',
ko: '{count, p, 개|개}'
}, { count: 5 }) // → "5 items"
// 3-part: zero|singular|plural
it({
en: '{count, p, no items|item|items}',
ko: '{count, p, 항목 없음|개|개}'
}, { count: 0 }) // → "no items"
it({
en: '{count, p, no items|item|items}',
ko: '{count, p, 항목 없음|개|개}'
}, { count: 1 }) // → "1 item"
it({
en: '{count, p, no items|item|items}',
ko: '{count, p, 항목 없음|개|개}'
}, { count: 5 }) // → "5 items"The shorthand is preprocessed into standard ICU plural syntax before parsing.
Locale Persistence
Auto-save and restore the user's locale to cookie or localStorage:
import { configure, setLocale, restoreLocale } from 'inline-i18n-multi'
// Configure persistence
configure({
persistLocale: {
storage: 'cookie', // 'cookie' | 'localStorage'
key: 'LOCALE', // storage key (default: 'LOCALE')
expires: 365 // cookie expiry in days (default: 365)
}
})
// Restore locale from storage (returns the saved locale or undefined)
const saved = restoreLocale()
if (saved) {
// locale was restored from storage
}
// setLocale() automatically saves to the configured storage
setLocale('ko') // also saves 'ko' to cookie or localStorage// localStorage example
configure({
persistLocale: {
storage: 'localStorage',
key: 'APP_LOCALE'
}
})
restoreLocale() // reads from localStorage
setLocale('ja') // saves 'ja' to localStorageTranslation Scope
Create a scoped translation function bound to a specific namespace with createScope:
import { createScope, loadDictionaries } from 'inline-i18n-multi'
loadDictionaries({ en: { greeting: 'Hello' }, ko: { greeting: '안녕하세요' } }, 'common')
const tc = createScope('common')
tc('greeting') // → "Hello"Eliminates the need to write namespace prefixes (t('common:greeting')) every time, keeping your code cleaner.
Unused Key Detection
Use the --unused flag to detect translation keys defined in dictionaries but not used in your code:
npx inline-i18n validate --unusedHelps identify and clean up translations that are no longer needed.
TypeScript Type Generation
Auto-generate TypeScript type definition files for your translation keys with the typegen command:
npx inline-i18n typegen --output src/i18n.d.tsThe generated types enable autocomplete and type checking for t() function key arguments.
Context System
Use the _context parameter to disambiguate translations for the same key based on context. Dictionary keys use the key#context format:
import { t, loadDictionaries } from 'inline-i18n-multi'
loadDictionaries({
en: {
greeting: 'Hi',
'greeting#formal': 'Good day',
'greeting#casual': 'Hey',
},
ko: {
greeting: '안녕하세요',
'greeting#formal': '안녕하십니까',
'greeting#casual': '야',
}
})
// No context (default)
t('greeting') // → "Hi"
// Formal context
t('greeting', { _context: 'formal' }) // → "Good day"
// Casual context
t('greeting', { _context: 'casual' }) // → "Hey"
// Falls back to the uncontexted key when a contexted key is not found
t('greeting', { _context: 'unknown' }) // → "Hi"Useful when the same translation key has different meanings depending on context (e.g., "open" used as both a verb and an adjective).
Translation Extraction
Extract inline translations from source code into JSON files using the extract command:
# Default (outputs to ./locales directory)
npx inline-i18n extract
# Specify output directory
npx inline-i18n extract --output ./src/locales
# Specify locales
npx inline-i18n extract --locales en,ko,jaScans it() calls in your source code and generates per-locale JSON files. Existing JSON files are preserved, with only new keys being added.
CLI Watch Mode
Add the --watch flag to validate and typegen commands to automatically re-run on file changes:
# Watch mode for validation
npx inline-i18n validate --watch
# Watch mode for type generation
npx inline-i18n typegen --output src/i18n.d.ts --watch
# Combine with strict mode
npx inline-i18n validate --strict --watchProvides instant feedback during development by re-running validation or type generation each time a file is saved.
Fallback Value
Provide custom fallback text when a translation key is missing, instead of returning the raw key:
import { t, loadDictionaries, setLocale } from 'inline-i18n-multi'
loadDictionaries({
en: { greeting: 'Hello' }
})
setLocale('en')
// Without _fallback: returns the raw key when missing
t('missing.key') // → "missing.key"
// With _fallback: returns custom fallback text
t('missing.key', { _fallback: 'Default text' }) // → "Default text"
// _fallback is stripped from interpolation output (not passed as a variable)
t('greeting', { _fallback: 'Fallback' }) // → "Hello" (uses real translation, ignores _fallback)
// Works with variables — _fallback is not treated as a variable
t('welcome', { name: 'John', _fallback: 'Welcome!' })
// If 'welcome' exists: uses translation with {name} interpolated
// If 'welcome' is missing: → "Welcome!"Useful for providing user-friendly defaults in UI components where raw keys would be confusing.
Locale Display Names
Get human-readable display names for locale codes using Intl.DisplayNames:
import { getLocaleDisplayName, setLocale } from 'inline-i18n-multi'
setLocale('en')
// Get display name in a specific locale
getLocaleDisplayName('ko', 'en') // → "Korean"
getLocaleDisplayName('ja', 'en') // → "Japanese"
getLocaleDisplayName('zh', 'en') // → "Chinese"
// Display name in the target's own locale
getLocaleDisplayName('ko', 'ko') // → "한국어"
getLocaleDisplayName('en', 'ja') // → "英語"
// Omit displayLocale to use current locale
setLocale('ko')
getLocaleDisplayName('en') // → "영어"Translation Key Listing
Get a list of all loaded translation keys:
import { getTranslationKeys, loadDictionaries } from 'inline-i18n-multi'
loadDictionaries({
en: { greeting: 'Hello', farewell: 'Goodbye' },
ko: { greeting: '안녕하세요' }
}, 'common')
// Get all keys for a specific locale and namespace
getTranslationKeys('en', 'common') // → ['greeting', 'farewell']
getTranslationKeys('ko', 'common') // → ['greeting']
// Omit namespace to get keys from all namespaces
getTranslationKeys('en') // → ['common:greeting', 'common:farewell']
// Omit all parameters to use current locale
getTranslationKeys() // → all keys for current localeMissing Translation Tracker
Collect missing translation keys at runtime to identify what needs translating:
import { trackMissingKeys, getMissingKeys, clearMissingKeys, t, loadDictionaries } from 'inline-i18n-multi'
loadDictionaries({ en: { greeting: 'Hello' } })
// Enable missing key tracking
trackMissingKeys(true)
// Use keys that don't exist
t('missing.key')
t('another.missing')
// Get all missing keys
getMissingKeys() // → ['missing.key', 'another.missing']
// Clear the tracked list
clearMissingKeys()
getMissingKeys() // → []
// Disable tracking
trackMissingKeys(false)Useful for discovering untranslated content during development and testing.
Locale Change Event
Subscribe to locale changes with onLocaleChange() to run custom logic when the locale switches:
import { setLocale, onLocaleChange } from 'inline-i18n-multi'
const unsubscribe = onLocaleChange((newLocale, previousLocale) => {
console.log(`Locale changed: ${previousLocale} → ${newLocale}`)
})
setLocale('ko') // logs: "Locale changed: en → ko"
unsubscribe() // stop listeningConfiguration
Configure global settings for fallback behavior and warnings:
import { configure, getConfig, resetConfig } from 'inline-i18n-multi'
configure({
fallbackLocale: 'en', // Final fallback (default: 'en')
autoParentLocale: true, // BCP 47 parent (zh-TW → zh)
fallbackChain: { // Custom chains
'pt-BR': ['pt', 'es', 'en']
},
warnOnMissing: true, // Enable warnings
onMissingTranslation: (w) => { // Custom handler
console.warn(`Missing: ${w.requestedLocale}`)
}
})Locale Fallback Chain
Automatic locale fallback with BCP 47 support:
setLocale('zh-TW')
it({ en: 'Hello', zh: '你好' }) // → '你好' (falls back to zh)
t('greeting') // Also works with dictionariesLanguage Pair Helpers
For common language combinations, use the shorthand helpers:
import { it_ja, en_zh, ja_es } from 'inline-i18n-multi'
// Korean ↔ Japanese
it_ja('안녕하세요', 'こんにちは')
// English ↔ Chinese
en_zh('Hello', '你好')
// Japanese ↔ Spanish
ja_es('こんにちは', 'Hola')Available helpers:
it(ko↔en),it_ja,it_zh,it_es,it_fr,it_deen_ja,en_zh,en_es,en_fr,en_deja_zh,ja_es,zh_es
API Reference
Core Functions
| Function | Description |
|----------|-------------|
| it(ko, en, vars?) | Translate with Korean and English |
| it(translations, vars?) | Translate with object syntax |
| setLocale(locale) | Set current locale |
| getLocale() | Get current locale |
Key-Based Translation
| Function | Description |
|----------|-------------|
| t(key, vars?, locale?) | Key-based translation with optional locale override |
| loadDictionaries(dicts, namespace?) | Load translation dictionaries with optional namespace |
| loadDictionary(locale, dict, namespace?) | Load dictionary for a single locale with optional namespace |
| hasTranslation(key, locale?) | Check if translation key exists (supports namespace:key) |
| getLoadedLocales() | Get array of loaded locale codes |
| getLoadedNamespaces() | Get array of loaded namespace names |
| getDictionary(locale, namespace?) | Get dictionary for a specific locale and namespace |
| clearDictionaries(namespace?) | Clear dictionaries (all or specific namespace) |
Configuration
| Function | Description |
|----------|-------------|
| configure(options) | Configure global settings (fallback, warnings, debug, missingVarHandler, icuCacheSize, persistLocale) |
| getConfig() | Get current configuration |
| resetConfig() | Reset configuration to defaults |
| loadAsync(locale, namespace?) | Asynchronously load dictionary using configured loader |
| isLoaded(locale, namespace?) | Check if dictionary has been loaded |
| parseRichText(template, names) | Parse rich text template into segments |
| clearICUCache() | Clear the ICU message AST cache |
| restoreLocale() | Restore locale from configured persistent storage (cookie or localStorage) |
| createScope(namespace) | Return a translation function scoped to the given namespace |
| getLocaleDisplayName(locale, displayLocale?) | Get human-readable display name for a locale using Intl.DisplayNames |
| getTranslationKeys(locale?, namespace?) | Get all loaded translation keys |
| trackMissingKeys(enabled) | Enable or disable missing translation key tracking |
| getMissingKeys() | Get all tracked missing translation keys |
| clearMissingKeys() | Clear the tracked missing keys list |
| onLocaleChange(callback) | Subscribe to locale changes, returns unsubscribe function |
| clearLocaleListeners() | Remove all locale change listeners |
| formatNumber(value, options?, locale?) | Format numbers (currency, percent, etc.) |
| formatDate(value, options?, locale?) | Format dates and times |
| formatList(values, options?, locale?) | Format lists (conjunction, disjunction) |
| tRaw(key, locale?) | Get raw template string without interpolation |
Custom Formatters
| Function | Description |
|----------|-------------|
| registerFormatter(name, formatter) | Register a custom ICU-style formatter |
| clearFormatters() | Clear all custom formatters |
Locale Detection
| Function | Description |
|----------|-------------|
| detectLocale(options) | Auto-detect user's locale from multiple sources |
React Hooks & Components
| Export | Description |
|--------|-------------|
| RichText | Rich text translation component with embedded components |
| useRichText(components) | Hook returning function for rich text translations |
| useLoadDictionaries(locale, ns?) | Hook for lazy loading dictionaries with loading state |
Types
type Locale = string
type Translations = Record<Locale, string>
type TranslationVars = Record<string, string | number | Date | string[]>
interface Config {
defaultLocale: Locale
fallbackLocale?: Locale
autoParentLocale?: boolean
fallbackChain?: Record<Locale, Locale[]>
warnOnMissing?: boolean
onMissingTranslation?: WarningHandler
debugMode?: boolean | DebugModeOptions
loader?: (locale: Locale, namespace: string) => Promise<Record<string, unknown>>
missingVarHandler?: (varName: string, locale: string) => string
icuCacheSize?: number
persistLocale?: PersistLocaleOptions
}
interface PersistLocaleOptions {
/** Storage backend */
storage: 'cookie' | 'localStorage'
/** Storage key (default: 'LOCALE') */
key?: string
/** Cookie expiry in days (default: 365, cookie only) */
expires?: number
}
interface DebugModeOptions {
showMissingPrefix?: boolean
showFallbackPrefix?: boolean
missingPrefixFormat?: (locale: string, key?: string) => string
fallbackPrefixFormat?: (requestedLocale: string, usedLocale: string, key?: string) => string
}
interface TranslationWarning {
type: 'missing_translation'
key?: string
requestedLocale: string
availableLocales: string[]
fallbackUsed?: string
}
type WarningHandler = (warning: TranslationWarning) => void
type CustomFormatter = (value: unknown, locale: string, style?: string) => string
type DetectSource = 'navigator' | 'cookie' | 'url' | 'header'
interface DetectLocaleOptions {
/** Locales your app supports */
supportedLocales: Locale[]
/** Fallback when no source matches */
defaultLocale: Locale
/** Detection sources in priority order (default: ['navigator']) */
sources?: DetectSource[]
/** Cookie name to read (default: 'NEXT_LOCALE') */
cookieName?: string
/** Accept-Language header value (for SSR) */
headerValue?: string
}
interface RichTextSegment {
type: 'text' | 'component'
content: string
componentName?: string
}Why Inline Translations?
Traditional i18n
Code → Key → JSON file → Translation
↑
Hard to traceInline i18n
Code ← Translation (same place!)| Aspect | Traditional | Inline | |--------|-------------|--------| | Finding text in code | Hard (key lookup) | Easy (direct search) | | Adding translations | Create key, add to JSON | Write inline | | Refactoring | Update key references | Automatic | | Code review | Check JSON separately | All visible in diff | | Type safety | Limited | Full support |
Framework Integrations
React
React hooks and components for inline translations. Includes LocaleProvider for context management, useLocale() hook for locale state, and T component for JSX translations. Automatic cookie persistence when locale changes.
npm install inline-i18n-multi-reactNext.js
Full Next.js App Router integration with SSR/SSG support. Server Components use async it(), Client Components use React bindings. Includes SEO utilities: createMetadata() for dynamic metadata, getAlternates() for hreflang links, and createI18nMiddleware() for locale detection.
npm install inline-i18n-multi-nextDeveloper Tools
CLI
Command-line tools for translation management. Find translations with inline-i18n find "text", validate consistency with inline-i18n validate, extract inline translations with inline-i18n extract, and generate coverage reports with inline-i18n coverage. Compare locales with inline-i18n diff en ko and view statistics with inline-i18n stats. Supports --watch mode for validate and typegen.
npm install -D @inline-i18n-multi/cliRequirements
- Node.js 18+
- TypeScript 5.0+ (recommended)
Documentation
Please read the full documentation on GitHub for:
- Complete API reference
- Framework integrations (React, Next.js)
- CLI tools
- Best practices and examples
License
MIT
