# Languages

A MyGo app says its messages in the language of the people who use it.
Its messages live in catalogs, a JSON file per language, from which
`mygo dev` and `mygo build` write a Go package with a typed function per
message,
which says it in the language the app shows, with the plural forms of
that language. The whole app shows one language: its own messages,
MyGo's controls and menus, and its pages if it has any.

```json
{
  "filesSelected": "{count, plural, =0 {No files selected} one {# file selected} other {# files selected}}"
}
```

```go
ui.Text(c, locales.FilesSelected(len(app.selected))) // "3 files selected"
```

Package [`i18n`](#package-i18n) underneath formats numbers and dates
with the data of the Unicode CLDR, and an app with a frontend gets the
same messages as a TypeScript module ([In pages](#in-pages)).

## Catalogs

Turn the catalogs on in the [configuration](https://mygo.egoist.dev/docs/configuration.md#i18n), in
`mygo.json` (or `mygo.config.ts`):

```json
{
  "i18n": { "dir": "locales", "source": "en" }
}
```

`dir` (default `locales`) holds a catalog per language, named after its
[BCP 47](https://www.rfc-editor.org/info/bcp47) tag: `en.json`,
`de.json`, `pt-BR.json`, `zh-Hans.json`, `zh-Hant.json`. `source`
(default `en`) is the language the app is written in: its catalog has
every message, and the others fall back to it. The languages the app
ships are the files of `dir`, which holds nothing else but Go files; an
app without catalogs ships English.

A catalog is an object of messages, in the ARB format, whose keys name
them. The source language's also describes them, under the key
with an `@`, for the translators and the generated code:

```json
{
  "@@locale": "en",
  "filesSelected": "{count, plural, =0 {No files selected} one {# file selected} other {# files selected}}",
  "@filesSelected": {
    "description": "The status bar, while files are selected.",
    "placeholders": {
      "count": { "type": "int", "example": "3" }
    }
  },
  "greeting": "Hello, {name}!"
}
```

```json
{
  "@@locale": "de",
  "filesSelected": "{count, plural, =0 {Keine Dateien ausgewählt} one {# Datei ausgewählt} other {# Dateien ausgewählt}}",
  "greeting": "Hallo, {name}!"
}
```

A key is a letter, then letters, digits and `_`; the functions of a key
are named after it. A placeholder's `type` is `String`, `int`, `double`,
`num`, `DateTime` or `Object`, which makes the type of the functions'
parameter (`string`, `int`, `float64`, `float64`, `time.Time` and `any` in
Go; `string`, `number`, `Date` and `unknown` in TypeScript). Without one,
an argument counted by `plural`, `selectordinal` or `number` is an `int`, a
`date` or `time` argument a `DateTime`, and any other a `String`. The
parameters come in the order of `placeholders`, then of the message.
`@@locale` is optional, and must name the file's language.

Keys are flat: a dot in a key is for MyGo's own strings
([`mygo.undo`](#mygos-own-strings)). Name related messages with a common
start, as `sidebarTitle`, `sidebarEmpty` and `sidebarNewNote`, which sort
together in the catalogs and in your editor's completion
(`locales.Sidebar…`).

## Messages

Messages are written in ICU MessageFormat, the format of ARB files, of
Android and of Format.JS:

| Message                                                                                              | Says                                                                                            |
| ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `Hello, {name}!`                                                                                     | the value of `name` in its place                                                                |
| `{count, plural, one {# file} other {# files}}`                                                      | the case of the plural category of `count` in the message's language; `#` is the number         |
| `{count, plural, =0 {No files} one {# file} other {# files}}`                                        | `=0` is for exactly 0, before the categories                                                    |
| `{n, plural, offset:1 =0 {Nobody} =1 {{host}} one {{host} and # other} other {{host} and # others}}` | with `offset:1`, the categories and `#` count `n - 1`                                           |
| `{place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}`                                  | the case of the ordinal category: 1st, 22nd, 103rd, 11th                                        |
| `{gender, select, female {She} male {He} other {They}} replied.`                                     | the case named after the value, else `other`                                                    |
| `{price, number}`, `{ratio, number, percent}`, `{n, number, integer}`                                | a number in the language's way (1,234.5; 1.234,5; 12,34,567)                                    |
| `{price, number, ::.00}`, `{n, number, ::group-off}`                                                 | a number with exactly two fraction digits, or without separators of thousands                   |
| `{price, number, ::currency/EUR}`                                                                    | an amount of euros in the language's way (€1,842.00; 1.842,00 €)                                |
| `{day, date, short}`, `{day, time, short}`                                                           | a date, or a time, in a style: `short`, `medium`, `long` or `full`                              |
| `{day, date, ::yMMMd}`                                                                               | a date of the fields of a skeleton of CLDR, in the language's order (Jan 5, 2026; 5. Jan. 2026) |

Cases nest, and every `plural`, `selectordinal` and `select` has an
`other` case. A number in `{n}` is written as `{n, number}` writes it: up
to three fraction digits, grouped, in the language's digits; a number
given as a string of a decimal, `"1.50"`, keeps its fraction digits. A
plural case is the one of the number as `#` writes it, as ICU picks it:
1.0004 is written 1, whose case in English is `one`. A time in `{t}` is
written as `{t, date, short}` and `{t, time, short}` together. Numbers and
dates follow CLDR's data in Go and `Intl` in pages, which agree (see
[Formatting numbers and dates](#formatting-numbers-and-dates)). Of ICU's
number skeletons, Go and pages read `percent`, `integer`, the precision of
fraction digits (`.00`, `.0#`), `group-off` and `currency/EUR`, an amount
with the currency's symbol and fraction digits. The
plural categories are those of the [Unicode CLDR](https://cldr.unicode.org)
for the message's language: `one` and `other` in English, only `other`
in Chinese and Japanese, `one`, `few`, `many` and `other` in Russian, six in
Arabic. Write the ones the language has; `other` is required.

An apostrophe quotes the next brace, or `#` in a plural case: `'{'` says
`{`, `'{name}'` says `{name}`, and two apostrophes say one. Any other
apostrophe is just one, so `don't` needs no quoting.

## In Go

`mygo dev`, `mygo build` and `mygo generate` write `catalog_gen.go` into
the catalogs' directory, a Go package of its own (named after the
directory, or as its other Go files say), which embeds the catalogs:

```go
import "example.com/notes/locales"

title := locales.FilesSelected(3)                // in the language the app shows
german := locales.In("de").FilesSelected(3)      // in another
```

Each key has a function with the typed parameters of its placeholders,
which says the message in the language the app shows, and a method of
`locales.In(tag)` for another language. A message without arguments is
kept once said, so saying it again costs a map lookup and allocates
nothing: a view of native UI says its messages every frame.

The package registers its catalog with package `i18n` as it initializes,
which makes the catalogs' languages the app's: import it, with
`import _ "example.com/notes/locales"` if the Go code says no message
itself. `mygo dev` and `mygo build` warn when the app does not.

`mygo dev` writes the functions as it starts and again whenever a catalog
changes, and `mygo build` before it compiles the app; `mygo generate`
writes them alone. They are generated code: commit them with the
catalogs, so that `go build` and `go test` work without the CLI.

### When the language changes

A view of native UI is built again for every frame, so what it says
follows the language as it changes:

```go
func (app *notes) view(c *ui.Context) {
	ui.Column(c).Gap(8).Children(func() {
		ui.Text(c, locales.FilesSelected(len(app.selected)))
		if ui.Button(c, locales.NewNote()).Clicked() {
			app.add()
		}
	})
}
```

Menu items labelled by their role ("Undo", "Quit Notes") take the new
language's labels themselves. What the app set once, as a window's title
or the labels of its own menu items, it sets again when it hears the
change:

```go
win := mygo.NewWindow(mygo.WindowOptions{Title: locales.Title(), Content: ui.View(app.view)})
mygo.App.OnLocaleChanged(func(string) {
	win.SetTitle(locales.Title())
	mygo.App.SetMenu(app.menu()) // the menu, made again with locales' labels
})
```

## The language the app shows

The app shows one language, which `mygo.App.Locale()` returns: of the
languages it is written in, the one the user reads best, by the languages
they prefer in the order the system lists them
(`App.PreferredLanguages()`), as `zh-Hant` for a reader of `zh-TW`; else
the app's source language. An app written in English alone shows English
to a reader of Chinese, MyGo's controls and menus too, as AppKit does. The
languages the app is written in are its catalogs', which `App.SetLanguages`
replaces for an app without them.

Before 0.5, `App.Locale()` returned the user's first preferred language
whatever the app is written in. An app that translates its texts itself,
without catalogs or `App.SetLanguages`, now gets `"en"` from it: declare
its languages with `App.SetLanguages`, or read the user's from
`App.PreferredLanguages()`.

`App.SetLocale("de")` shows another language at once, and keeps it for the
next launches; `App.SetLocale("")` follows the user's languages again.
`App.ChosenLocale()` returns what `SetLocale` chose and keeps, normalized
(`"de"`, `"zh-Hans"`), or `""` while the app follows the user's languages,
which `App.Locale()` can't tell from the language they resolve to. A
language menu, with an item that follows the system and each language
named in itself ("Deutsch", "简体中文"):

```go
chosen := mygo.App.ChosenLocale()
items := []*mygo.MenuItem{{Label: "System", Type: mygo.MenuItemRadio,
	Checked: chosen == "",
	Click:   func(*mygo.MenuItem, *mygo.Window) { mygo.App.SetLocale("") }}}
for _, tag := range mygo.App.Languages() {
	items = append(items, &mygo.MenuItem{Label: i18n.Autonym(tag), Type: mygo.MenuItemRadio,
		Checked: chosen == tag,
		Click:   func(*mygo.MenuItem, *mygo.Window) { mygo.App.SetLocale(tag) }})
}
```

`ChosenLocale` reports the tag as it was chosen, normalized, even one the
app is not written in: an app that passes `SetLocale` other tags than
those of `App.Languages()`, as `"de-AT"`, gets those back, to compare with
its own or match with `i18n.Match`. While `MYGO_LOCALE` chooses the
language for a run, it reports that one. From another goroutine than the
main thread it waits for it, so it reports the `SetLocale` calls made
before it.

macOS keeps the language as the app's language in System Settings
(`AppleLanguages` of the app's defaults), which AppKit and WebKit say their
own texts in from the next launch; browsers keep it in `localStorage`;
Windows and Linux in the file `locale` of `App.Path(mygo.PathUserData)`.
`App.OnLocaleChanged` hears the language change, as `SetLocale` changes it
or the user's languages do (macOS, Windows and browsers report theirs;
Linux reads the environment, `LANGUAGE`, `LC_ALL`, `LC_MESSAGES` and
`LANG`, once).

When the language changes, windows of native UI draw anew, menu items
labelled by their role say the new language's labels (see
[When the language changes](#when-the-language-changes)), and pages hear
it ([In pages](#in-pages)). The app is laid out in the language's
direction, right to left in Arabic and Hebrew, unless `MYGO_DIRECTION`
(`rtl` or `ltr`) says otherwise: native UI, pages (`mygo.dir`) and, on
Linux, GTK's own widgets, as menus, agree.

### Native UI

`ui.Context.Locale()` and `ui.Context.Region()` return the app's language
and formats; a frame follows their changes. Text is drawn in the app's
language, which picks the glyphs the system's text stack draws for it, as
the Han characters Chinese and Japanese draw apart. `Element.Lang` sets the
language of an element's text and of the elements inside it, as HTML's
`lang` does, `Span.Lang` of a span of a `RichText`, `Font.Lang` of text a
`Painter` shapes:

```go
ui.Text(c, "「直」").Lang("ja") // a Japanese quote in a Chinese app
```

Han characters in a language that does not write them, as a Chinese name
in English text, keep the glyphs the system picks for the user, as
browsers do. On Windows, whose font fallback goes by the locale of the
user's regional formats, they take the first of the user's preferred
languages that writes them, when that locale does not: on English Windows
with Chinese among the preferred languages, a Chinese song title in an
English app is drawn in one Chinese font, not in a mix of a Japanese font
and a Chinese one, of two weights. Browsers draw the text of native UI in the page's language,
which MyGo makes the app's.

Korean text (`ko`, `ko-KR`, …) breaks its lines between words, at spaces
and after punctuation, as Korean is written and as Korean sites have
browsers break it with CSS's `word-break: keep-all`, rather than between
any two syllables as Unicode's line breaking allows; a word longer than a
line still breaks within it, unless the text keeps words whole anyway
(`TextShape.NoBreakWords`). An app showing Korean
(`App.SetLanguages("ko")`) gets it in `Text`, `TextInput`, `TextArea` and
the text it shapes, on every platform; `Lang("ko")` gives it to Korean
text in an app of another language.

`examples/gallery` has a Languages page: a menu of languages, numbers and
dates in each, the controls in Chinese, and the pseudo locales.

### Regional formats

How dates, times and numbers are written is a setting of its own:
`App.Region()` returns the locale of the user's formats, which may be
another language's than the one the app shows, the first day of the week
and whether times have 12 or 24 hours, with the changes the user made in
the system's settings. `App.OnRegionChanged` hears them change. What the
system does not say, as a libc without the data or a browser that does
not tell, comes from CLDR's week and time data for the region
(`i18n.FirstWeekday`, `i18n.HourCycle`).

`Region.FormatLocale(language)` puts the two together as Apple's
`Locale.current` does: English read with British formats is
`en-GB-u-fw-mon-hc-h23`, with German ones `en-DE-u-fw-mon-hc-h23`; format
with it to write dates in the app's language the way the user set them:

```go
formats := mygo.App.Region().FormatLocale(mygo.App.Locale())
label := i18n.FormatDate(formats, due, i18n.DateOptions{Date: i18n.MediumStyle})
```

MyGo's date and time controls do so: the [calendar](https://mygo.egoist.dev/docs/ui/calendar.md)
starts its weeks on the region's first day, the
[date input](https://mygo.egoist.dev/docs/ui/date-input.md) shows the region's numeric date, the
[time input](https://mygo.egoist.dev/docs/ui/time-input.md) its hour cycle, with AM and PM in the
window's language.

## Formatting numbers and dates

Package `i18n` writes numbers, dates and times as ICU and the browsers'
`Intl` write them, with the data of the Unicode CLDR for the locales of
its modern coverage (430 locales of 104 languages: `en`, `en-GB`, `de-CH`,
`pt-BR`, `zh-Hant-HK`, `ar-EG`...). A locale it has no data of formats as
the nearest it has (`zh-TW` as `zh-Hant`, `en-AU` as `en-001`), else as
English.

```go
i18n.FormatNumber("de", 1234567.891, i18n.NumberOptions{})                 // 1.234.567,891
i18n.FormatNumber("hi", 1234567, i18n.NumberOptions{})                     // 12,34,567
i18n.FormatNumber("fa", 0.25, i18n.NumberOptions{Percent: true})           // ۲۵٪
i18n.FormatNumber("en", 1.5, i18n.NumberOptions{MinFractionDigits: 2})     // 1.50
i18n.FormatNumber("en", 1234, i18n.NumberOptions{SignDisplay: i18n.SignAlways}) // +1,234
i18n.FormatNumber("en", 0, i18n.NumberOptions{SignDisplay: i18n.SignExceptZero}) // 0
i18n.FormatNumber("en", 1234, i18n.NumberOptions{Notation: i18n.CompactShort}) // 1.2K
i18n.FormatNumber("fr", 1234, i18n.NumberOptions{Notation: i18n.CompactLong})  // 1,2 millier
usd := i18n.CurrencyOf("USD")
i18n.FormatNumber("en", 1842, i18n.NumberOptions{Currency: usd})                   // $1,842.00
i18n.FormatNumber("en", -1842, i18n.NumberOptions{Currency: usd, Accounting: true}) // ($1,842.00)
i18n.FormatNumber("de", 1842, i18n.NumberOptions{Currency: i18n.CurrencyOf("EUR")}) // 1.842,00 €
i18n.FormatNumber("ja", 1842, i18n.NumberOptions{Currency: i18n.CurrencyOf("JPY")}) // ￥1,842
i18n.FormatNumber("en", 1842, i18n.NumberOptions{Currency: usd, CurrencyDisplay: i18n.CurrencyName}) // 1,842.00 US dollars
i18n.FormatNumber("en", 4.2e6, i18n.NumberOptions{Currency: usd, Notation: i18n.CompactShort})       // $4.2M
i18n.FormatDate("en", t, i18n.DateOptions{Date: i18n.FullStyle})           // Monday, January 5, 2026
i18n.FormatDate("ja", t, i18n.DateOptions{Date: i18n.LongStyle, Time: i18n.ShortStyle}) // 2026年1月5日 13:07
i18n.FormatDate("ru", t, i18n.DateOptions{Skeleton: "yMMMM"})             // январь 2026 г.
i18n.FormatDate("en", t, i18n.DateOptions{Skeleton: "jm", HourCycle: 24}) // 13:07
i18n.FormatDateRange("en", start, end, i18n.DateOptions{Skeleton: "yMMMd"}) // Jan 5 – 9, 2026
```

* **Numbers**: the language's separators and grouping (the Indian
  12,34,567, and none in four digits where it groups from five, as
  Spanish and Polish do), its minus and percent signs, and the digits of
  its numbering system (Arabic-Indic in Persian and Egyptian Arabic,
  Bengali in Bengali; `-u-nu-latn` asks for Latin ones). Fraction digits
  are rounded half away from zero, as `Intl` rounds them, or half to even
  (`HalfEven`). `NumberFormatter` keeps a formatter.
* **The sign**: `SignDisplay` is `Intl`'s `signDisplay`: `SignAuto` (the
  default) writes the minus of a negative number only, `SignNegative` too
  but no minus before a negative zero, `SignAlways` a plus before a number
  that is not negative, and `SignExceptZero` one, but not before a number
  that rounds to zero; `SignNever` writes neither. The plus goes where the
  locale puts its minus, before the percent sign ("+%25" in Turkish) and
  the currency's ("+$1,842.00"), or after it where that is where the
  locale's negative pattern puts it ("€ +1.234,00" in Dutch,
  "CHF+1'234.00" in Swiss German), and a plus of a compact number too
  ("+1.2K").
* **Compact numbers**, as a chart's axis labels them: `Notation:
  i18n.CompactShort` writes 1234 as "1.2K" in English, "1,2 k" in French,
  1.2万 in Japanese, and `i18n.CompactLong` as "1.2 thousand" and "1,2
  millier", as `Intl`'s `notation: "compact"` with `compactDisplay`
  writes them: two significant digits, or the whole number of two digits
  or more (12K, 123K), unless the options give fraction digits; the
  plural form of what is written ("2 millions"); and the decimal pattern
  where the locale has no compact one (German writes thousands whole,
  12.345).
* **Amounts of money**, as a price, a total or a balance shows them:
  `Currency: i18n.CurrencyOf("EUR")` (a code of ISO 4217) writes the
  number as `Intl`'s `style: "currency"` does, in the locale's pattern of
  amounts, which places the currency's sign and spaces it from the digits
  ("$1,842.00", "1.842,00 €", "EUR 1,842.00"), with the currency's
  fraction digits of CLDR's `currencyData` (two for USD, none for JPY,
  three for BHD), which `MinFractionDigits`, `MaxFractionDigits` and
  `Integer` override as `Intl`'s options do. `CurrencyDisplay` is
  `currencyDisplay`: the symbol (`CurrencySymbol`, the default: "US$" in
  British English), `CurrencyNarrowSymbol` ("$"), `CurrencyCode` ("USD")
  or `CurrencyName` ("1,842.00 US dollars", in the amount's plural form).
  `Accounting` is `currencySign: "accounting"`: "($1,842.00)" where the
  locale writes negative amounts so. With `Notation`, amounts are compact
  as `Intl` writes them ("$4.2M"; "4.2 million euros" with names).
* **Dates and times** by style (`FullStyle` to `ShortStyle`, as
  `dateStyle` and `timeStyle`) or by **skeleton**, the fields to show in
  any order (`yMMMd`, `MMMd`, `Ed`, `jm`): the locale's pattern of CLDR
  that suits it best is found as ICU's `DateTimePatternGenerator` finds
  it, so `{year: "numeric", month: "short", day: "numeric"}` in `Intl`
  and `yMMMd` here write the same. `j` is the hour in the locale's
  cycle; `HourCycle` (or `-u-hc-h23` in the locale) makes it 12 or 24.
  Dates are in the Gregorian calendar, times in their `time.Location`.
  `DateFormatter` keeps a formatter, and `Pattern` says the pattern.
* **Ranges** of dates and times, as a date-range picker's trigger labels
  them: `FormatDateRange` (or `DateFormatter.FormatRange`) writes once
  what the two share and twice what they do not, as `Intl`'s
  `formatRange` does with CLDR's interval formats: "Jan 5 – 9, 2026",
  "Jan 5 – Feb 9, 2026", "10:00 – 11:30 AM", or one date when they differ
  in no field the options write. The end is written in the start's
  location.
* **Names**: `Months`, `Weekdays` and `DayPeriods` in a `Width`, standing
  alone (a calendar's title, "январь") or inside a date ("января");
  `Autonym` is a language's name in itself, for a menu of languages
  ("Deutsch", "简体中文", "British English").
* **Week and clock**: `FirstWeekday` and `HourCycle` of a locale's region
  (or its language's likely one), `RegionFor` both.

### Relative times

`FormatRelativeTime` writes a value and unit as `Intl.RelativeTimeFormat`
does, for timestamps, reminders and activity feeds:

```go
i18n.FormatRelativeTime("en", -3, i18n.RelativeMinute, i18n.RelativeTimeOptions{}) // 3 minutes ago
i18n.FormatRelativeTime("zh", 1, i18n.RelativeDay, i18n.RelativeTimeOptions{Auto: true}) // 明天
i18n.FormatRelativeTime("fr", -2, i18n.RelativeDay, i18n.RelativeTimeOptions{Auto: true}) // avant-hier
i18n.FormatRelativeTime("en", -3, i18n.RelativeMinute, i18n.RelativeTimeOptions{Style: i18n.RelativeNarrow}) // 3m ago
```

Units run from `RelativeSecond` through `RelativeYear`, including weeks,
months and quarters. The zero options use full names and always include a
number; `RelativeShort` and `RelativeNarrow` shorten the unit names.
`Auto` uses the locale's words such as “yesterday”, “tomorrow” and “now”
where it has them, and numbers elsewhere. As ICU does, it chooses words
by rounding the offset to hundredths: `1.001` days can say “tomorrow”.
Numbers round to at most three fraction digits, ties to even, and pick
the plural form of the rounded number; negative zero means the past.
`NewRelativeTimeFormatter` keeps the locale and options for repeated,
concurrent use. Unknown units or styles and non-finite values panic.

The caller computes the value and chooses its unit; a month or year is
a calendar unit, not a fixed duration. Formatting reads no clock and
starts no timer. Web frontends can use `Intl.RelativeTimeFormat` directly.
The Go formatter uses CLDR's own words, including locales ICU 78 lacks
(`ht`, `el-polyton`) and `be-tarask`'s short and narrow words for the
current minute and hour, which ICU 78 inherits from `be`.

### Formatting data

Formatting allocates its result only; formatters and the locales' data
are made once and kept. The data is compressed and inflated on first use
in under a millisecond. Every app links the formatting of numbers, as
MyGo's own strings format them, with their data, about 3 KB; the
formatting of dates and their data, with the names of languages, are
linked into an app only when it formats them: with a catalog of its own
(`NewCatalog`, which `mygo generate` writes), `ParseMessage`, the
formatters or the functions that read the data. The patterns of compact
numbers are linked only into an app that names `CompactShort` or
`CompactLong`, and those of ranges only into one that formats a range;
messages read neither (ICU's `compact-short` skeleton is written as a
plain number). The patterns and symbols of currencies (6 KB of data,
about 40 KB with their code) are linked into an app that calls
`CurrencyOf` or has messages of its own (a catalog, `ParseMessage`),
whose skeletons may name a currency; their names, about 300 KB, only into
one that names `CurrencyName` (messages write the symbol for ICU's
`unit-width-full-name`, as for the other stems they do not read); and the
patterns of compact amounts, 10 KB, only into one that also names a
compact notation. Relative times and their patterns are linked only into
an app that calls `FormatRelativeTime` or `NewRelativeTimeFormatter`.
It is one package, not one per language: the user's
regional formats may be any locale's, whatever languages the app ships.

What differs from ICU (`i18n/format_icu_test.go` lists each case the
tests skip): time zones other than UTC are written as offsets from GMT
(`GMT+8`, `GMT+05:30`) where ICU names some ("China Standard Time"); the
calendar is always the Gregorian one, where ICU's default for Thai or
Persian is another; times write spaces where CLDR has narrow no-break
ones, as V8 does, though ranges keep CLDR's thin and narrow no-break
spaces, as V8's `formatRange` does (Safari's writes spaces); and the few
locales whose data ICU 78 (CLDR 48.0) lacks
or has older (Haitian Creole, polytonic Greek) follow CLDR 48.2, as do
ICU's Burmese names of currencies (written after the amount, where CLDR
puts them before) and Serbian Cyrillic of Montenegro's (in Latin there).
Amounts follow ICU's quirks: the formats CLDR gives a currency in a
locale (euros in English of Germany, "€1,842.00") apply to any currency
in a locale of the region that uses it ("US$1,842.00" in `en-DE`), as
ICU's symbols keep them. The fraction digits of currencies are CLDR's,
as V8 (Chrome, Edge and WebView2) has them; JavaScriptCore (Safari,
WKWebView and WebKitGTK) takes ISO 4217's, which differ for a few
(COP, HUF, IDR, IQD, IRR, LAK and PKR have none in CLDR, two or three in
ISO 4217). Compact amounts with `MinFractionDigits` keep up to three
fraction digits, as ECMA-402 and JavaScriptCore have them ("$1.234K");
V8 keeps as many as the minimum ("$1.23K").

## MyGo's own strings

MyGo's controls, its code editor, diff viewer, rich text editor and
Markdown view, the terminal plugin, its document dialogs, the buttons of
pages' alert, confirm and prompt, the titles of file dialogs, and its
menus say their own words:
"Undo", "Find", "Next month", "Do you want to save the changes made to the
document “Notes”?", "Quit Notes", and the names assistive technology reads
("hours", "Saturation and brightness"). They say the language the app
shows: in native UI, the window's (`ui.Context.Locale`). An app that
ships only English shows them in English to a Chinese reader, as AppKit
does. Saying one again in the same language costs a map lookup and
allocates nothing, so controls say theirs every frame.

English is always linked. The translations to other languages, Japanese (`ja`),
Simplified Chinese (`zh-Hans`) and Traditional Chinese (`zh-Hant`) so far, are
packages of their own, so that an app carries only those of the languages
it ships. The code `mygo generate` writes for an app's catalogs imports
them for the app's languages: an app with a `zh-TW.json` gets MyGo's
`zh-Hant`. An app without catalogs, which sets its languages with
`App.SetLanguages`, imports them itself:

```go
import _ "github.com/egoist/mygo/locales/zh-Hans"
```

An app that ships a language MyGo is translated to without importing the
translation is told so as it starts, and shows MyGo's strings in English
there. An app changes MyGo's strings, or gives them its other languages,
with keys `mygo.<key>` in its catalogs:

```json
{
  "mygo.undo": "Rückgängig",
  "mygo.menuQuitApp": "{app} beenden"
}
```

The keys, their messages and their arguments are MyGo's catalog,
[internal/l10n/en.json](https://github.com/egoist/mygo/blob/main/internal/l10n/en.json).
A string resolves as the app's own do: in the app's language, then the
languages it falls back to, then the app's source language, then English.
The inspector of native UI, a tool of developers, stays in English.

The Windows installer's finish page uses `mygo.desktopShortcut` for the
check box that creates a desktop shortcut. The build reads it from the
app's catalogs, including the source language's, or uses MyGo's translation
of that language, falling back to English (see [distribution](https://mygo.egoist.dev/docs/distribution.md#languages)).

### Adding a language to MyGo

MyGo's own strings are `internal/l10n/en.json`, and each translation is
`locales/<tag>/<tag>.json`. To translate MyGo to a language, add
`locales/<tag>/<tag>.json` with every key of `en.json` (the descriptions
say where each shows), run `go generate ./internal/l10n`, which writes the
translation's package and adds it to the languages `mygo generate` links
for apps, and `go test ./internal/l10n`, which checks it as `mygo generate`
checks an app's catalogs: every message in every language, with the
source's placeholders and the language's plural cases.
`mygo.appName` and `mygo.appDescription` are the app's name and
description in a language, which packaging writes where systems list the
app (see [building and distributing](https://mygo.egoist.dev/docs/distribution.md#languages)).

## Which message

A message is the one of the catalog's language that suits the language
asked for best: `zh-Hant` for `zh-TW`, `pt-PT` for `pt-AO`, `en-GB` for
`en-AU`. When that language lacks it, it falls back along CLDR's parent
locales (`en-AU`, `en-001`, `en`; `zh-Hant-HK`, `zh-Hant`), then to the
source language: a missing translation says the source's message rather
than nothing. Its plural cases follow the rules of the language the
message is written in; numbers and dates, the conventions of the language
asked for.

## Checking the catalogs

`mygo generate` and `mygo build` check the catalogs, and so does
`mygo i18n check`. Errors stop them:

* a file not named after a canonical tag, or whose `@@locale` differs;
* a message that does not parse;
* a key a translation has and the source does not, or a key `mygo.<key>`
  MyGo has no message for;
* an argument a translation has and the source's message does not, or used
  in a way its type cannot be (a `String` counted by `plural`);
* two keys whose functions would have the same name.

Warnings do not: messages a translation lacks, which say the source
language's; arguments it leaves out; plural cases its language does not
have (`few` in English) or lacks (`few` in Russian, which says `other` in
its place); placeholders a message does not use.

## Trying a language

`mygo dev -lang de` runs the app in German without changing the language
it keeps. Two pseudo locales test layouts before any translation exists,
as Android's do:

* `en-XA` accents the letters of the source language's messages, makes them
  a third longer and brackets them, `[Šéļéçţ ƒîļéš one two]`: text that
  stays plain is not in a catalog, cut text is too short a space, and a
  sentence of several brackets is made of pieces a translation cannot
  reorder.
* `ar-XB` shows each word right to left, and lays the app out right to
  left: what does not mirror shows.

## In pages

An app with a frontend says the same messages in its pages. Next to the
[TypeScript client](https://mygo.egoist.dev/docs/bindings.md#the-generated-client), the CLI writes a
module named after the catalogs' directory, such as
`src/locales.ts`, with a typed function per key; an app without a
frontend gets none.

```ts
import { languages, messages, messagesIn } from "./locales";

heading.textContent = messages.filesSelected(3); // in the language the page shows
messagesIn("de").greeting("Ada");                 // "Hallo, Ada!"
```

The module holds the messages of every language, and `mygo-runtime/i18n`
formats them with the browser's `Intl`, so the pages and Go say the same
words in the same plural forms. The messages are bundled with the
frontend rather than fetched: the first paint is in the right language,
without waiting for a request, also in a page of the dev server opened in
a browser. `messages` says each message in the language of the moment:
the app's, which MyGo gives the page before its scripts run, else the
browser's.

`mygo.locale` and `mygo.dir` are there before the page's scripts run.
When the language changes, pages hear the `mygo:locale` event
(`onLocaleChange` of `mygo-runtime`): draw the page again. Pages loaded
later start in the new language. Web views take the language too, for
their own texts: WebView2 as its environment is made, which keeps it (a
change shows from the next launch), WebKitGTK at once, WKWebView from the
next launch.

`formatMessage(locale, message, args)` from `mygo-runtime/i18n` formats a
message of your own.

## Package i18n

The generated code is built on package
[`i18n`](https://pkg.go.dev/github.com/egoist/mygo/i18n), which apps can
use directly: `NewCatalog` reads catalogs from any `fs.FS`, `ParseMessage`
and `Message.Format` format a message, `PluralCategory` and
`OrdinalCategory` give the CLDR plural category of a number, written as an
integer, a float or a decimal string whose fraction digits count (`"1.50"`
has two), `Parent` the locale a locale falls back to, and the formatting
of [numbers and dates](#formatting-numbers-and-dates). Its data comes from
CLDR `i18n.CLDRVersion`.
