🤖 locales
amadan.net/rastrillo/rastrillo/locales
The CLDR data that writes and reads money in each language. The core
is this package. Each language's data is a pack of its own,
locales/<lang>, which your app links with a blank import. Go links
only what is imported, so a language you do not offer costs nothing.
Localization is the guide.
Packs
One pack covers one base language and every modern regional variant of
it: locales/de has de, de-AT, de-CH, de-LI and the rest. A
generated app gets the packs it needs from manifest/app.toml.
rastrillo generate writes them to gen/locales/packs.go:
import (
_ "amadan.net/rastrillo/rastrillo/locales/en"
)
A hand-written app imports the packs for the languages it offers. If one is missing, the app refuses to start and names the import to add.
func PacksFor(offered []string) ([]string, error)
PacksFor is the packs a set of language codes needs, without
duplicates: en, zh-Hans, zh-Hant, pt-PT and yue need en,
pt, yue and zh. A code is what your app offers, routes and keeps
catalogs under; a pack is only where its formatting data lives.
The en pack adds about 55 KB to a binary, and most packs far less.
locales/modern imports all 101, about 500 KB, for a tool that has to
format in any language.
CheckConfig
func CheckConfig(defaultLocale string, offered []string) (Config, error)
func CanonicalIdentity(code string) (string, error)
type Config struct {
Default string // the declared effective default: an offered identity
DefaultFormat string // the linked locale that formats it
Offered []string // as declared, canonical
Formats map[string]string // offered identity -> formatting locale
Packs []string // derived, deduplicated
LinkedPacks []string // what this binary actually links
}
CheckConfig checks the languages an app offers, in the binary that
will serve them. Serve and Handler run it before they open the
database, and the app refuses to start on any problem. It checks that:
- each code is written the way CLDR writes it: no spaces, hyphens not
underscores, the language in lower case, a script in title case and a
region in upper case.
CanonicalIdentitychecks one code and returns the right spelling; - no code is offered twice;
- each code is a language CLDR's modern set formats, and its pack is linked. A parent standing in for a missing pack is not enough;
- the default is one of the offered codes. It is
defaultLocale, or the first offered code, oren.
A wrong configuration is never repaired, because a code that only works once corrected would be stored and routed under its wrong spelling. The error says what to write:
locale configuration: "en_US" is not in canonical form; write "en-US"
locale configuration: "de" needs the de pack: import _ "amadan.net/rastrillo/rastrillo/locales/de"
Config keeps your codes and their formatting locales apart. zh-TW
is the code your app routes and keeps catalogs under; Formats says
zh-Hant writes its money.
Resolve
func Resolve(tag string) Resolution
type Resolution struct {
ID string
Wanted string
MissingPack string
Malformed bool
UsedDefault bool
}
Resolve finds the linked data for any language tag, such as one from
Accept-Language or a stored preference. Unlike CheckConfig, it is
forgiving. It checks the tag's grammar, applies CLDR's aliases and
likely scripts, and matches every locale rastrillo ships, linked or not.
That match is Wanted. zh-TW is not a CLDR locale, so it matches
zh-Hant; cutting it to zh would pick Simplified Chinese data.
If Wanted's pack is not linked, Resolve walks up CLDR's parents to a
linked one, and names the pack it lacked in MissingPack: hi-Latn
falls back to en-IN, ht to fr-HT, nb and nn to no. With no
linked parent it uses the app's default language and sets
UsedDefault. ID is the locale that writes the amount. Only the
conventions fall back; the currency never changes.
Malformed is a tag that is not a well-formed language tag, such as
de--CH.
The configuration probe
Run your app with RASTRILLO_CHECK_CONFIG=1 and it checks its
languages, prints one JSON line and exits. It opens no database, runs no
migration and serves nothing, so it is safe to run beside a live copy:
RASTRILLO_CHECK_CONFIG=1 go run ./cmd/shop
{"rastrillo_check_config":1,"ok":true,"default":"en","default_format":"en","offered":[],"formats":{"en":"en"},"packs_required":["en"],"packs_linked":["en"],"problems":[]}
RASTRILLO_CHECK_CONFIG=1: checked the configuration and exited without serving; unset it to serve
It exits 0 when the configuration is good and 1 when problems lists
something. Any other value of the variable exits 2:
RASTRILLO_CHECK_CONFIG="yes": set it to 1 to check the configuration, or unset it to serve
It always exits, even in production. A server that inherits the variable stops at once, with that line in its log, rather than serving.
rastrillo.Run and rastrillo.Resolve run the probe first. A main
that calls neither calls
rastrillo.ProbeConfig first.
rastrillo doctor builds your app and runs the probe for you; see
The CLI.
func Probe(env, defaultLocale string, offered []string, stdout, stderr io.Writer) (run bool, exit int)
type ProbeReport struct {
Version int `json:"rastrillo_check_config"`
OK bool `json:"ok"`
Default string `json:"default"`
DefaultFormat string `json:"default_format"`
Offered []string `json:"offered"`
Formats map[string]string `json:"formats"`
PacksRequired []string `json:"packs_required"`
PacksLinked []string `json:"packs_linked"`
Problems []string `json:"problems"`
}
Probe is the logic ProbeConfig runs. ProbeReport is the line it
prints; every field is always there, so a script can read it.
The rest
Most apps never call these. The framework uses them, and so can a test or a tool.
func Install(cfg Config)
func SetDefault(id string)
func SetDefaultForTest(id string) (restore func())
func FormatFor(identity string) string
Install makes an accepted Config the process's own, so request-time
formatting uses the mapping boot checked. rastrillo.Handler calls it.
SetDefault sets only the default formatting locale.
SetDefaultForTest replaces the default for one test and returns a
function that puts it back. FormatFor is the formatting locale for a
code: the installed mapping when the code is offered, else what
Resolve finds.
func Linked(id string) bool
func LinkedIDs() []string
func LinkedPacks() []string
func PackOf(id string) (string, bool)
func Parent(id string) string
func DigitSets() []string
func StorageCodes() []string
func Storage(code string) (Currency, bool)
const CLDRVersion = "48.0.0"
Linked, LinkedIDs and LinkedPacks say what this binary links.
PackOf names the pack that carries a locale, linked or not. Parent
is a locale's CLDR parent, which may be in another pack. DigitSets is
every numbering system's digits, so text in any of them can be read.
StorageCodes lists the storage set, and Storage gives one code's
Currency: its Exponent, and whether it is Eligible for new
records. CLDRVersion is the CLDR release the data comes from.
type Locale struct{ /* the marks, digits, patterns and symbols of one locale */ }
type Override struct{ Pattern, Decimal, Group string }
func Data(id string) (Locale, bool)
func Merge(child, parent Locale) Locale
func Register(pack string, locs map[string]Locale)
Locale is one locale's money data, stored in its pack as what differs
from its parent. Override is a pattern or mark that applies to one
currency only. Data merges a locale with its parents, and Merge
merges one pair. Register is how a pack adds its locales when it is
imported; you never call it.
Two test packages sit under locales/localestest. gallery links only
the twelve languages the framework's strings ship in. crosspack links
only en, fr and no, to test the fallbacks across packs.
Read this page as markdown — exact, unstyled, and cheap for an agent to fetch.