npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

inline-i18n-multi

v0.14.0

Published

Inline i18n - write translations inline, support multiple languages

Readme

inline-i18n-multi

npm version License: MIT

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:

  1. Search for "Hello" in JSON files
  2. Find the key greeting.hello
  3. Search for that key in your code
  4. 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-TWzhen)
  • 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 scoped t())
  • Unused Key Detection - CLI --unused flag to detect unused translation keys
  • TypeScript Type Generation - typegen command for auto-generating translation key type definitions
  • Context System - Contextual translation disambiguation (t('greeting', { _context: 'formal' }) with key#context dictionary keys)
  • Translation Extraction - Extract inline translations to JSON files (npx inline-i18n extract)
  • CLI Watch Mode - --watch flag for validate and typegen commands
  • 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, formatList powered by Intl APIs (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-multi

Quick 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 values
  • selectordinal: ordinal plural categories (one, two, few, other)
  • number: decimal, percent, integer, currency, compact, compactLong
  • date: short, medium, long, full
  • time: 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 namespace

Debug 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')  // → true

React 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 localStorage

Translation 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 --unused

Helps 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.ts

The 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,ja

Scans 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 --watch

Provides 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 locale

Missing 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 listening

Configuration

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 dictionaries

Language 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_de
  • en_ja, en_zh, en_es, en_fr, en_de
  • ja_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 trace

Inline 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-react

View React package →

Next.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-next

View Next.js package →


Developer 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/cli

View CLI package →


Requirements

  • 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