Docs

GlotFox translation file format

Version 1. This is the on-disk format shared by the CLI, the API and Git sync. The extractor and the type generator are built from this spec.

Key words MUST, SHOULD and MAY are used as in RFC 2119.

1. Directory layout

glotfox.config.json
locales/
  en/                    <- source locale
    common.json
    common.meta.json     <- optional descriptions (source locale only)
    checkout.json
  de/
    common.json
    checkout.json
  pt-BR/
    common.json

2. Namespace file

A namespace file is a single flat JSON object. Each member maps a key to an ICU MessageFormat string.

{
  "cart.items": "{count, plural, one {# item} other {# items}}",
  "cart.title": "Your cart",
  "greeting": "Hello, {name}!"
}

2.1 Key naming rules

3. Values: ICU MessageFormat

Values use ICU MessageFormat. Supported constructs:

3.1 Plain text

{ "welcome": "Welcome back" }

3.2 Placeholders (arguments)

{ "greeting": "Hello, {name}!" }

Argument names match [A-Za-z_][A-Za-z0-9_]*. Positional (numeric) arguments are discouraged.

3.3 Typed arguments

{
  "invoice.total": "Total: {amount, number, currency}",
  "invoice.due": "Due {date, date, medium}",
  "event.time": "Starts at {time, time, short}"
}

Supported types: number (styles integer, percent, currency, or a skeleton ::...), date and time (styles short, medium, long, full, or a skeleton ::...).

3.4 Plural

{
  "cart.items": "{count, plural, =0 {Your cart is empty} one {# item} other {# items}}"
}

3.5 Select

{
  "invite": "{gender, select, female {She invited you} male {He invited you} other {They invited you}}"
}

other is REQUIRED.

3.6 Nesting

Plural and select may be nested:

{
  "share": "{gender, select, other {{count, plural, one {They shared # photo} other {They shared # photos}}}}"
}

3.7 Escaping

3.8 Rich text tags (optional)

Simple tags without attributes are allowed: "Read the <link>docs</link>". The runtime maps the tag name to a component or function.

3.9 Validation and types

The type generator derives, from each source message, the set of arguments and their types:

ICU constructGenerated TypeScript type
{x}string | number
{x, number...}, plural, selectordinalnumber
{x, date...}, {x, time...}Date | number
{x, select, a {..} b {..} other {..}}'a' | 'b' | (string & {})
<tag>..</tag>(chunks) => ReactNode (or framework equivalent)

A target-locale message is valid when it parses, uses the same argument names as the source (it MAY omit an argument but MUST NOT introduce one), has compatible argument types, and has all required plural/select branches (other).

4. Descriptions and context

Translators and the AI need context. It is stored outside the translation file so translation files remain plain key -> string maps (simple to load at runtime and to diff).

locales/<source>/<namespace>.meta.json:

{
  "cart.items": {
    "description": "Shown under the cart icon. `count` is the number of line items.",
    "maxLength": 40,
    "screenshot": "docs/screens/cart.png"
  },
  "greeting": { "description": "Header greeting on the dashboard." }
}

5. Extraction

The extractor scans files matched by include/exclude (section 6) for translation calls and writes keys and source strings into the source locale's namespace files. It also adds new keys, reports keys that are no longer used (removed only with --prune), and creates missing target-locale files (with empty values or omitting keys, per output.emptyValues, default omit).

6. Config file: glotfox.config.json

Located at the project root (the nearest ancestor of the working directory containing it). JSON, UTF-8.

{
  "$schema": "https://glotfox.app.dukauskas.lt/schema/config.v1.json",
  "version": 1,
  "sourceLocale": "en",
  "targetLocales": ["de", "fr", "pt-BR"],
  "defaultNamespace": "common",
  "include": ["src/**/*.{ts,tsx,js,jsx,vue,svelte}"],
  "exclude": ["**/*.test.*", "**/node_modules/**"],
  "output": {
    "dir": "locales",
    "pattern": "{locale}/{namespace}.json",
    "format": "json",
    "emptyValues": "omit",
    "types": "src/glotfox.d.ts"
  }
}
FieldTypeRequiredDefaultNotes
$schemastringno—Editor hint only.
versionintegerno1Config schema version.
sourceLocalestringyes—BCP 47 tag.
targetLocalesstring[]yes—May be empty. Must not contain sourceLocale or duplicates.
defaultNamespacestringno"common"Namespace for keys without a prefix.
includestring[]no["src/**/*"]Globs, relative to the config file, for the extractor.
excludestring[]no["**/node_modules/**"]Applied after include.
output.dirstringno"locales"Root of locale directories.
output.patternstringno"{locale}/{namespace}.json"Must contain {locale} and {namespace}.
output.formatstringno"json""json", "po" or "yaml" (see section 7). Set a matching output.pattern; its default follows the format (.json, .po, .yaml).
output.emptyValues"omit" | "empty"no"omit"How untranslated keys are written in target locales.
output.typesstring | nullnonullPath of the generated TypeScript declaration file; null disables it.

7. Other formats: PO and YAML

Select a format with output.format (and a matching output.pattern, e.g. {locale}/{namespace}.po). All formats hold the same model (key, ICU message); glotfox extract, check, push, pull and GitHub sync/import work with each. Descriptions always stay in the JSON file <namespace>.meta.json. Files are written with sorted keys and a trailing newline, so parse then serialise gives identical output.

7.1 PO (gettext)

Each key is one entry: msgctxt is the key, msgid repeats it and msgstr is the ICU message. Entries without msgctxt use msgid as the key. Comments, flags and the header are ignored when reading. Gettext plural entries (msgid_plural, msgstr[n]) are read as an ICU {count, plural, …} message (categories chosen by nplurals; %d becomes #); they are written back as the ICU message in msgstr.

msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Language: en\n"

msgctxt "cart.items"
msgid "cart.items"
msgstr "{count, plural, one {# item} other {# items}}"

7.2 YAML

Dotted keys become nested maps (cart.items is items under cart), values are double-quoted strings. Reading accepts plain, double- and single-quoted strings and comments; lists, anchors, flow collections and block scalars are rejected. A key cannot also be a prefix of another key.

cart:
  items: "{count, plural, one {# item} other {# items}}"

8. Versioning

This is format version 1. Breaking changes bump version in the config; tools MUST refuse a config whose version they don't know.