ConfigurationFormats

Formats

The twelve supported formats, how each one reads and writes its files, and the document key order guarantee.

verbatra works on your locale files through a format adapter. Each adapter reads a file into one format-neutral shape that the diff, hashing, and integrity checks run on, then writes it back in the file's original shape. You pick the format with the format field in your config. Twelve are supported: four JSON flavors, plus XLIFF, YAML, ARB, Java/Spring properties, Apple .strings, Xcode String Catalogs (.xcstrings), Android strings.xml, and gettext .po/.pot.

The four JSON formats

formatForPlaceholdersPluralsICU
i18next-jsoni18next{{name}} and $t(...) nesting referencesCLDR plural suffix on the keyno
vue-i18n-jsonvue-i18n{name}, {0}pipe in the valueno
next-intl-jsonnext-intlICU argument and tag namesICU plural or selectordinalyes
ngx-translate-jsonngx-translate{{name}}noneno

All four read nested JSON objects of string leaves. Their differences are the message syntax:

  • i18next uses {{double-brace}} interpolation and decides plural from the CLDR suffix on the key (_zero, _one, _two, _few, _many, _other). It additionally extracts and guards $t() nesting references (which splice another key's content into the value), for example $t(common.foo) and $t(common.foo, { options }), as placeholders, so a translation that drops or alters one fails the integrity check. Two limitations: nested parentheses inside the $t() options are not supported, and only the default $t( prefix is recognized.
  • Single-brace text under the double-brace formats. In i18next-json, ngx-translate-json, and yaml, a {name}-shaped token is literal text, not interpolation, so it is not extracted as a placeholder and a translation is free to drop or reword it. See the fabrication guard for the one direction in which it is still checked.
  • vue-i18n uses single-brace {name} and {0} tokens and decides plural from a pipe in the value.
  • next-intl values are ICU MessageFormat: the placeholders are the ICU argument and rich-text tag names, plural follows an ICU plural or selectordinal argument, and the ICU body is kept verbatim through the pipeline. A value that fails to parse as ICU is reported as invalid and skipped, never thrown.
  • ngx-translate shares i18next's {{double-brace}} interpolation but has no $t() nesting and no built-in plural or ICU. Its files may be flat (dotted keys) or nested, and verbatra preserves whichever style the file uses on write; a new target file is written nested. Uniquely, a file that mixes flat dotted keys with nested objects fails the read with MIXED_STRUCTURE, since such a file is ambiguous rather than guessable.

The single-brace fabrication guard

Available from 0.9.0

This needs verbatra 0.9.0 or newer. Earlier releases do not have it, so check your installed version with verbatra --version and upgrade if it is older.

Under i18next-json, ngx-translate-json, and yaml, a {name}-shaped token that shows up in a translation and never appeared in the source is rejected as a fabrication. That covers a placeholder invented outright and one altered into a name the source never had. The guard runs in that direction only: verbatra has no setting for i18next's custom interpolation.prefix and interpolation.suffix, so if you have switched to single-brace delimiters, a translation that silently drops one of your placeholders is not caught.

Namespace layouts

Splitting strings across several namespace files per locale is common in i18next projects, with common.json, auth.json, and so on sitting next to each other under a locale directory. One verbatra config addresses exactly one file per locale: files.pattern is a literal path with {locale} substituted, it is never expanded as a glob, and there is no namespace token. A pattern such as public/locales/{locale}/*.json is looked up as a file literally named *.json and fails with SOURCE_UNREADABLE.

A single-namespace layout is the one that works as configured:

files: {
  pattern: "public/locales/{locale}/common.json",
},

One config per namespace

For a multi-namespace project, write one config per namespace and run verbatra once per config. Any filename works, since --config loads an explicit path by its extension:

verbatra translate --config verbatra.common.config.ts
verbatra translate --config verbatra.auth.config.ts

Each run handles its own namespace: keys that are missing from a target file are translated as usual.

Only the namespace that ran last keeps change detection

Every config in the same working directory shares one verbatra.lock.json, and each translate run replaces that locale's recorded baselines with the keys it just processed. After the second run, the first namespace has no baselines left, so editing one of its source strings is reported as in sync by check and diff and is not retranslated. Changing the order of the runs does not help; whichever runs last wipes the other. To retranslate an edited string under this setup, delete that key from the target file (or delete the whole target file) so it counts as missing rather than changed.

XLIFF

The xliff format covers .xlf and .xliff files, XLIFF 1.2 (file/body/trans-unit) and 2.0 (file/unit/segment). Unlike the tree formats, an XLIFF document is a flat list of trans-units.

  • Keys. Each entry is keyed by its trans-unit id, falling back to resname. A unit with neither falls back to a positional key, which shifts when earlier units are added or removed, so give every unit a stable id. Two units resolving to the same key (typically a duplicate id) fail the read with INVALID_STRUCTURE rather than silently keeping one.
  • Values. The value comes from <target> when present, otherwise <source>.
  • Writes update targets in place. verbatra writes each value into its <target> and leaves <source>, attributes, and <note> elements untouched, so the document round-trips. Because a flat key/value map cannot reconstruct an XLIFF document, the destination file must already exist: verbatra updates targets in a pre-seeded target file (the standard XLIFF workflow) and does not create a missing one; a missing destination fails with INVALID_STRUCTURE.
  • Inline markup. Inline placeholder elements (x, g, bx, ex, ph, it, mrk) and single-brace {name} interpolation are extracted as placeholders and guarded across translation. On write, only those allow-listed elements (with their own minimal, non-executable attributes) survive as live markup in <target>; anything else in a translated value, including an unexpected element or a namespace mismatch, is written as plain text instead.
  • Notes as context. A trans-unit's <note> (in 2.0, the unit's <notes><note>, shared by every segment in the unit) is read as developer context: it reaches the provider as disambiguation context and appears in the Context column of an exported workbook. It is read-only and never written back.

YAML

The yaml format covers .yml and .yaml files: a nested tree in YAML syntax, the same shape as a nested JSON file, handled by the same tree pipeline. It assumes i18next-compatible {{double-brace}} interpolation, including the single-brace fabrication guard described above, and detects by extension only.

  • YAML comments are not carried across a write, the same way JSON has no comment concept.
  • Scalar non-string keys keep their string form (1: reads as "1", true: as "true").
  • A composite key (a map or sequence used as a mapping key) has no faithful string form, so the read fails with INVALID_STRUCTURE instead of silently collapsing it to text.
  • Malformed syntax is reported as INVALID_YAML, and anchor-alias expansion is bounded, so a hostile document cannot blow up the parse.

ARB

The arb format covers Flutter's .arb files: JSON with a flat object of message keys alongside @-prefixed metadata (@key per-message metadata and @@-prefixed globals such as @@locale).

  • Metadata is preserved, never translated. @-prefixed keys are stripped before translation and merged back on write in their document position. A destination file that exists but is corrupt fails the write rather than silently erasing its metadata.
  • Messages are ICU. Placeholders, plural handling, and ICU validation work exactly as they do for next-intl-json.
  • Descriptions become context. Each @key.description is read as developer context for that message: it reaches the provider as disambiguation context (never as text to translate) and appears in the Context column of an exported workbook. It is read-only and never written back.

Properties

The properties format covers Java and Spring .properties files, detected by the .properties extension: a flat list of key/value lines. Keys are kept verbatim as flat keys, never split into a tree.

  • Separators and comments. A key is separated from its value by =, :, or whitespace, with any spaces around the separator ignored, matching java.util.Properties. A line whose first non-blank character is # or ! is a comment.
  • Continuations and escapes. A line ending in a backslash continues onto the next. The standard escapes (\t, \n, \r, \f, \\, an escaped separator or comment character) and \uXXXX are decoded on read. Input is read as UTF-8; on write, every non-ASCII character is emitted as an ASCII-safe \uXXXX escape, so the file still loads under a legacy ISO-8859-1 reader.
  • Order, comments, and blank lines are preserved. A write re-reads the destination and keeps its key order, comments, and blank lines: each existing key line is rewritten in place with its new value, and a key the file does not yet have is appended in source order. A duplicate key keeps its first position and takes the last value, matching Properties.load.
  • Line endings follow the destination. A file containing any CRLF is written back entirely with CRLF, a CR-only file with CR, and everything else, including a file that does not exist yet, with LF. A mixed file therefore converges on one style rather than being kept line by line, and a \r or \n inside a value is still escaped rather than emitted as a line break. This keeps a two-key translation change a two-line diff in the CRLF repositories these files often live in.
  • Placeholders are MessageFormat. Values are read as java.text.MessageFormat, so {0}, the typed form {0,number}, the styled form {0,number,integer}, and named arguments such as {count} are extracted and guarded across translation. Sub-message arguments (plural, select, selectordinal, choice) are recognized, so translating the branch text stays a match while dropping or renaming an argument does not.
  • Limitation: single quotes are not interpreted. MessageFormat single-quote quoting is not honored, so a quoted literal such as '{0}' is still read as a placeholder. This is deliberate, so an ordinary apostrophe in translated text never swallows a following placeholder.

Apple .strings

Available from 0.10.0

This needs verbatra 0.10.0 or newer. Earlier releases do not have it, so check your installed version with verbatra --version and upgrade if it is older.

The apple-strings format covers Apple's .strings files for iOS and macOS: a flat sequence of "key" = "value"; statements, detected by the .strings extension. Plural rules live in the sibling .stringsdict file (same directory, same base name, .stringsdict extension), which verbatra reads and writes automatically alongside the .strings file, under the same apple-strings format id.

  • Encoding is UTF-8 only. Xcode's genstrings can emit UTF-16 with a byte-order mark. verbatra detects a UTF-16 byte-order mark, little or big endian, and rejects it with INVALID_STRUCTURE rather than parsing it into corrupt, NUL-interleaved keys. Re-save the file as UTF-8 (Xcode does this natively) before running verbatra on it.
  • Comments and escapes. A /* ... */ block comment immediately before an entry is read as that entry's description: it reaches the provider as disambiguation context and appears in the Context column of an exported workbook. It is read-only and never written back. A // line comment is preserved on write but carries no description. The escapes \", \\, \n, \t, and the four-hex-digit \U unicode escape are decoded on read; on write, a quote, backslash, newline, and tab are escaped and every other character, including non-ASCII text, is written as raw UTF-8.
  • Missing destination. Unlike XLIFF, a .strings destination that does not exist yet is synthesized from the entries rather than failing the write, matching the properties format's behavior.
  • Order, comments, and blank lines are preserved. A write re-reads the destination and rebuilds it from that structure: each existing key is rewritten in place with its new value, a key the destination lacks is appended in source order, and a key removed from the entries is dropped from the destination together with its own leading comment.
  • Placeholders are printf-style. %@, %d, %1$@, and the escaped literal %% are extracted and guarded across translation, with flags, width, precision, and length modifiers (%05.2f, %ld) treated as decoration and not part of the token's identity. A positional reorder such as %1$@ %2$@ becoming %2$@ %1$@ is accepted, since that is the purpose of positional specifiers. A bare % followed by ordinary text, as in "50% off", extracts nothing.
  • Plurals live in the sibling .stringsdict. Localizable.strings pairs with Localizable.stringsdict in the same directory; verbatra discovers it automatically and merges its plural categories into the same locale resource, so translating still targets one files.pattern per locale. Each CLDR category present in an entry (zero, one, two, few, many, other) becomes its own translatable entry, keyed with the same <key>_<category> suffix convention i18next-json uses, for example photo_count_one and photo_count_other. A locale that supplies only some categories, the common case since most languages need just one and other, round-trips with exactly those: none is invented for a target locale and none is dropped from the source. Printf placeholders inside a category's format string, such as %d in "%d photos", are extracted and guarded the same way as in .strings values. A malformed .stringsdict (invalid XML, a missing %#@variable@ substitution, or an unsupported plural category) raises a structured error naming the file and the key. A .stringsdict destination that does not exist yet is created the same way a .strings destination is, .lproj directory included, and its non-translatable structure (the format key, the substitution variable name, and the value type) is preserved across a rewrite.
  • One file per locale, .lproj included. files.pattern with {locale}.lproj/Localizable.strings addresses Apple's per-locale bundle layout directly: {locale} is a literal token substitution, so it needs no special locale style, and a write creates a missing {locale}.lproj directory the same way it creates any other missing destination directory.

Xcode String Catalogs (.xcstrings)

Available from 0.10.0

This needs verbatra 0.10.0 or newer. Earlier releases do not have it, so check your installed version with verbatra --version and upgrade if it is older.

The apple-xcstrings format covers Xcode's String Catalog files, .xcstrings, introduced in Xcode 15 to replace the .strings/.stringsdict pair for new projects: a single JSON document holding every locale's strings, plurals, and translation state together, detected by the .xcstrings extension. This is structurally different from every other format verbatra supports, which is one file per locale: an apple-xcstrings catalogue is one file for every locale at once.

  • One shared catalogue, not one file per locale. files.pattern still requires the {locale} token, but for this format it resolves to the same path regardless of which locale is substituted, for example {locale}Localizable.xcstrings addresses one Localizable.xcstrings file. The source locale and every configured target locale therefore share one physical file.
  • Writes into a shared catalogue serialize. Because every locale's write touches the same file, verbatra serializes every operation that can write it, including translate, a Studio edit, single-key retranslation, and workbook import, against one another. In practice this means an apple-xcstrings project runs those operations one at a time even when --concurrency is set above 1; every other format still runs its provider calls concurrently as configured.
  • The key is the source string. A catalogue key with no explicit localizations entry for the document's own declared sourceLanguage falls back to the key text itself, matching Xcode's own convention. A key missing an entry for any other locale is simply treated as not yet translated for that locale, the same way a missing key in any other format's target file is.
  • Plurals live in variations.plural. Each CLDR category (zero, one, two, few, many, other) present under a locale's variations.plural becomes its own translatable entry, keyed with the same <key>_<category> suffix convention i18next-json and the Apple .strings format's .stringsdict support both use, for example %lld photos_one and %lld photos_other. A locale that supplies only some categories round-trips with exactly those: none is fabricated and none is dropped.
  • Placeholders are printf-style. %@, %d, %1$@, %lld, and the escaped literal %% are extracted and guarded across translation the same way Apple .strings values are; a length modifier such as the ll in %lld is decoration, not part of the token's identity, so %lld and %d are the same placeholder.
  • shouldTranslate: false is respected. A key the catalogue marks this way is never sent to a provider and is never written back changed.
  • Writes patch the document, they do not rebuild it. A write re-reads the current catalogue and updates only the localizations it touches, so extractionState, every other locale, non-translatable entries, plural categories, the catalogue's top-level version, and its sourceLanguage all survive untouched. A value verbatra just translated gets stringUnit.state: "translated"; an unchanged value's existing localization, state included, is left byte-identical rather than rewritten.
  • The destination catalogue must already exist. Unlike .strings or .properties, verbatra does not create a new .xcstrings catalogue: create it in Xcode first, then point files.pattern at it.
  • Malformed input is specific. A structured AdapterError names the file and, where the problem is inside one entry, the key and locale too, for example an entry missing both a stringUnit and a variations.plural field, or a plural category outside the CLDR set.

Android strings.xml

Available from 0.10.0

This needs verbatra 0.10.0 or newer. Earlier releases do not have it, so check your installed version with verbatra --version and upgrade if it is older.

The android-xml format covers Android's resource files, res/values/strings.xml for the source locale and res/values-<qualifier>/strings.xml for every target, detected by the .xml extension. Point files.localeStyle at android (see the config file) so {locale} in files.pattern expands to the right resource-qualifier directory instead of a literal BCP-47 tag.

  • <string> and <plurals>. A <string name="key">value</string> is one entry. A <plurals name="key"> becomes one entry per <item quantity="..."> (zero, one, two, few, many, other), keyed as key[quantity], for example count[one] and count[other]. A locale that supplies only some quantities round-trips with exactly those: none is fabricated and none is dropped. Every resource name is validated against Android's identifier grammar (a letter or underscore, then letters, digits, or underscores) before it becomes a key, so a name cannot forge a [quantity]-shaped collision with real plural key space.
  • translatable="false" is respected. A <string> or <plurals> carrying this attribute is never sent to a provider and is left untouched on disk.
  • formatted="false" is translated normally. The attribute only tells Android's own build tooling not to validate printf arguments; it does not change what verbatra does. %s/%d-style placeholders are still extracted and guarded the same way.
  • <string-array> and inline markup are read-through in this version. A <string-array> and a <string> whose content is not plain text (an inline element such as <b>, <xliff:g>, or a CDATA section) are preserved exactly as they are and never enter verbatra's translatable set. Item-level translation for these is a possible future addition, not supported today.
  • Escaping is decoded on read, re-encoded on write. \', \", \n, \t, and a leading \@ or \? are decoded to their literal characters when read, and the same characters (plus a leading, unescaped @ or ?) are re-escaped on write; XML's own &, <, and > entities are handled independently by the XML layer underneath, so a translated value containing any of these characters is written back safely without you having to escape it yourself.
  • Placeholders are printf-style. %s, %d, %1$s, and the escaped literal %% are extracted and guarded across translation. A bare % followed by ordinary text, as in "50% off", extracts nothing: the extractor does not treat a space as a valid conversion flag, unlike Java's own String.format, so a percentage sign in ordinary prose is never mistaken for a placeholder.
  • Writes update targets in place; a missing destination is synthesized. An existing res/values-<qualifier>/strings.xml is patched: a translated key is updated, a key removed from the source is deleted, and the parent directory is created if it does not exist yet. A destination that doesn't exist yet is created from scratch, res/values-<qualifier>/ directory included.
  • Limitation: a key demoted to read-through after translation is pruned, not preserved. If a key was already translated in a target file and then, in the source file, gains translatable="false" or inline markup, the next run cannot tell that apart from the key having been deleted outright, and removes the stale translation from the target file. This is narrow (it only affects a key whose source classification changes after it was already translated) and recoverable through version control or a fresh translate run; it does not affect a key that keeps its original classification, including ordinary new, changed, and removed keys.
  • Limitation: --prune has no plural-quantity awareness. verbatra's orphan and --prune detection compares keys against the source's own key set, with no special case for key[quantity] groups. A target locale needing more plural quantities than the source declares (for example a language with more grammatical number distinctions) has target-only quantities that a --prune run cannot distinguish from a genuinely removed key, and can delete them. Off by default; an ordinary verbatra translate run without --prune is unaffected.
  • Malformed input is specific. Malformed XML raises a structured AdapterError naming the problem; a document whose root element is not <resources>, a resource name that fails the identifier grammar, a <plurals> item with a quantity outside the CLDR set, or two elements resolving to the same key are all reported by name rather than as a generic parse failure. A DTD or entity declaration is rejected outright.

gettext .po/.pot

Available from 0.10.0

This needs verbatra 0.10.0 or newer. Earlier releases do not have it, so check your installed version with verbatra --version and upgrade if it is older.

The gettext-po format covers GNU gettext's .po and .pot catalogs, detected by either extension: a flat sequence of msgid/msgstr entries, keyed by their msgid, never split into a tree.

  • msgctxt disambiguation. A msgctxt before an entry composes with its msgid into one key, so two entries sharing a msgid under different contexts never collide. The composite uses a reserved private-use codepoint (U+E000) as its internal separator, a character no real .po file legitimately contains; a source string that does contain it is rejected with a structured error rather than silently corrupting the key.
  • Plurals are msgid_plural/msgstr[n], keyed by index, not CLDR category. Each msgstr[n] becomes its own entry, keyed as key[n] (n the raw decimal index), for example one item[0] and one item[1]. This is deliberately not the <key>_<category> suffix the JSON formats and Apple's .stringsdict use: turning a gettext index into a CLDR category would require evaluating the file's own Plural-Forms C expression, which verbatra does not do. The Plural-Forms header is preserved verbatim as untouched text; verbatra only reads the nplurals=N integer out of it, to bound-check that every msgstr[n] index is in range. A file with plural entries and no Plural-Forms header is rejected, matching what msgfmt --check itself enforces.
  • fuzzy entries are read, not skipped. A #, fuzzy entry's existing msgstr is read as that entry's current value, the same as any other translated entry; verbatra does not treat fuzzy as meaning missing. The flag is preserved verbatim on every write and is never added or cleared automatically.
  • Comments and the header round-trip untouched. #. developer comments become the entry's description: they reach the provider as disambiguation context and appear in the Context column of an exported workbook. #: source references and other #, flags are preserved on write but not otherwise interpreted. A #~ obsolete block is preserved as inert text and never becomes a translatable entry. The header entry (the msgid "" block carrying Content-Type, Plural-Forms, and similar metadata) is never touched by a write.
  • .pot templates read cleanly. A .pot source template, whose entries carry an empty msgstr, reads without error; each entry's value is simply the empty string until translated.
  • Missing destination. Like the properties and Apple .strings formats, a .po destination that does not exist yet is synthesized from the entries rather than failing the write, including a minimal header. A synthesized file with plural entries gets a Plural-Forms header sized to the highest index present; for one or two forms this is the standard universal expression, for three or more it is a safe in-range fallback rather than a guessed, possibly wrong, linguistic rule; seed the destination from a real target-locale template first if you need the exact grammar.
  • Placeholders are printf-style, including Python's named form. %s, %d, %1$s, the Python-style %(name)s, and the escaped literal %% are extracted and guarded across translation.
  • Malformed input is specific. A structured AdapterError names the offending msgid and the physical line, for example an unterminated quoted string, an unknown escape sequence, a non-contiguous msgstr[n] index, or a plural entry with no Plural-Forms header, rather than a generic parse failure.
  • Limitation: --prune has no plural-index awareness. verbatra's orphan and --prune detection compares keys against the source's own key set, with no special case for key[n] groups. A target locale needing more plural forms than the source declares (for example a language with more grammatical number distinctions) has target-only indices that a --prune run cannot distinguish from a genuinely removed key, and can delete them. Off by default; an ordinary verbatra translate run without --prune is unaffected.

Document key order

Writes preserve your document's key order. The JSON family, YAML, and ARB round-trip keys exactly in document order:

  • An integer-like key such as "2", "10", or "404" keeps its position instead of being hoisted to the front and re-sorted, so a file keyed by numeric ids, HTTP status codes, or years stays in its own order.
  • A key that a translate run adds to a target file appends after the target's existing keys, following the source document's order, rather than being inserted alphabetically.
  • ARB metadata blocks round-trip in their document position too.

XLIFF is unaffected: it updates existing <target> elements in place, so the document's order was never rebuilt in the first place.

Dotted keys and collisions

For i18next-json, vue-i18n-json, and next-intl-json, a literal dotted leaf (a key such as "foo.bar" used as a single leaf) round-trips losslessly: verbatra reads it and writes it back with its on-disk shape preserved, not re-nested into foo then bar. Real nested paths stay nested.

The one case that fails by design is a genuine collision, where one file expresses the same effective path both as a literal dotted leaf and as a real nested path (for example "foo.bar" alongside "foo": { "bar": ... }). That read fails with INVALID_STRUCTURE rather than guessing or corrupting data.

ngx-translate-json treats a dotted key as a nested path rather than a literal leaf, so there is no dotted-vs-literal ambiguity to preserve, but it gets the same collision safety: a dotted key whose path collides with a real nested path, including one being an ancestor of the other, fails with INVALID_STRUCTURE instead of silently overwriting a value.

How files are read and written

verbatra caps input size and nesting depth on read, and resists a file changing underneath it. It writes atomically: it writes to a temporary file, then renames it into place, so an interrupted write never leaves a half-finished locale file. When verbatra cannot handle a file, it raises a structured, secret-free error with a stable code rather than a raw one: INVALID_JSON, INVALID_YAML, or INVALID_XML for malformed syntax, INVALID_STRUCTURE for a parseable file of the wrong shape, MAX_DEPTH_EXCEEDED and INPUT_TOO_LARGE for the caps, and MIXED_STRUCTURE for ngx-translate's mixed-style case.

A tree-based locale file (the JSON family, YAML, and ARB) can carry a leaf that is not a string, for example a count: 5 or enabled: true value sitting next to the translatable keys. verbatra accepts these: a leaf can be a string, a number, a boolean, or null, and only a leaf of another type, such as an array, fails with INVALID_STRUCTURE. A non-string leaf is excluded from the translatable set: it is never translated, hashed, diffed, or checked for placeholders or ICU. Exclusion is not preservation: if verbatra later writes that same locale file, the write is rebuilt from the strings it manages, so a non-string leaf present at read time is not carried into the output. If such a value needs to survive a rewrite, keep it out of the files verbatra writes to. XLIFF trans-unit values and .properties values are always strings, so this does not apply there.

Why the format matters

The format tells verbatra two things it needs for a safe run. First, the placeholder syntax, so the placeholder integrity check knows what to compare before and after translation. Second, for the ICU formats, which values to validate, so an invalid ICU source key is skipped rather than sent in a broken state. The wrong format compares the wrong tokens, so match it to your i18n library: i18next-json for i18next, vue-i18n-json for vue-i18n, next-intl-json for next-intl, ngx-translate-json for ngx-translate, xliff for XLIFF files, yaml for YAML-based i18n, arb for Flutter, properties for Java or Spring .properties files, apple-strings for Apple .strings files, apple-xcstrings for Xcode String Catalog .xcstrings files, android-xml for Android strings.xml, and gettext-po for gettext .po/.pot catalogs.

Edit on GitHub