# The Internationalization System ## Extracting Translations For translators to be able to translate text, and for us to be able to work with those translations, we need translation files. These files are located in the `/po` directory. These files are automatically generated by running the `make po` command. In order to extract translations from the source code, the `make po` command will search for calls to the `Get()` family of functions, such as `Get()` and `GetN()`. ```go func example(n int) { /* Not extracted for translation */ fmt.Println("Hello, Sailor!") /* Extracted for translation */ fmt.Println(i18n.GetN("1 book", "{N} books", n, map[string]any{"N": n})) } ``` Sometimes you want to provide additional context about a translation to the translators. You can do this by placing a comment above the translated string with the prefix ‘TRANSLATORS:’. ```go func example() string { /* TRANSLATORS: ‘Home’ button on the navigation bar */ return i18n.Get("Home") } ``` Especially when working in HTML templates, you may have a string that you want to go through the formatting system but _not_ be marked for translation. You can do this by calling the underlying `Sprintf()` function instead. ```html
{{ .Printer.Sprintf "{N:m}" (map "N" 2) }}
``` ## The Formatting System For string formatting we use a custom implementation of `Sprintf()`, and all `Get*()` functions format the translated strings using our own `Sprintf()`. Do note that all `Sprintf()` output is automatically HTML escaped. Unlike the standard `Sprintf()`, we use named placeholders enclosed by curly braces instead of percent codes. As placeholders are named, they need to be passed via a map of type `map[string]any`. ```go func example() { status := "late" /* Go’s Sprintf */ _ = fmt.Sprintf("The bus is %s", status) /* Our Sprintf */ _ = i18n.Sprintf("The bus is {LateOrEarly}", map[string]any{"LateOrEarly": status}) } ``` The result is a lot more visually-noisy than what Go does — mostly due to the map syntax — but it offers infinitely more information to translators, which is important. Translation functions can be provided multiple argument maps, and maps can be easily created in HTML templates using the `map` function. ```html {{ $nlargs := (map "DutchStart" `` "DutchEnd" `em,span`) }}{{ .Get "{Name} said ‘{DutchStart:r}{Quote}{DutchEnd:E}’!" (map "Name" "Thomas" "Quote" "...") $nlargs }}
``` In a placeholder you can also use a colon and additional character code to customize how formatting is performed. The default behaviour is as follows: - Strings are printed verbatim - Numbers (`int`) and floats (`float64`) are formatted with the locale-specific grouping- and decimal separators - Dates (`time.Time`) are formatted according to the current locale - Other types are coerced to a string by `fmt.Sprintf()` and then printed verbatim The following character codes are available with the following behaviour: `e` : \[e]mail — inserts a link to an email address `E` : \[E]nd — inserts closing-tags for the comma-separated HTML tags `l` : \[l]ink (internal) — links to an internal page `L` : \[L]ink (external) — links to an external page `m` : \[m]onetary — formats the given `int` or `float64` as an amount of Euros. `r` : \[r]aw — inserts the given string verbatim without HTML escaping ```html{{ .Get "{Email:e}" (map "Email" "help@euro-cash.eu") }}
{{ .Get "{SwedishStart:r}Växjösjön{SwedishEnd:E}" (map "SwedishStart" `` "SwedishEnd" "em,span") }}
{{ .Get "Click {Link:l}here{-:E}!" (map "Link" "/banknotes") }}
{{ .Get "Click {Link:L}here{-:E}!" (map "Link" "https://euro-cash.eu") }}
{{ .Get "{1:m} and {1.00:m}" (map "1" 1 "1.00" 1.00) }}
``` Some additional notes: - The `-` name is special. `{-:E}` inserts ``. This exists because of how often you need to do this. - The `m` character code won’t include decimals when the argument is an integer, and will include the decimals when the argument is a floating-point number.