# What the compiler checks of translations

`jadpo check` checks every language file against the messages, enums,
emails and failures it words. Each problem has its own error, listed at the
end of the page.

## Mistakes in a language file

- a member that no message, enum, email or failure declares
  (`translation.member_unknown`, with the nearest name);
- a member written twice (`translation.member_duplicate`);
- a member in the wrong shape for what it words, such as a string where a
  block of values is needed, or the reverse (`translation.wording_shape`);
- a wording that isn't a plain string with `{field}` placeholders
  (`translation.wording_invalid`);
- a variant that isn't a value of the enum, or a plural form the language
  doesn't use (`translation.variant_unknown`);
- a missing variant or plural form (`translation.variant_missing`);
- a placeholder that isn't a field (`translation.placeholder_unknown`);
- an amount of money or a percentage written without its style, or a style
  on another field (`translation.placeholder_style`);
- a field type a wording can't hold (`translation.field_type`), or more
  than one enum or `PluralCount` field (`translation.too_many_selectors`);
- a language file that isn't named for a locale
  (`translation.file_name_invalid`), or is for one the application doesn't
  support (`translation.locale_unsupported`);
- a call of no message (`translation.message_unknown`);
- a `locale:` that isn't a supported locale (`type.locale_unsupported`);
- translations in a project with no `locales { }`
  (`translation.locales_required`).

## Missing wording

Every supported locale must be complete, unless it is listed in
`in_progress:`. A message, a failure a route can answer with, or an enum
once any language words it, missing from such a locale is an error:
`translation.file_missing` for a locale with no language file,
`translation.wording_missing` for a file that lacks a member. The error's
`next:` names the file and the member to add, with the placeholders to use,
and ends `(agent)`: your coding agent can add it itself.

## A field no wording uses

A field that no wording in a language uses is `translation.field_unused`.
One variant leaving a field out is normal. Jadpo has no warnings, so this
is an error, but a judgement call: a person can answer it by writing
`decline translation.field_unused: "<reason>"` above the member
([Declining a judgement call](declining-a-judgement-call.md)).
That is right for a language that needs no such word.

## Locales in progress

`in_progress:` is how you say a locale isn't finished. A locale it lists
falls back where its language file has no wording, with no error: to the
same language's regional sibling first (`fr_ca` tries `fr_fr`, if
supported), then to the default locale. The sentence is written entirely in
the language it came from.

Wording the file does give is checked as for any locale, so a mistake in
it is still an error. Removing the locale from `in_progress:` makes the gaps
errors again. Quality comes from the review setting below, not from
waiting. `jadpo translations missing` lists what an in-progress locale falls
back on.

## Stale translations: `sources.jadpo_record`

A translation is stale when the wording it was translated from has changed
since. Jadpo knows from a file it writes in each `translations/` folder,
`sources.jadpo_record`, which you commit. It is a record
([The record format](what-is-committed-and-why.md#the-record-format)) headed
`record translations 1`, written by `jadpo translations update`, listed by
`jadpo records` and merged by git's `union` merge.

It has one line for each translation outside the default locale, sorted,
with a hash of the default wording it was translated from (`from`) and of
its own wording when that was recorded (`wording`). A hash is the first 12
hex digits of BLAKE3 over the member's whole text, every value and plural
form in name order, so reordering a block changes nothing.

A translation whose `from` no longer matches today's default wording is
stale (`translation.stale`). That is an error until it is translated again
and recorded. An in-progress locale's stale translation is listed by `jadpo
translations missing` instead.

A translation with no line yet isn't stale: it was never recorded, so
nothing says what it was made from. (With review required, it is new and
not accepted.) So a project that never runs `jadpo translations update`
sees no stale error.

A line Jadpo can't read is `translation.sources_invalid`, which `jadpo
translations update` fixes. A header Jadpo didn't write, or lines out of
order, is `record.edited`, and a newer format is `record.format_newer`, as
for every record ([Checking a record](what-is-committed-and-why.md#checking-a-record)).

## Review

By default a translation needs no one to accept it. To have a person accept
each one, set `translations: { review: TranslationReview.required }` in
`application { }`; the default is `TranslationReview.none`. Then every new
or changed wording outside the default locale is not accepted until a
person runs `jadpo translations accept <locale>`, which records the
acceptance in the same record. A line says a wording is accepted only while
the wording is the one the line hashes.

`jadpo translations missing` lists wording that isn't accepted. It isn't an
error, so `jadpo check` passes with it: your code review is the gate.
Jadpo can't tell who wrote a wording, so this means "changed and not yet
accepted", not "written by an agent".

A `translations:` option other than `review:` is
`translation.option_unknown`, and a `review:` that isn't a
`TranslationReview` is `translation.review_invalid`.

```jadpo
application {
    translations: {
        review: TranslationReview.required
    }
}

locales {
    default: Locale.en_gb
    supported: [Locale.en_gb, Locale.de_de]
}
```

## Jadpo's and packages' wording

The root folder's wording of Jadpo's and packages' text is checked too,
each problem with its own error:

- **Jadpo's messages.** A supported locale that isn't in progress, in
  which Jadpo ships no wording and the root file words none of the messages
  of a feature the app has on, is one error for the locale, listing what is
  missing (`translation.jadpo_missing`) and pointing at `jadpo translations
  builtin`. A message of a feature that is off is
  `translation.feature_off`, and a group of the root file that names no
  owner is `translation.group_unknown`. Everything else about a group's
  wording is checked as a folder's is: `translation.member_unknown` (with
  the nearest of Jadpo's messages), `member_duplicate`, `wording_shape`,
  `wording_invalid`, `placeholder_unknown`, `variant_missing` and
  `variant_unknown`.
- **The root folder's record.** The root folder has its own
  `sources.jadpo_record`, kept by the same commands, with the owner in the
  member's name (`translation fr_fr.jadpo.invalid_input from ... wording
  ...`), as a package's is `postcodes.<message>`.
- **A library package's text.** Its own language files are checked by
  themselves, as a domain's are. Its group in the root file is checked
  against its public messages, enums and failures
  (`translation.member_unknown`, with the nearest of them). Each supported
  locale not in progress must word all of it in the root file or the
  package's own file (`translation.wording_missing` with a root file for
  the locale, `translation.file_missing` without). A call through the
  package of a message it doesn't have is `translation.message_unknown`,
  and of one it doesn't make `public`, `package.not_public`. A root record
  line is made from the app's default wording or, when the app gives none,
  the package's, and is stale when that changes (`translation.stale`).
