aboutsummaryrefslogtreecommitdiffhomepage
path: root/doc/i18n.md
diff options
context:
space:
mode:
Diffstat (limited to 'doc/i18n.md')
-rw-r--r--doc/i18n.md131
1 files changed, 131 insertions, 0 deletions
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
+<p>{{ .Printer.Sprintf "{N:m}" (map "N" 2) }}</p>
+```
+
+## 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" `<span lang="nl"><em>`
+ "DutchEnd" `em,span`) }}
+<p>{{ .Get "{Name} said ‘{DutchStart:r}{Quote}{DutchEnd:E}’!"
+ (map "Name" "Thomas" "Quote" "...") $nlargs }}</p>
+```
+
+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
+<!-- <a href="mailto:help@euro-cash.eu">help@euro-cash.eu</a> -->
+<p>{{ .Get "{Email:e}" (map "Email" "help@euro-cash.eu") }}</p>
+
+<!-- <span lang="sv"><em>Växjösjön</em></span> -->
+<p>{{ .Get "{SwedishStart:r}Växjösjön{SwedishEnd:E}"
+ (map "SwedishStart" `<span lang="sv"><em>`
+ "SwedishEnd" "em,span") }}</p>
+
+<!-- Click <a href="/banknotes">here</a>! -->
+<p>{{ .Get "Click {Link:l}here{-:E}!"
+ (map "Link" "/banknotes") }}</p>
+
+<!-- Click <a href="https://euro-cash.eu" target="_blank">here</a>! -->
+<p>{{ .Get "Click {Link:L}here{-:E}!"
+ (map "Link" "https://euro-cash.eu") }}</p>
+
+<!-- Varies per locale -->
+<!-- €1 and €1.00 -->
+<p>{{ .Get "{1:m} and {1.00:m}" (map "1" 1 "1.00" 1.00) }}</p>
+```
+
+Some additional notes:
+- The `-` name is special. `{-:E}` inserts `</a>`. 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