# 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`.

```text
Wrote build/translations/de_de.xlf
    de_de  5 units, 5 translated

Wrote build/translations/pl_pl.xlf
    pl_pl  7 units, 7 to translate

Exported 2 locales: 12 units, xliff

next: 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.

```text
<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_record` gets 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](command-errors.md), 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`.

```text
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         added

Wrote domains/shipping/translations/sources.jadpo_record

Imported pl_pl: 7 added

next: 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](failures-for-client-apps.md#jadpos-and-packages-text).
