No description
  • TypeScript 98.6%
  • JavaScript 1.4%
Find a file
Andrés Correa Casablanca 2da4c82620
docs: document astro example
Signed-off-by: Andres Correa Casablanca <andreu@ctrlback.coop>
2026-06-18 11:07:59 +02:00
.hooks chore: init repository 2026-06-13 17:34:13 +02:00
examples docs: document astro example 2026-06-18 11:07:59 +02:00
packages feat: implement the i18n-extract-astro pkg 2026-06-14 13:01:21 +02:00
.gitignore chore: init repository 2026-06-13 17:34:13 +02:00
.prototools chore: init repository 2026-06-13 17:34:13 +02:00
CONTRIBUTING.md docs: extract contributing guidelines 2026-06-18 11:00:57 +02:00
LICENSE.md chore: init repository 2026-06-13 17:34:13 +02:00
package.json chore: init repository 2026-06-13 17:34:13 +02:00
pnpm-lock.yaml feat(example-astro): add Astro i18n integration example 2026-06-14 13:31:16 +02:00
pnpm-workspace.yaml feat(example-astro): add Astro i18n integration example 2026-06-14 13:31:16 +02:00
README.md docs: document astro example 2026-06-18 11:07:59 +02:00

@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:

  1. Write strings inline as MF2 in tr(...) calls.
  2. i18n-extract extract scans your sources into a template.
  3. i18n-extract sync creates or updates each locale's translation from the template (new messages become untranslated, existing ones are preserved, removed ones are marked obsolete).
  4. Translate, then i18n-extract coverage reports completeness.
  5. At runtime, build a catalog from a translation and construct a tr for 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/basic is 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/astro runs the same pipeline inside an Astro site: tr(...) calls written inline in .astro templates and a .ts API 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.