No description
- TypeScript 98.6%
- JavaScript 1.4%
|
|
||
|---|---|---|
| .hooks | ||
| examples | ||
| packages | ||
| .gitignore | ||
| .prototools | ||
| CONTRIBUTING.md | ||
| LICENSE.md | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
@ctrlback/i18n
A set of lightweight FLOSS internationalization libraries for TypeScript, built on Unicode MessageFormat 2.0 (MF2).
Messages are identified by their source string (gettext style): you call tr
with the untranslated MF2 source and get back its translation, falling back to
the source when none exists.
The toolkit is split into small, focused packages so you only depend on what you use, and the runtime core is ESM-only and runtime-agnostic (browser, Node, Deno, workers).
Packages
| Package | Runtime | Purpose |
|---|---|---|
@ctrlback/i18n-mf2 |
universal | Typed, Result-based wrapper over the messageformat v4 MF2 engine. |
@ctrlback/i18n-core |
universal | The constructible tr() factory, the catalog model, and the template/translation operations. |
@ctrlback/i18n-json-load |
universal + Node | Parse and validate catalog JSON (string or file) into typed documents. |
@ctrlback/i18n-json-persist |
universal + Node | Serialize catalog documents to deterministic JSON (string or file). |
@ctrlback/i18n-extract |
Node | CLI and library to extract tr(...) strings from sources and create, sync, and measure translations. |
Dependency graph:
messageformat v4
|
i18n-mf2
|
i18n-core
|
+---------+----------+
| |
i18n-json-load i18n-json-persist
\ /
\ /
i18n-extract (uses oxc-parser)
How it fits together
On disk, catalogs mirror gettext's .pot / .po split:
- a template (locale-agnostic, produced by the extractor) lists every source message;
- a per-locale translation holds the actual messages and a status.
The typical loop:
- Write strings inline as MF2 in
tr(...)calls. i18n-extract extractscans your sources into a template.i18n-extract synccreates or updates each locale's translation from the template (new messages becomeuntranslated, existing ones are preserved, removed ones are markedobsolete).- Translate, then
i18n-extract coveragereports completeness. - At runtime, build a catalog from a translation and construct a
trfor the locale.
Quick start
Runtime (@ctrlback/i18n-core):
import { createTranslator, toRuntimeCatalog } from '@ctrlback/i18n-core'
const built = toRuntimeCatalog({
format: 'ctrlback-i18n-translation@1',
locale: 'ca',
entries: [
{ source: 'Hello, {$name}!', context: null, message: 'Hola, {$name}!', status: 'translated' },
],
})
if (!built.ok) throw new Error(built.error.kind)
const tr = createTranslator({ locale: 'ca', catalog: built.value })
tr('Hello, {$name}!', { name: 'Kat' }) // 'Hola, Kat!'
tr('Untranslated') // 'Untranslated' (falls back to source)
MF2 gives you pluralization, ordinals, exact-value overrides, and select/gender natively:
tr(`.input {$count :number}
.match $count
one {{You have {$count} unread message}}
* {{You have {$count} unread messages}}`, { count: 3 })
// 'You have 3 unread messages'
Tooling (@ctrlback/i18n-extract):
i18n-extract extract --root src --template locales/messages.template.json
i18n-extract sync --template locales/messages.template.json --translations-dir locales --locales ca,es
i18n-extract coverage --template locales/messages.template.json --translations-dir locales --locales ca,es
Examples
examples/basicis a runnable, end-to-end walk-through: write strings, extract, sync, translate to Catalan, and render with plural and context selection. See its README.examples/astroruns the same pipeline inside an Astro site:tr(...)calls written inline in.astrotemplates and a.tsAPI route, locale negotiation in middleware, and three locales (including right-to-left Arabic) across static and on-demand pages. See its README.
Contributing
See CONTRIBUTING.md.
License
MIT. See LICENSE.md.