# i18n

Source: https://apigenie.pl/docs/reference/cli/i18n

Extracts texts for localization from OpenAPI, Swagger 2.0 and AsyncAPI specifications and
Protobuf and GraphQL files, checks and updates their translations, and applies them. All
subcommands work on all five: `i18n extract` writes the texts as a key-value file, `i18n check`
tells whether a translation file is complete, `i18n sync` updates one after the document changed,
and `i18n apply` puts the translated values back.

## Usage

```sh
apigenie i18n extract [OPTIONS] FILE
apigenie i18n check --translations TRANSLATIONS [OPTIONS] FILE
apigenie i18n sync --previous PREVIOUS --translations TRANSLATIONS [OPTIONS] FILE
apigenie i18n apply --translations TRANSLATIONS [--translations TRANSLATIONS ...] [OPTIONS] FILE
```

In version 0.9.0 `i18n` became a command with subcommands: `apigenie i18n FILE --output DEST` is now
`apigenie i18n extract FILE --output DEST`.

A typical workflow:

1. `apigenie i18n extract openapi.yaml --output i18n-en.json` writes the texts of the spec.
2. Translators turn `i18n-en.json` into `i18n-pl.json`, changing the values and keeping the keys.
3. `apigenie i18n check openapi.yaml --translations i18n-pl.json` fails, in CI for instance,
   until every text is translated and nothing else is.
4. `apigenie i18n apply openapi.yaml --translations i18n-pl.json --output openapi.pl.yaml`
   writes the Polish spec.
5. When the spec changes, `apigenie i18n sync openapi.yaml --previous i18n-en.json --translations
   i18n-pl.json --output i18n-pl.json` keeps the translations of the unchanged texts and lists the
   new texts, the changed ones (their translations may be stale) and the removed ones; then
   `i18n extract` writes the new `i18n-en.json`.

The translation files can also be given to
[`apigenie-generator docs`](/docs/reference/generator/docs) with `--i18n pl=i18n-pl.json`,
which writes a translated copy of each document into the documentation site, with a locale
switcher, without writing a translated spec.

| Input | Recognized by | Keys | Texts |
| --- | --- | --- | --- |
| OpenAPI 3.0, 3.1, 3.2 | the `openapi` property (JSON or YAML) | JSON pointers | titles, summaries, descriptions, names |
| Swagger 2.0 | the `swagger` property | JSON pointers | titles, summaries, descriptions, names |
| AsyncAPI 3.x | the `asyncapi` property | JSON pointers | titles, summaries, descriptions, names |
| Protobuf | the `.proto` extension | fully qualified names | leading comments |
| GraphQL | the `.graphql`, `.graphqls` or `.gql` extension | schema coordinates and names | descriptions |

## i18n extract

This command extracts texts suitable for translations, from an OpenAPI,
Swagger 2.0 or AsyncAPI 3.x document, or from a Protobuf or GraphQL file. Files with the `.proto`
extension are read as Protobuf files (see [Protobuf files](#protobuf-files)), and files
with the `.graphql`, `.graphqls` or `.gql` extension as GraphQL files (see
[GraphQL files](#graphql-files)); the kind of any other document is detected by its
`openapi`, `swagger` or `asyncapi` property. Extracted texts are written in key-value files. For
OpenAPI, Swagger and AsyncAPI, keys are JSON pointers to locations in the input file, and values
are the strings the keys point to. For Protobuf and GraphQL files, keys are the full
names of the documented declarations (see below).

Example:

```sh
apigenie i18n extract --output i18n-en.json openapi-in.yaml
```

may produce the following output:

```yaml
/info/title: My API
/info/contact/name: My Team
```

The command extracts the following properties of OpenAPI documents:

| Object | Properties |
| ------ | ---------- |
| `Info` | `title`, `summary` (3.1+), `description` |
| `Contact` | `name` |
| `License` | `name` |
| `Server` | `name` (3.2), `description` |
| `ServerVariable` | `description` |
| `PathItem` | `summary`, `description` |
| `Schema` | `title`, `description` |
| `Response` | `description` |
| `Parameter` | `description` |
| `Example` | `summary`, `description` |
| `RequestBody` | `description` |
| `Header` | `description` |
| `SecurityScheme` | `description` |
| `OAuthFlow` | the description of each scope, keyed `.../scopes/<scope>` |
| `Link` | `description` |
| `Operation` | `summary`, `description` |
| `ExternalDocumentation` | `description` |
| `Tag` | `summary` (3.2), `description` |
| Reference Object | `summary`, `description` (3.1+): they override those of the referenced object at the place of the `$ref` only, so they are keyed there |

Schemas are extracted wherever they are, including under `$defs`, `dependentSchemas` and
`additionalItems` (`definitions` is not an OpenAPI 3.1 keyword, and is skipped). Identifiers and
values that are part of the API's meaning are never extracted: names of tags, parameters,
properties and scopes, `operationId`, URLs, enum values, defaults, examples, `xml.name` and
`$comment`.

the following properties of Swagger 2.0 documents, as for OpenAPI:

| Object | Properties |
| ------ | ---------- |
| `Info` | `title`, `description` |
| `Contact` | `name` |
| `License` | `name` |
| `Operation` | `summary`, `description` |
| `Parameter` | `description` |
| `Response` | `description` |
| `Header` | `description` |
| `Schema` | `title`, `description` |
| `SecurityScheme` | `description` |
| `ExternalDocumentation` | `description` |
| `Tag` | `description` |

and the following properties of AsyncAPI documents:

| Object | Properties |
| ------ | ---------- |
| `Info` | `title`, `description` |
| `Contact` | `name` |
| `License` | `name` |
| `Server` | `title`, `summary`, `description` |
| `ServerVariable` | `description` |
| `Channel` | `title`, `summary`, `description` |
| `Parameter` | `description` |
| `Operation` | `title`, `summary`, `description` |
| `OperationTrait` | `title`, `summary`, `description` |
| `OperationReplyAddress` | `description` |
| `Message` | `title`, `summary`, `description` |
| `MessageTrait` | `title`, `summary`, `description` |
| `MessageExample` | `summary` |
| `CorrelationId` | `description` |
| `Schema` | `title`, `description` |
| `SecurityScheme` | `description` |
| `ExternalDocumentation` | `description` |
| `Tag` | `description` |

Texts are extracted where they are defined. An object used through a
reference is extracted once, under `components` for instance, and objects
kept in other files are not extracted; bundle the document first
(`apigenie bundle`) to extract them too. Schemas in formats other than
AsyncAPI and JSON Schema, such as Avro, and specification extensions are
skipped.

### Protobuf files

The texts of a Protobuf file are the leading comments of its declarations:
the comment block right above a declaration, as `protoc` attaches it. Keys are
fully qualified names instead of JSON pointers:

| Declaration | Key |
| ----------- | --- |
| The file (the comment of its `syntax` statement) | the package, e.g. `acme.store.v1` |
| Message, enum, service | `acme.store.v1.Order` |
| Field, map field, oneof | `acme.store.v1.Order.id` |
| Field of a oneof | the key of a field of the message, `acme.store.v1.Order.card` |
| Enum value | under its enum, `acme.store.v1.Order.Status.STATUS_OPEN` |
| Method | `acme.store.v1.OrderService.CreateOrder` |
| Extension field | in the scope of its `extend` block, `acme.store.v1.max_length` |

For example, the file

```proto
syntax = "proto3";
package acme.greeter.v1;

// Greets people.
service Greeter {
  // Returns a greeting for one person.
  rpc SayHello(HelloRequest) returns (HelloReply);
}
```

gives

```json
{
  "acme.greeter.v1.Greeter": "Greets people.",
  "acme.greeter.v1.Greeter.SayHello": "Returns a greeting for one person."
}
```

Comment markers are removed, as are one space at the start of each line, the
whitespace at the end of each line, and empty lines at the start and the end
of a comment. Line breaks inside a comment are kept. In a file without a
package, the keys start with the top-level names and the file comment has the
empty key.

Trailing comments, detached comments (separated from the declaration by an
empty line), and comments on the package, imports, options, `extend` blocks and
`reserved` statements are not extracted. Imported files are not read.

### GraphQL files

The command extracts the descriptions of GraphQL definitions, keyed by their full names:

| Definition | Key | Example |
| ---------- | --- | ------- |
| Schema | `schema` | `schema` |
| Scalar, object, interface, union, enum and input object type | type name | `Book` |
| Field of an object or interface type | `Type.field` | `Book.title` |
| Argument of a field | `Type.field(argument:)` | `Query.search(text:)` |
| Field of an input object type | `Input.field` | `BorrowInput.days` |
| Enum value | `Enum.VALUE` | `Genre.POETRY` |
| Directive | `@directive` | `@key` |
| Argument of a directive | `@directive(argument:)` | `@key(fields:)` |
| Operation | operation type and name | `query FindBooks` |
| Variable of an operation | `operation($variable:)` | `query FindBooks($text:)` |
| Fragment | `fragment Name` | `fragment BookSummary` |

Type system keys are the schema coordinates of the GraphQL specification. Fields, arguments and
enum values of type extensions (`extend type`) are named after the extended type. An anonymous
operation is keyed by its operation type only, e.g. `query`. Empty descriptions are skipped, and
the document must be valid: duplicate names are reported as errors. For example,

```sh
apigenie i18n extract --output i18n-en.yaml schema.graphql
```

may produce:

```yaml
Query: The entry points of the library API
Query.search(text:): The words to look for
schema: The library service
```

### Options

#### -o, --output DEST

Name of the file with extracted texts. Format of the file is determined by its
extension: `.json`, or `.yaml` (`.yml`). Without it nothing is written: the file is only read and
its problems reported.

## i18n apply

This command replaces the texts of a document by their translations and writes the translated
document. A translation file has the format `i18n extract` writes, in JSON or YAML: an object
whose keys are those `i18n extract` gives for the document and whose values are the translated
texts. Applying the texts `i18n extract` gives, untranslated, changes nothing.

```sh
apigenie i18n apply --translations i18n-pl.json openapi.yaml --output openapi.pl.yaml
```

- A key that addresses nothing in the document (a key of an older version of the document, a
  mistyped one) is reported as a warning, and the other keys are applied. With `--strict`, it is
  an error, and nothing is written.
- The translation files can be repeated: they are applied in order, so the translations of later
  files override those of earlier ones.
- Comments are kept, unless `--strip-comments` is given.
- A file that can't be read or parsed, or a translation that can't be applied (see below), is an
  error, and nothing is written.

### OpenAPI, Swagger and AsyncAPI specifications

The keys are JSON pointers into the document (JSON or YAML). With `i18n-pl.json`

```json
{
  "/info/title": "Moje API",
  "/paths/~1pets/get/summary": "Lista zwierząt"
}
```

the title of the API and the summary of `GET /pets` are translated. Everything else is
kept as it is: other values, the order of properties, and the YAML comments of the
document, those of the translated texts included.

- Only the texts `i18n extract` extracts are translated. A key pointing anywhere else, to an
  `operationId`, a URL, an object or a value that is not a string, addresses nothing: it is
  reported as a warning (an error with `--strict`), and the value is kept. The document must be
  a valid specification, so that its texts can be extracted.
- Keys that are not JSON pointers, such as those of Protobuf and GraphQL files, and translations
  that are not strings, are errors.
- References are not followed: a text used through a `$ref` is translated where it is
  defined, as `i18n extract` extracts it there.

### Protobuf files

The keys are the fully qualified names `i18n extract` gives, and each translation replaces the
leading comment of its declaration (the comment of the `syntax` statement for the package key):

```sh
apigenie i18n apply --translations i18n-de.json greeter.proto --output greeter.de.proto
```

The comment is rewritten in place, in its style: `//` lines stay `//` lines with their
indentation, and a `/* */` comment stays one, on one line if it was and the translation fits on
one, as a `/*`, ` * line`, ` */` block otherwise. A translation containing `*/` is written as `//`
lines, and an empty translation removes the comment. Nothing else changes: layout, trailing and
detached comments, and the comments without a translation are kept byte for byte. A key naming a
declaration without a leading comment addresses nothing.

### GraphQL files

The keys are the schema coordinates and names `i18n extract` gives, and each translation
replaces the description of its definition:

```json
{
  "Query": "Die Einstiegspunkte der Bibliotheks-API",
  "Query.search(text:)": "Die zu suchenden Wörter"
}
```

The description is rewritten in place, in its form: a block string (`"""..."""`) stays one, on
one line if it was and the translation fits on one, on lines of their own at the indentation of
the original otherwise; a quoted string (`"..."`) stays quoted, with escapes. A translation a
block string can't hold (control characters, a common indentation of all lines) is quoted.
Everything else is kept byte for byte, `#` comments included. A key naming a definition without a
description addresses nothing.

### Options

#### --translations TRANSLATIONS

Path to a translation file, in JSON or YAML. Required. It can be repeated: the files are
applied in order, so the translations of later files override those of earlier ones.

#### -o, --output OUTPUT

Path to the translated document. Defaults to the standard output.

#### --format FORMAT

Format of a translated specification, `json` or `yaml`. Defaults to the format of the input. Not
available for Protobuf and GraphQL files, which are written in their own format.

#### --strict

Fails, without writing anything, when a key of a translation file addresses nothing in the
document.

#### --strip-comments

Drops the comments of the document instead of keeping them: the YAML comments of a
specification, the `#` comments of a GraphQL file (descriptions are kept, they are part of the
schema). Not available for Protobuf files, whose translated texts are comments.

## i18n check

This command compares a translation file with the texts of a document, and fails unless every
text has a translation and every key of the file is a text. It is meant for CI: run it before
publishing a translation.

```sh
apigenie i18n check openapi.yaml --translations i18n-pl.json
```

`FILE` is a spec, a Protobuf or GraphQL file, whose texts are extracted as `i18n extract` does,
or a file `i18n extract` wrote. The command reports, as errors:

- the texts without a translation, in the order of the texts;
- the texts whose translation is empty, unless `--allow-empty` is given;
- the keys that are not texts of the document: a key of an older version of the document, a
  mistyped one, or one pointing to a value that is not meant to be translated, such as an
  `operationId`;
- the keys that are not JSON pointers (for specs), and the translations that are not strings.

It then writes, on the standard output, how many texts are translated:

```text
i18n-pl.json: 74 of 76 texts translated (97.4%).
```

A text whose source changed after it was translated still counts as translated: `i18n sync`
finds those.

### Options

#### --translations TRANSLATIONS

Path to the translation file, in JSON or YAML. Required.

#### --allow-empty

Counts empty translations (`""`) as translated, for texts meant to be empty in the language.

#### --format FORMAT

`text` (the default) or `json`. With `json`, nothing is written to the standard error, and the
standard output is an object:

```json
{
  "complete": false,
  "translated": 74,
  "total": 76,
  "coverage": 0.9736842105263158,
  "missing": ["/info/description", "/paths/~1pets/get/summary"],
  "empty": [],
  "unknown": [],
  "invalid": []
}
```

## i18n sync

This command updates a translation file after the document changed. Keys are locations, not
texts: when a description changes, its key stays the same, and the old translation would still
look complete. `i18n sync` compares the texts the translations were made from (`--previous`)
with the current ones (`FILE`), and:

- keeps the translations of the unchanged texts;
- reports the texts whose source changed, as warnings: review their translations, which may be
  stale. With `--drop-changed`, their translations are dropped, so that `i18n check` reports them
  as missing;
- reports the translations of removed texts, and keeps them unless `--prune` is given;
- counts the new texts, which have no translation yet.

```sh
apigenie i18n sync openapi.yaml --previous i18n-en.json --translations i18n-pl.json -o i18n-pl.json
```

`FILE` and `PREVIOUS` are specs, Protobuf or GraphQL files, or files `i18n extract` wrote: keeping
the `i18n extract` output of the version the translators worked on is enough. Texts are matched by
key, so a text that moved, such as a renamed path, is removed at its old key and added at its new
one. The output follows the order of the current texts, then the kept translations of removed
texts. Keys that are not JSON pointers, and values that are not strings, are copied at the end.

### Options

#### --previous PREVIOUS

The texts the translations were made from. Required.

#### --translations TRANSLATIONS

Path to the translation file to update, in JSON or YAML. Required.

#### -o, --output OUTPUT

Path to the updated translation file; its format follows its extension. With it, the command
writes a summary (`3 new texts, 2 changed, 1 removed, 70 unchanged.`) on the standard output.
Defaults to the standard output, in the format of the translation file.

#### --prune

Drops the translations of the texts the document no longer has.

#### --drop-changed

Drops the translations of the texts that changed, instead of keeping them for review.

## Exit status

The subcommands exit with 0 on success, warnings about unknown keys and changed texts included.
They exit with 1 when `i18n check` finds the translation file incomplete, or when, with `--strict`,
a key of `i18n apply` addresses nothing. They exit with 2 when the input or a translation file
can't be read or parsed, a translation can't be applied, an option isn't available for the input
(`--format` for Protobuf and GraphQL files, `--strip-comments` for Protobuf files), or `i18n` is
run without a subcommand. See the [exit statuses](/docs/reference/cli/overview#exit-status) shared by the three binaries.

## See also

- [bundle](/docs/reference/cli/bundle), to put the texts of referenced files into the
  document before extracting them
- [apigenie-generator docs](/docs/reference/generator/docs), which takes the translation files with
  `--i18n`
