From 62cf77b09298f31d2bc9d6b1e25e011e19fede5a Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Thu, 24 Jul 2025 20:47:07 +0200 Subject: Rename docs/ to doc/ --- doc/i18n.md | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 doc/i18n.md (limited to 'doc/i18n.md') diff --git a/doc/i18n.md b/doc/i18n.md new file mode 100644 index 0000000..475a512 --- /dev/null +++ b/doc/i18n.md @@ -0,0 +1,131 @@ +# 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: + +| Code | Mnemonic | Description | +| ---- | ------------------ | ----------------------------------------------------------- | +| `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. \ No newline at end of file -- cgit v1.2.3