# Translations as messages

Text your server writes for people, such as emails, generated pages and a
failure's text for client apps, is translated in Jadpo itself. Each message
is a type, each language is a file of wordings, and both live in the domain
that uses them.

## Where translations live

A domain's translations live in `domains/<domain>/translations/`, as its
emails live in `emails/`. A message a second domain uses moves to
`shared/translations/`, as anything two domains use does
([Sharing and promotion](sharing-and-promotion.md)).
You move it by hand for now: `layout.promotion_required` doesn't cover
messages yet, so another domain's call of a message is
`visibility.private_cross_domain` until you do.

The root `translations/` folder holds only your app's wording for text it
didn't write: Jadpo's own ([Jadpo's and packages' text](failures-for-client-apps.md#jadpos-and-packages-text))
and its library packages'. A `.json` file there is ignored, like any other
file that isn't source.

```text
domains/shipping/translations/
    order_shipped.jadpo       type OrderShipped = Message { ... }
    parcels.jadpo             type Parcels = Message { ... }
    en_gb.jadpo               translations { ... }: the English wording
    de_de.jadpo               translations { ... }: the German wording
    sources.jadpo_record      generated: what each translation was made from
shared/translations/          messages several domains use
translations/                 the app's wording for Jadpo's and packages' text only
    fr_fr.jadpo               translations { jadpo: { ... } }: grouped by whose text it is
    sources.jadpo_record      generated: what each of those was made from
```

## Messages

A message is one piece of text to translate. It is a type built on
`Message`, a type base like `Failure`, `Event` and `EmailMessage`, one per
file in the domain's `translations/` folder, the file named after the type
([Files and names](files-and-names.md)). Its
fields are the wording's placeholders. Its `///` description reaches
translators as context when you export the messages
([Tooling for translations](tooling-for-translations.md)). `Message` is a
type base, so no type can be called `Message`, and message names are unique
across the project, as a failure's or an email's are.

```jadpo
use shipping/types { Formality }

/// Subject of the email sent when a parcel leaves the warehouse.
type OrderShipped = Message {
    formality: Formality
    name: Text
}
```

```jadpo
/// How many parcels an order has, on the tracking page.
type Parcels = Message {
    count: PluralCount
}
```

```jadpo
/// When a delivery comes, on the tracking page.
type DeliveryWindow = Message {
    day: PresentationText
    hours: PresentationText
}
```

`PluralCount` is a built-in whole number that picks a plural form, such as
"1 parcel" or "2 parcels". It takes a value built on `Int`, is used directly
as a `Message` field, and is written as `{count}` in each form's wording. It
has no use outside a message.

## Languages

Each language is one `translations { }` declaration, with no name, in a
file named after its locale (`de_de.jadpo`). Jadpo finds it by the file's
name, as it finds `config { }`, and code never refers to it by name.
`translations` is a reserved word, like `config` and `locales`.

A file words the messages, the emails and the enums of its own folder's
scope: a domain's, or `shared/`'s. An enum is worded in the scope that
declares it. Each member of the file is a message's, an enum's or an
email's name in lower_snake_case (`order_shipped` is `OrderShipped`), as the
message's file name and its call are
([Naming](naming.md)). When two kinds share a
name in one scope, the first keeps it, and the naming rules report the
clash.

Every wording is a plain string whose only syntax is `{field}`. Write a
literal brace twice: `{{` writes `{` and `}}` writes `}`. There is no
grouping inside a file: the domain is the group, and nesting is only for
enum values and plural forms (the root file's groups by owner, below, are
the exception). There is no message inside another. A value used again,
such as a product name, is a placeholder, because languages whose words
change form can't reuse a fragment.

```jadpo
translations {
    order_shipped: {
        formal: "Guten Tag {name}, Ihre Bestellung wurde versandt."
        casual: "Hallo {name}, deine Bestellung ist unterwegs!"
    }
    parcels: {
        one: "{count} Paket"
        other: "{count} Pakete"
    }
    delivery_window: "Lieferung am {day}, zwischen {hours}."
}
```

How a message's type maps to its wording in each language's file:

| In the type | In each language's file |
|---|---|
| The type's name, `OrderShipped` | The member `order_shipped`, in the language files of the type's own `translations/` folder |
| No fields | A plain string |
| A `Text`, `PresentationText`, `Int` or `Decimal` field, or a named type built on `Text`, `Int` or `Decimal` | A `{field}` placeholder; numbers are written the locale's way |
| A field built on `Money` | A `{field, number, currency}` placeholder, written as `text.format` shows the amount in the locale; `{field}` alone is `translation.placeholder_style` |
| A field built on `Percentage` | A `{field, number, percent}` placeholder, written as `text.format` shows the percentage in the locale; `{field}` alone is `translation.placeholder_style` |
| One enum field | One member per value of the enum; the field is not a placeholder |
| One `PluralCount` field | One member per plural form the language needs, from Unicode CLDR (English `one` and `other`; Polish `one`, `few`, `many` and `other`); `{count}` is available in each |
| Any other field type (`Instant`, an object, `T?`) | `translation.field_type`: format it in code and pass the text |
| More than one enum or `PluralCount` field | `translation.too_many_selectors`: split the message, so a translator never fills a grid of combinations |

The plural forms are the locale's counting forms in CLDR, and no others.
There is no exact `=0` form: a language that says "no items" differently
words it as its own `zero` form, where CLDR has one.

## Dates, money and percentages in a message

Format dates and times in code, with `text.format`
([Showing dates and times to people](showing-dates-and-times-to-people.md)),
and pass them in as `PresentationText`. An amount of money is a placeholder
of its own, written by the same formatter as `text.format`, so the
translator sees a price and each language writes it its own way
([Money operations](money-operations.md)):

```jadpo
use orders/types { OrderAmount }

/// What an order comes to, on its confirmation page.
type OrderTotal = Message {
    total: OrderAmount
}
```

```jadpo
translations {
    order_total: "Your order comes to {total, number, currency}."
}
```

```jadpo
translations {
    order_total: "Tu pedido suma {total, number, currency}."
}
```

```jadpo
use orders/types { OrderAmount }

test "a price is written each language's way" {
    var total: OrderAmount = OrderAmount(money.from_minor(123450, Currency.mxn))
    assert confirm_total(total, Locale.en_gb) == "Your order comes to MX$1,234.50."
    assert confirm_total(total, Locale.es_mx) == "Tu pedido suma $1,234.50."
}
```

A percentage is a placeholder of its own in the same way, written
`{rate, number, percent}`
([Prices, discounts and rates](prices-discounts-and-rates.md)):

```jadpo
use orders/types { DiscountRate }

/// The discount on an order, on its confirmation page.
type OrderDiscount = Message {
    reduction: DiscountRate
}
```

```jadpo
translations {
    order_discount: "You saved {reduction, number, percent}."
}
```

```jadpo
translations {
    order_discount: "Ahorraste {reduction, number, percent}."
}
```

```jadpo
test "a discount is written each language's way" {
    assert confirm_discount(12.5%, Locale.en_gb) == "You saved 12.5%."
    assert confirm_discount(12.5%, Locale.es_mx) == "Ahorraste 12.5%."
}
```

## Using a message

The compiler generates one call per message,
`translations.<domain>.<message>(...)`. It takes every field by name, plus
`locale:`, one of your app's supported locales. A value given without its
name is the next field's, in the order the type declares them. The locale
is always named.

Each field takes a value of its kind:

- a field that picks the wording takes its enum;
- a `PluralCount` takes any value built on `Int`;
- a text field takes a value built on `Text` or `PresentationText`;
- a number field takes a value built on `Int` or `Decimal`.

The call returns `PresentationText`, so the result can be shown but never
stored. A message belongs to its domain: another domain may call it only
when it is `public`. So a message two domains use lives in
`shared/translations/`, and is called `translations.shared.<message>(...)`.

In an email template the same call takes the email's locale, and is written
without `locale:`: `{{ translations.shipping.parcels(count: parcel_count) }}`
([Email](email.md)).

```jadpo
use shipping/types { CustomerName, Formality, ParcelCount, ShippingText }

/// What a customer is told about their order, in their language.
action describe_order(
    formality: Formality,
    name: CustomerName,
    count: ParcelCount,
    reader_locale: Locale
) -> ShippingText {
    return ShippingText {
        subject: translations.shipping.order_shipped(
            formality: formality,
            name: name,
            locale: reader_locale
        )
        parcels: translations.shipping.parcels(count: count, locale: reader_locale)
    }
}
```

The text is the language's own. The plural form comes from CLDR's rules for
the locale, and numbers are written with its digits and separators, from
the same data as `text.format`. So the interpreter, a native deployable and
WebAssembly write the same text.

```jadpo
use shipping/types { Formality }

test "a message is worded by its enum value and its plural count" {
    assert translations.shipping.order_shipped(
        formality: Formality.formal,
        name: "Ana",
        locale: Locale.de_de
    ) == "Guten Tag Ana, Ihre Bestellung wurde versandt."
    assert translations.shipping.order_shipped(
        formality: Formality.casual,
        name: "Ana",
        locale: Locale.en_gb
    ) == "Hi Ana, your order is on its way!"
    assert translations.shipping.parcels(count: 1, locale: Locale.en_gb) == "1 parcel"
    assert translations.shipping.parcels(count: 1234, locale: Locale.en_gb) == "1,234 parcels"
    assert translations.shipping.parcels(count: 1234, locale: Locale.de_de) == "1.234 Pakete"
}

test "a field given without its name is the next field" {
    assert translations.shipping.delivery_window(
        "Monday",
        "9 to 12",
        locale: Locale.de_de
    ) == "Lieferung am Monday, zwischen 9 to 12."
}
```
