# Packages' text in an app

To show a package's or Jadpo's message in a locale, Jadpo looks first in
your app's root wording for that locale, then in the owner's own. For an
in-progress locale it then tries the regional sibling, in the same order,
and then the default locale, in the same order.

Your app's supported locales must be complete for every package and Jadpo
message the app can show. So an English-only package in an app that
supports French is an error until the root file supplies the French.

Here an app supporting English and French installs a package that words
its text in English only, calls its message, and words the French itself:

```jadpo
/// Shown under a postcode field.
public type PostcodeHint = Message {}
```

```jadpo
translations {
    invalid_postcode: "That isn't a UK postcode."
    postcode_hint: "Write it as it is on a letter, such as SW1A 1AA."
}
```

```jadpo
use packages/postcodes

/// What the postcode field says, in the reader's language.
action postcode_hint_text(reader_locale: Locale) -> PresentationText {
    return Postcodes.translations.postcode_hint(locale: reader_locale)
}
```

```jadpo
translations {
    postcodes: {
        invalid_postcode: "Ce code postal n'existe pas."
        postcode_hint: "Écrivez-le comme sur une lettre."
    }
}
```

```jadpo
test "the package words the English and the app the French" {
    assert postcode_hint_text(Locale.en_gb) == "Write it as it is on a letter, such as SW1A 1AA."
    assert postcode_hint_text(Locale.fr_fr) == "Écrivez-le comme sur une lettre."
}
```

Without `translations/fr_fr.jadpo`, French has nothing to show:

```jadpo
application {}

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

```text
error: `Locale.fr_fr` is supported but package `Postcodes` has no wording in it for `postcode_hint`
 --> app.jadpo:5:31
  |
5 |     supported: [Locale.en_gb, Locale.fr_fr]
  |                               ^^^^^^^^^^^^ no `translations/fr_fr.jadpo`
  reason: Package `Postcodes` ships no `Locale.fr_fr` wording of `postcode_hint`, and the application supports `Locale.fr_fr`, so the application words it in its root file, under the package's folder name. A locale that isn't finished yet is listed in `in_progress:`, and falls back to the default until it is.
  next: Add `translations/fr_fr.jadpo` wording `postcode_hint` under `postcodes: { }` (agent)
  rule: translation.file_missing
  help: https://jadpo.dev/docs/errors/translation.file_missing

check failed: 1 error, 4 files
```

To show a package failure under a different code, not just in different
words, map it to your app's own failure at the call with `attempt`, as for
any package failure ([Calling](calling.md)).

## When a package changes

A package that changes a message's or a failure's fields makes a breaking
change to its surface, the list of what it offers
([The surface](the-surface.md)). So your app's rewording
fails to compile until you update it.

A package that changes its own wording marks your app's rewording stale,
through `sources.jadpo_record`
([Stale translations](what-the-compiler-checks-of-translations.md#stale-translations-sourcesjadpo_record)), and the
same holds for Jadpo's wording. The root folder's
`sources.jadpo_record` records, for each wording your app gives outside the
default locale, a hash of the default wording it was made from. That is
your app's own default-locale wording of the message or, if the app gives
none, what Jadpo or the package ships. A default locale Jadpo doesn't ship
and the app doesn't word has nothing to compare with, so its translations
aren't judged.

A library package is published with complete wording in its own default
locale and a `///` description on every public message.

## Wording two versions of a package

While your app holds two versions of a library package side by side
([Where packages live](where-packages-live.md)), it words
each under a key carrying the version, `postcodes_v2: { }` and
`postcodes_v3: { }`, as its names carry it. With one version held, the key
is the folder's name. Here the app's own `Postcodes` is held at `v2` and
`v3` while callers move across, and the French words each:

```jadpo
translations {
    postcode_hint: "Write it as it is on a letter, spaces and all."
}
```

```jadpo
use shared/providers/postcodes

/// The hint as callers showed it before the upgrade.
action old_hint(reader_locale: Locale) -> PresentationText {
    return Postcodes.v2.translations.postcode_hint(locale: reader_locale)
}
```

```jadpo
use shared/providers/postcodes

/// The hint with the next version.
action new_hint(reader_locale: Locale) -> PresentationText {
    return Postcodes.v3.translations.postcode_hint(locale: reader_locale)
}
```

```jadpo
translations {
    postcodes_v2: {
        postcode_hint: "Écrivez-le comme sur une lettre."
    }
    postcodes_v3: {
        postcode_hint: "Écrivez-le comme sur une lettre, espaces compris."
    }
}
```

```jadpo
test "the earlier version is worded under its key" {
    assert old_hint(Locale.fr_fr) == "Écrivez-le comme sur une lettre."
}
```

```jadpo
test "the next version is worded under its key" {
    assert new_hint(Locale.fr_fr) == "Écrivez-le comme sur une lettre, espaces compris."
    assert new_hint(Locale.en_gb) == "Write it as it is on a letter, spaces and all."
}
```

When the second version is added, the app's wording under the folder's
name no longer says which version it words. It was written while the
earlier version was held alone, so the check's edit moves it to that
version's key; the new version is worded afresh. When one of two is
removed, the other's key goes back to the folder's name, and the removed
version's group is the app's to delete:

```jadpo
translations {
    postcodes: {
        postcode_hint: "Écrivez-le comme sur une lettre."
    }
    postcodes_v3: {
        postcode_hint: "Écrivez-le comme sur une lettre, espaces compris."
    }
}
```

```text
error: `postcodes` words package `Postcodes`, which is held in two versions
 --> translations/fr_fr.jadpo:2:5
  |
2 |     postcodes: {
  |     ^^^^^^^^^ which version?
  reason: While two versions of a package are held side by side, the application words each under its folder name and version, as its names carry the version: `postcodes_v2: { }` for `Postcodes.v2` and `postcodes_v3: { }` for `Postcodes.v3`. This group was written while one version was held, so it words the earlier one unless it says otherwise.
  next: Rename the group to `postcodes_v2`, or to the other version's key if it words that one (agent)
  rule: translation.group_unknown
  help: https://jadpo.dev/docs/errors/translation.group_unknown

check failed: 1 error, 9 files
```

## Packages' text: groups, calls and the surface

- **A package's group is its folder's name.** The root file words
  `packages/postcodes/v2/`'s text under `postcodes: { }`, the name a
  domain can't share (`package.name_taken`); the record's lines and
  `jadpo translations missing` name its members `postcodes.<member>`.
- **What the app can word and call.** A package's public messages, its
  enums with wording and its failures, each by the name its own files use;
  a member naming anything else, or something the package keeps private,
  is `translation.member_unknown`, and a call of a message that isn't
  `public` is `package.not_public`. A package's own code doesn't call its
  messages: the app does, and `translations.postcodes.<message>(...)` is
  `translation.message_unknown` with the call through the package as its
  edit.
- **The package's own files** are checked by themselves, each against the
  locale its name gives, whether or not the app supports it: a package is
  published apart from any app. Their wording, shapes, placeholders and
  plural forms are checked as a domain's are.
- **What a supported locale needs.** Every public message, an enum once
  the package or the app words it, and a failure some route answers with,
  in the root file or the package's own file for that locale. With a root
  file for the locale, each one missing is `translation.wording_missing`;
  without one, the locale is one `translation.file_missing` naming them.
  A locale in progress falls back, as for the app's own messages.
- **Every language a package ships is complete.** A package has no default
  locale, so the publishing bar, the checks a package must pass to be
  published, holds every language it ships to completeness. Each language
  file words every public message, and an enum once any file words it (a
  `wording` row, shown when the package carries translations). A package
  with messages and no language file fails it.
- **The surface.** The package's entry has a `locale` line for each
  language it words; each public message is an entry, `message
  translations.<message>` with its fields, after the functions. A new
  locale or message is the next minor version, a message's changed fields
  or a dropped locale are breaking, and changed wording is a patch.
- **The commands.** `missing`, `update` and `accept` take a package's
  text in, and so do `export`, `import` and `unused`, below.

## Packages' text in the exchange

- **What is exchanged.** What a supported locale must word of a package
  (above) and anything else of it the app words in its root file, each
  unit `postcodes.<member>` with `.<value>` or `.<form>`, in an XLIFF
  `<file id="postcodes" original="translations">` after the app's own and
  before Jadpo's. It is translated from the app's default-locale wording
  of it, else the package's, the wording the record's `from` hashes. A
  package in a folder named `jadpo` or like a scope of the app's would
  share unit ids, so export refuses it, as it does a domain named `jadpo`.
- **What a locale has.** The app's root wording, else the package's own
  for that locale, which is what the app shows; the package's own is
  `translated`, never `reviewed` or stale, since no record of the app's
  judges it. Exporting and importing unchanged writes nothing.
- **What comes back** is the app's: written into the root file for the
  locale, in the package's group (created, beside `jadpo: { }`, when the
  file lacks one), the whole member when one plural form or value
  changed, its other forms from the package's wording, and a line in the
  root record. The package's files are never written: a translator who
  changes the package's wording makes the app reword it. Import's rows
  name a package's units `postcodes.<member>`, as `missing` does.
- **`unused`** lists, after the app's own and under `Package wording`,
  each member of the root files' package groups that shows nothing, with
  its place and why: a message or enum of the package no code of the
  app's calls (`no code calls it`), a name the package no longer has
  (`not in Postcodes`) or keeps private (`private to Postcodes`), and a
  group no installed library package words text under (`no such
  package`). These are the members to delete. A package's message the
  app neither calls nor words is the package's, and is not listed; a
  failure is shown by being reachable, as the app's are; Jadpo's group
  is not a package's. `--json` gives them as `packageWording`, each with
  a `reason` (`uncalled`, `notInPackage`, `private`, `noPackage`).

## Two versions of a package

How your app's wording follows a library package held in two versions side
by side
([Two versions side by side, lazy loading and the package cache](two-versions-side-by-side-lazy-loading-and-the-package-cache.md)):

- **The key.** Exactly while names through the package carry the version,
  the app words each version's text
  under `<folder>_<version>`, `postcodes_v3: { }`, even when only one of
  the two carries translations; otherwise under the folder's name. The
  key is the same everywhere the folder's name was: the root files'
  groups, the root record's members (`de_de.postcodes_v3.postcode_hint`),
  `jadpo translations missing`, `export`'s `<file id="postcodes_v3">` and
  unit ids, what `import` writes into, and `unused`, which names the
  package with its version (`not in Postcodes.v3`). The errors name the
  package as code does, `Postcodes.v3`. A domain named like a version's
  key is `package.name_taken` while two versions are held, as one named
  like the folder always is.
- **Adding a second version moves nothing by itself.** The app's wording
  under the folder's name was written while one version was held, so
  `jadpo check` reports the group (`translation.group_unknown`, "held in
  two versions") with the edit that renames it to the earlier version's
  key, unless the file already has that group, when its members are moved
  by hand. The new version starts with none of the app's wording: it may
  have changed its messages, so nothing is copied, and the check's
  missing wording lists what each supported locale needs of it.
- **Removing one of two** puts the other's key back to the folder's name,
  reported with the edit that renames it ("held in one version"); the
  removed version's group words nothing (`translation.group_unknown`,
  "which the app doesn't hold"), and is the author's to delete. Neither
  `jadpo package add` nor `remove` rewrites the app's root files, as they
  don't rewrite its names, and `jadpo fix` is still only for type
  clashes ([Commands for records, findings and review](commands-for-records-findings-and-review.md)).

- **The record follows the wording.** A translation with no line under
  its new key takes the line it had under the package's other key in the
  same locale, the folder's name or another version's, when nothing is
  worded under that key any more and the wording is the one the line
  hashes; so it keeps its acceptance, and is stale when the version it
  now words has different default wording from what it was translated
  from. Wording changed on the way is a new translation, recorded afresh. `jadpo translations update` writes the
  line under the new key (`moved with its wording to the package's new
  key`) and drops the old one.
- **The publishing bar** checks one package in its own folder, where one
  version is held, so its `wording` row is unchanged.
