---
title: "Hand off to an agency (XLIFF)"
description: "Export the strings that need translating as XLIFF 2.0 or 1.2 for a CAT tool, and import the returned file through the same safety checks as any run."
type: "how-to"
---
# Hand off to an agency (XLIFF)

Export the strings that need translating as XLIFF 2.0 or 1.2 for a CAT tool, and import the returned file through the same safety checks as any run.

<AvailableFrom version="0.12.0" />

Use this when professional translators work in a CAT tool such as Trados, memoQ, OmegaT, Matecat or Phrase. XLIFF reaches them as segments with states, notes and protected placeholders, which is what their translation memory and QA checks are built around. verbatra writes XLIFF from any project format, not only from an XLIFF project, and reads it back through the same import as a [workbook handoff](https://verbatra.kreitz-webdev.de/docs/manual-translation.md).

## The round-trip [#the-round-trip]

```bash
# 1. Export one XLIFF 2.0 file per target locale into handoff/
verbatra export --format xliff2 --out handoff

# 2. The agency translates handoff/de.xlf, handoff/fr.xlf ... in its CAT tool and sends them back

# 3. Import every returned file, recording who signed off on the reviewed ones
verbatra import handoff --format xliff2 --reviewer "Agency GmbH"
```

If the tool does not read XLIFF 2.0 yet, use `--format xliff12`. Either way you get one `<locale>.xlf` per target locale. A file left over from an earlier, wider export is refused on import instead of applied.

To import a single returned file, name it. A path ending in `.xlf` or `.xliff` is read as XLIFF without `--format`, and the XLIFF version comes from the file itself:

```bash
verbatra import handoff/de.xlf
```

The locale comes from the file name. If the tool renamed the file (say to `project_de.xlf`), the target language the file declares is matched against your configured locales instead.

## What lands in the file [#what-lands-in-the-file]

Export picks the same keys as a workbook export: the missing and changed ones, plus the up-to-date ones with `--include-unchanged`. Each key becomes one unit:

- **The key names the unit**: the unit's `name` in XLIFF 2.0, the `trans-unit` `id` and `resname` in XLIFF 1.2.
- **Placeholders, inline markup and ICU syntax become inline codes** (`<ph>`), which a CAT tool shows as tags the translator places but cannot edit. The text inside each ICU arm stays translatable. Import turns every code back into its exact original text.
- **Context travels as notes**: a key's description and meaning, where the project format carries them.
- **The source hash travels as metadata**, so import can tell whether the source changed since export.
- **A changed key carries its old translation** as the target, as a starting point, in the initial state.

### Segment states [#segment-states]

The state of each segment comes from the lock file and the key's [review decision](https://verbatra.kreitz-webdev.de/docs/the-lock-file.md#review-decisions):

| The key is | XLIFF 2.0 `state` | XLIFF 1.2 `state` |
| --- | --- | --- |
| missing | `initial`, no target | no target |
| stale (source changed) | `initial`, old translation as target | `needs-translation`, old translation as target |
| up to date, not reviewed | `translated` | `translated` |
| up to date and approved | `reviewed` | `signed-off`, and `approved="yes"` |
| up to date but rejected | `initial` | `needs-translation` |

Where a value came from does not change its state: a machine translation, a memory hit and a value a person typed are all `translated` until someone approves them.

### Machine-translation markers [#machine-translation-markers]

The origin travels next to the state, so the agency can see which targets a machine wrote. In XLIFF 1.2, an unapproved machine-written target carries `state-qualifier="mt-suggestion"`; in XLIFF 2.0 the unit's metadata carries its origin and review state. Import ignores both. See [Record machine-translation evidence](https://verbatra.kreitz-webdev.de/docs/provenance-evidence.md#xliff).

## What import accepts [#what-import-accepts]

Every returned unit runs through the checks of a [workbook import](https://verbatra.kreitz-webdev.de/docs/manual-translation.md#what-import-accepts): the integrity gate, the stale-source guard, `[[CLEAR]]` to empty a value on purpose, and an empty target meaning "not translated yet". XLIFF adds these rules:

- **A target left as exported changes nothing.** The key stays byte-identical and keeps its provenance. If you want a stale key's old translation kept for the new source, the translator moves its state past `initial`.
- **Any other target is a translation, whatever its state**, because some tools write the text but leave the state alone.
- **`reviewed` and `final` record an approval.** So do `signed-off`, `final` and `approved="yes"` in XLIFF 1.2. The approval is saved in `verbatra.provenance.json` with the `--reviewer` name and reported as [`HANDOFF_REVIEWS_RECORDED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#handoff_reviews_recorded). An imported value gets the origin `import`; a value the translator only confirmed keeps its origin, such as `machine`.
- **A stripped source hash falls back to the source text.** If the unit's source still matches, it imports; if not, it is withheld as drift.
- **Damage is reported, not fatal.** A unit with an unknown key or broken inline codes is reported as a malformed row, and a repeated key as a duplicate; the rest of the file still imports.
- **The XML is parsed safely.** A file that declares an entity or an internal DTD subset is refused, and so is a file larger than 32 MB.

Run `verbatra import --dry-run` first to see all of this, including the approvals, without writing a file.

XLIFF 1.2 cannot carry a character XML 1.0 forbids (a control character such as U+0007), so such a character is dropped from a 1.2 export. XLIFF 2.0 carries it exactly, so prefer `xliff2` when the tool reads it.

## Checking a tool by hand: OmegaT [#checking-a-tool-by-hand-omegat]

The automated tests edit exported files the way a CAT tool does, but they do not run one. To try a real round-trip, use [OmegaT](https://omegat.org), which is free:

1. Export a handoff: `verbatra export --format xliff12 --out handoff` (or `xliff2`).
2. In OmegaT, create a project with your source and target language, and copy `handoff/de.xlf` into the project's `source` folder.
3. Open the project. Placeholders such as `{{name}}` show as tags: translate the text around them, and insert each tag with Ctrl+T (Cmd+T on macOS) instead of typing it.
4. Translate some segments, leave some untranslated, and save.
5. Choose Project, Create Translated Documents. OmegaT writes `de.xlf` into the project's `target` folder.
6. Check it: `verbatra import path/to/target/de.xlf --dry-run`. Translated segments are listed under `translated`, untouched ones under `unfilled`, and nothing is refused unless a tag was lost.
7. Import it: `verbatra import path/to/target/de.xlf`, then `verbatra check`.

## From the SDK [#from-the-sdk]

The CLI wraps the same [`exportWorkbook`](https://verbatra.kreitz-webdev.de/docs/sdk/exchange.md#exportworkbook) and [`importWorkbook`](https://verbatra.kreitz-webdev.de/docs/sdk/exchange.md#importworkbook) as the workbook handoff:

```ts
import { exportWorkbook, importWorkbook, loadConfig } from "@verbatra/sdk";

const config = await loadConfig();

await exportWorkbook({ config, format: "xliff2", out: "handoff" });

const summary = await importWorkbook({
  config,
  workbook: "handoff",
  format: "xliff2",
  reviewer: "Agency GmbH",
});
```

## Related [#related]

- [`verbatra export`](https://verbatra.kreitz-webdev.de/docs/cli/export.md): every export flag, including the XLIFF formats.
- [`verbatra import`](https://verbatra.kreitz-webdev.de/docs/cli/import.md): every import flag, including `--reviewer`.
- [Hand off with Excel or CSV](https://verbatra.kreitz-webdev.de/docs/manual-translation.md): the workbook handoff and every check import applies.
