Exchanging translations with a service
Most teams send their wording to a translation service, such as Crowdin,
Lokalise or any that reads XLIFF or JSON. jadpo translations export [<locale>] writes the files to upload, and jadpo translations import <file> reads the translations back, both with --json. They exchange
every message, enum with wording, failure and email subject the default
locale words, Jadpo's own messages (those that differ by language), the
/// description of each, and each locale's wording.
Export
export writes one file for each translated locale (a supported locale
other than the default and the pseudo-locale), or for the one named, to
build/translations/<locale>.xlf. The file is the same each time, and the
next export overwrites it. --format json writes <locale>.json instead,
and --out <folder> writes to another folder, relative to the project and
inside it. Naming the default locale writes the source file to upload: the
same units with no translations.
A unit is one wording: a plain message, a value of the enum that picks a
wording, or a plural form of the locale. Its id is <domain>.<message>,
with .<value> or .<form> after it, which XLIFF splits into <file id>
and <unit id>. In place of a domain, shared stands for
shared/translations/, jadpo for Jadpo's own messages, and a library
package's folder name for its text. A domain named jadpo would share ids
with Jadpo's own messages, so export refuses it. Wording every locale
shares (TextDirection's) isn't exchanged.
The wording is written as it is in the language file, {field}
placeholders and doubled braces included, not as XLIFF inline <ph>
codes. So the same text goes to both formats, comes back exactly, and a
service sees ordinary text (both services recognise {x} as a
placeholder). A plural form the default locale has no form of is
translated from the default's other.
Wrote build/translations/de_de.xlf de_de 5 units, 5 translatedWrote build/translations/pl_pl.xlf pl_pl 7 units, 7 to translateExported 2 locales: 12 units, xliffnext: jadpo translations import build/translations/pl_pl.xlf reads the translations back
The file formats
The XLIFF is version 2.0 (namespace
urn:oasis:names:tc:xliff:document:2.0), which every tool that reads 2.x
reads. It has a <file> for each domain and a <unit> for each wording.
Each unit has description, context (the enum value or plural form it is
for) and placeholders notes, and a stale note for wording translated
from default wording that has since changed. Its <segment> has the
state initial without a translation, translated with one, and
reviewed when a person accepted it.
The JSON is { "<id>": { "message": ..., "description": ... } }, the shape
Chrome extensions use for messages.json. It has a place for a description
that Crowdin imports as it is ("Chrome JSON": message and description
fields). message is empty until translated. A flat { id: text } object
is the shape both services take, but it carries no description, which the
exchange exists to send. Whether Lokalise reads XLIFF 2.0, and imports the
Chrome shape, hasn't been checked; each format is the other's fallback.
<unit id="parcels.one"> <notes> <note category="description">How many parcels an order has, on the tracking page.</note> <note category="context">Used for the plural form `one` of `count`.</note> <note category="placeholders">{count}</note> </notes> <segment state="translated"> <source xml:space="preserve">{count} parcel</source> <target xml:space="preserve">{count} Paket</target> </segment></unit>
Import
import tells a file's format by what it starts with: < for XLIFF 2.0
or 2.1, { for JSON. Plain strings are accepted for message, so a flat
object from a service that can't keep descriptions reads too. The locale is
the one the file names (XLIFF's trgLang), --locale, or a JSON file's
name (pl_pl.json). XLIFF 1.2 is refused, with a message saying to ask the
service for 2.x, since the two are built differently. Inline codes a
service adds on the way back (<ph>, <pc>) are read as the text they
stand for. A unit with no translation is left alone. A status in the file
(reviewed) is never read back: only a person accepts a translation, with
jadpo translations accept.
Import then does what a person editing the language files would have to:
- each changed wording goes into its language file where its member is, keeping the file's other members, comments and order, or at the end;
- a missing file is created;
- the files are laid out by
jadpo fmt; - the folder's
sources.jadpo_recordgets a line for each changed translation, made from today's default wording and not accepted. Only changed translations get one, so a round trip never creates or changes a line.
Before anything is written, the project is checked as jadpo check checks
it, by the checker's own rules with nothing special for the exchange. Any
new finding refuses the whole import, with that finding's own rule,
pointing at the unit of the imported file that caused it:
translation.placeholder_unknown, translation.wording_invalid,
translation.variant_missing for a plural form or enum value left out, or
the declinable translation.field_unused for a placeholder every wording
dropped.
Two errors belong to the exchange itself:
- A unit naming nothing the project words, or a plural form the locale
doesn't use, is
translation.exchange_unit_unknown, with the nearest id. - A file that isn't XLIFF 2.x or JSON, isn't well formed, holds a unit
twice or in several segments, or was translated from another default
language is
translation.exchange_file_invalid.
Both are command errors, so the command
exits 1: it ran, and the file was wrong. All the reasons are listed at
once, and nothing is written. An in-progress locale may be translated in
part, as its language file may. Exporting and importing with nothing
changed writes nothing, and says Already up to date.
Wrote domains/shipping/translations/pl_pl.jadpo farewell added order_shipped.formal added order_shipped.casual added parcels.one added parcels.few added parcels.many added parcels.other addedWrote domains/shipping/translations/sources.jadpo_recordImported pl_pl: 7 addednext: jadpo check confirms the imported wording
Wording that changed while it was being translated
XLIFF carries what each translation was made from. So a unit whose
<source> isn't today's default wording, because the English changed while
the service worked, is written all the same and recorded as made from that
other wording. It is stale, counted as stale in the summary, and jadpo check fails with translation.stale until it is translated again, as for
wording changed by hand. This is the one case where import doesn't refuse,
because the work isn't lost and translation.stale already says what to
do. JSON doesn't carry its source, so its translations are taken as made
from today's wording.
Your app's own data, such as product names or articles, isn't a translation: model translated content as ordinary fields. Library packages' translations are in Jadpo's and packages' text.
Markdown for agents: /docs/exchanging-translations.md.Built from Jadpo main at a707efb.