@draggable/i18n

中文文档
1.2.2发布于 2026/11/06MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 4388

A simple yet robust i18n solution for universal JavaScript apps.

原始使用文档
文档目录15 节
  1. Features:
  2. Install
  3. Usage
  4. Options
  5. Translating
  6. Fallback locales
  7. Plurals
  8. Missing keys and arguments
  9. Add a Language after initialization
  10. Put a string
  11. Change language
  12. Process a config
  13. Server-side usage
  14. TypeScript
  15. .lang files

i18n NPM Version

Add multi-lingual support to any JavaScript app or module with this light-weight package. Used by Formeo.

Features:

  • Isomorphic/Universal: works in the browser and in Node (no fetch needed when languages are preloaded)
  • easy translation of static text files
    • benefits: anyone can translate, no more xliff
    • reduced margin of error.
  • Variable support
  • Fallback locales and plurals
  • TypeScript types included
  • Dependency free
  • Fast

Install

npm install @draggable/i18n

Usage

The default export is a shared singleton instance, I18N is the class.

import i18n, { I18N } from '@draggable/i18n'

const i18nOptions = {
  extension: '.lang', // extension used by your language files, defaults to ".lang"
  location: 'https://location.of/language/files/', // absolute or relative url to language files
  langs: [
    // locales you would like to be active
    'en-US',
    'pt-BR',
    'de-DE', // will attempt to load `https://location.of/language/files/de-DE.lang` with current config
  ],
  locale: 'en-US', // default locale
  override: {
    'en-US': {
      autocomplete: 'Autofinish', // override individual keys or provide entire language definition
    },
  },
}

// configure the singleton and load the current locale
await i18n.init(i18nOptions)

// or create a separate instance
const myI18n = new I18N(i18nOptions)
await myI18n.init()

I18N(options) (without new) returns the singleton, the same instance as the default export. Options passed this way are only used if the singleton has not been created yet; use i18n.init(options) to reconfigure it.

... then in your app

<label>{i18n.get('addOption')}</label>

Options

OptionTypeDefaultDescription
extensionstring'.lang'extension of remote language files
locationstring'assets/lang/'url or path of the directory containing language files
langsstring[]['en-US']available locales, handy for populating a language selector
localestring'en-US'initial locale, falls back to the first of langs when empty
preloaded{ [locale]: { [key]: string } }{}languages available without fetching
override{ [locale]: { [key]: string } }{}keys that take precedence over loaded and preloaded languages
fallbackLocalestring | string[]–locale(s) to fall back to when a key is missing, see Fallback locales
onMissing(info) => string | void–called for missing keys and arguments, see Missing keys

Translating

// languages: { 'en-US': { greeting: 'Hello {name}', total: '{count} of {count}' } }

i18n.get('greeting', { name: 'Ana' }) // 'Hello Ana'
i18n.get('total', 3) // '3 of 3', a primitive replaces every placeholder
i18n.get('greeting') // 'Hello {name}', no args: no replacement
i18n.get('missing') // undefined

// t() always returns a string, falling back to the key
i18n.t('greeting', { name: 'Ana' }) // 'Hello Ana'
i18n.t('missing') // 'missing'

// per-call locale, without changing the current locale
i18n.get('greeting', { name: 'Ana' }, 'de-DE') // 'Hallo Ana'
i18n.t('greeting', { name: 'Ana' }, 'de-DE') // 'Hallo Ana'
i18n.getValue('greeting', 'de-DE') // 'Hallo {name}', raw string

// t() bound to a locale, handy on servers handling several locales at once
const t = i18n.forLocale('de-DE')
t('greeting', { name: 'Ana' }) // 'Hallo Ana'

Missing placeholder arguments are replaced with an empty string.

Fallback locales

Without fallbackLocale a missing key is looked up in any loaded language that has it (legacy behaviour). With fallbackLocale set the lookup order is:

  1. the requested locale, eg. pt-BR
  2. its base language, if loaded, eg. pt
  3. each fallback locale, in order
  4. t() returns the key, get() returns undefined
const i18n = new I18N({
  locale: 'pt-BR',
  fallbackLocale: ['pt', 'en'],
  preloaded: {
    'pt-BR': { save: 'Salvar' },
    pt: { cancel: 'Cancelar' },
    en: { save: 'Save', cancel: 'Cancel', help: 'Help' },
  },
})

i18n.t('save') // 'Salvar'
i18n.t('cancel') // 'Cancelar'
i18n.t('help') // 'Help'
i18n.getLocaleChain('pt-BR') // ['pt-BR', 'pt', 'en']

When fallbackLocale is set, keys whose value is an empty string ('') count as translated and are returned as '' instead of falling back. Without it, empty strings are treated as missing.

init() also loads fallback locales that are not loaded yet.

Plurals

When a key is missing and args is a number or has a numeric count, plural forms of the key are used. The form is chosen with Intl.PluralRules for the locale: key.zero (only for a count of 0), then key.<category> (zero, one, two, few, many, other), then key.other. {count} and other placeholders are replaced as usual. If Intl.PluralRules is not available, 1 is one and anything else is other.

items.zero = No items
items.one = {count} item
items.other = {count} items
files.other = {count} files in {folder}
i18n.t('items', 0) // 'No items'
i18n.t('items', { count: 1 }) // '1 item'
i18n.t('items', 5) // '5 items'
i18n.t('files', { count: 2, folder: 'docs' }) // '2 files in docs'

A key that exists as-is (eg. items) always takes precedence over its plural forms.

Missing keys and arguments

const i18n = new I18N({
  onMissing: ({ key, locale, type, arg }) => {
    console.warn(`missing ${type}`, locale, key, arg)
    if (type === 'key') return `[${key}]` // optional replacement
  },
})

onMissing is called by get() and t() for missing keys (type: 'key') and for missing placeholder values (type: 'arg', with arg set to the placeholder name). Returning a string uses it instead; returning nothing keeps the default (undefined from get(), the key from t(), '' for arguments).

Add a Language after initialization

i18n.addLanguage('fr-FR', {
  addOption: 'Ajouter une option',
})

// or from the contents of a .lang file
i18n.addLanguage('fr-FR', 'addOption = Ajouter une option')

Adding the same locale again merges the keys; getLangs and loaded do not get duplicates.

Put a string

i18n.put('greeting', 'Hi {name}') // current language
i18n.put('greeting', 'Oi {name}', 'pt-BR') // a specific locale

Change language

await i18n.setCurrent('de-DE') // fetches de-DE.lang unless already loaded or preloaded

Process a config

i18n.processConfig({ locale: 'de-DE', override: { 'de-DE': { save: 'Speichern' } } })

processConfig(options) applies options without fetching anything; init(options) applies them and loads the current locale.

Server-side usage

No fetch is needed when languages are preloaded or added from strings:

import { readFileSync } from 'node:fs'
import { I18N } from '@draggable/i18n'

const i18n = new I18N({
  locale: 'en-US',
  fallbackLocale: 'en-US',
  preloaded: {
    'en-US': I18N.parse(readFileSync('lang/en-US.lang', 'utf8')),
  },
})
i18n.addLanguage('de-DE', readFileSync('lang/de-DE.lang', 'utf8'))

// per request
const t = i18n.forLocale(request.locale)
t('greeting', { name: user.name })

setCurrent() and init() do not fetch locales that are preloaded or added with addLanguage(). CommonJS is supported as well: const { I18N } = require('@draggable/i18n').

TypeScript

Types are included:

import i18n, { I18N, type I18NOptions, type I18NInstance } from '@draggable/i18n'

const options: I18NOptions = { locale: 'en-US', fallbackLocale: 'en-US' }
const instance: I18NInstance = new I18N(options)
const label: string = instance.t('addOption')

.lang files

# comments start with # or ; at the beginning of a line
de-DE = Deutsch
en-US = German

addOption = Option hinzufügen
allFieldsRemoved = Alle Felder wurden entfernt.
allowMultipleFiles = Upload mehrerer Dateien erlauben
autocomplete = Autovervollständigung

; a trailing backslash continues the value on the next line,
; leading whitespace of the next line is ignored
longText = Dies ist ein langer Text, \
    der über mehrere Zeilen geht.
  • one key = value per line, keys and values are trimmed
  • \n and \r\n line endings are supported, blank lines are ignored
  • # or ; only start a comment at the beginning of a line (after optional whitespace), not after a value
  • a single trailing \ joins the next line, \\ at the end of a line is kept as-is

Parse file contents with I18N.parse(text) (alias of I18N.processFile(text)).

版本与发布记录

@draggable/i18n 共发布 11 个版本,首次发布于 2024/10/30;过去 12 个月发布了 6 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.2.2latest2026/11/067 天前
1.2.12026/10/2915 天前
1.2.02026/10/2915 天前
1.1.22026/10/2915 天前
1.1.12026/05/096 个月前
1.1.02026/01/139 个月前

关于 @draggable/i18n 的常见问题

@draggable/i18n 是什么?

A simple yet robust i18n solution for universal JavaScript apps.

如何安装 @draggable/i18n?

在项目目录运行 npm install @draggable/i18n,或使用 pnpm add / yarn add / bun add。

@draggable/i18n 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@draggable/i18n 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@draggable/i18n 可以商用吗?

@draggable/i18n 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@draggable/i18n 的下载量有多少?

根据 npm 官方下载统计,@draggable/i18n 在 2026/10/13 – 2026/11/11 这 30 天内被下载 4,388 次,日均约 146 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@draggable/i18n 还在维护吗?

最新版本 1.2.2 发布于 2026/11/06(7 天前);至今共发布 11 个版本,其中过去 12 个月发布了 6 个;首次发布于 2024/10/30。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。