Internationalization¶
Warning
Important notice: please don't translate server logs with i18n.*()
. It uses the request locale, not the server one.
Quick links to Crowdin¶
Todo¶
- Add cookie: Locale
- Rewrite jet template on load to use translated strings
- Add dedicated option to set locale in settings page
- Check if any jet template strings are ignored (false negative)
- Setup Crowdin
- manual upload and download
- automatic upload and download
- automated upload and download via Woodpecker CI
Crowdin CLI usage¶
Install the CLI: See official instructions
Remember to check crowdin -V
. Should be 4.2.0
or later.
First, set up API token:
- Go to https://crowdin.com/settings#api-key
- Click the "New Token" button
- Set permission: Projects >
- Projects (List, Get, Create, Edit) -- Read only
- Source files & strings (List, Get, Create, Edit) -- Read and write
- Translations (List, Get, Create, Edit) -- Read and write
- Copy the new token and save it somewhere
Then, try it out.
Bash | |
---|---|
Crowdin Docker usage¶
These instructions assume CROWDIN_PERSONAL_TOKEN
is available as an environment variable.
Step 0: Generate i18n source files¶
Create locale/en
source files (code.json
and template.json
):
Bash | |
---|---|
Step 1: Set up Docker container¶
Create an interactive container using crowdin/cli:latest
, mounting local files:
Bash | |
---|---|
Step 2: Upload local sources to Crowdin¶
Update locale/en
sources on the Crowdin project:
Bash | |
---|---|
Step 3: Download translations to local repository¶
Fetch new translations from Crowdin and update non-English locales:
Bash | |
---|---|
Crowdin Web UI usage¶
Add new languages to translate in Settings > Languages.
./i18n/crawler -- Primer on html and jet's treatment of templates¶
Text Only | |
---|---|
html: abc {{.Hi}}
is text
jet: <a>abc
is text
jet: </a>
is text
So, jet doesn't care about HTML.
And we have to care about {* *}
comments.
./i18n/crawler -- Coalesce inline tags¶
Example: in below, the <a>
tag should be part of the string.
HTML | |
---|---|
Tags to consider as inline: a
Implementation details¶
Warning
This section was written using an LLM with codebase context, and may contain inaccuracies
Should be used as a rough primer on the i18n implementation only
This section covers implementation details for the i18n system.
The i18n system consists of the following components:
Component | Description | Key files/functions |
---|---|---|
Crawler | Extracts translatable strings from HTML template files | crawler/main.go |
Converter | Processes crawler output to generate translation map | converter/main.go , i18n.SuccintId() |
Locale files | Store en source and translations in JSON format |
i18n/locale/<lang_code>/code.json , i18n/locale/<lang_code>/template.json |
Lookup and rewrite functions | Core i18n functionality for loading translations and looking up strings | lookup.go , rewrite.go |
Integration | Wrapper functions for automatic translation lookup | Tr() , Sprintf() |
Additional notes:
- Uses
xxHash
for string hashing when generating IDs - Caches
strings.Replacer
objects for performance - Supports different locales per goroutine using
routine.InheritableThreadLocal
- Includes a Semgrep rule (
semgrep-i18n.yml
) for detecting untranslated strings
1. Crawler¶
The crawler (crawler/main.go
) scans HTML template files to extract translatable strings.
- Uses the
html
package to parse HTML - Traverses the DOM tree to find text nodes
- Ignores certain patterns (e.g., Jet template commands and strings included in the
IgnoreTheseStrings
variable) - Outputs a JSON array of objects containing the message and file path
Example output:
2. Converter¶
The converter (converter/main.go
) processes the crawler output to generate a translation map.
- Reads the crawler JSON from stdin
- Generates a unique ID for each string using
i18n.SuccintId()
- Outputs a JSON object mapping IDs to original strings
Example output:
3. Locale files¶
Translations are stored in JSON files under i18n/locale/<lang_code>/
:
code.json
: Translations for strings in Go codetemplate.json
: Translations for strings in HTML templates
The base locale (English) contains the original strings, while other locales contain translated strings.
4. Lookup and rewrite functions¶
The core i18n functionality is implemented in lookup.go
and rewrite.go
.
4.1. Lookup¶
lookup.go
provides functions to load translations and look up strings:
Init()
: Loads all locale files into memory__lookup_skip_stack_2()
: Performs the actual string lookupSuccintId()
: Generates a unique ID for a string based on file path and content
4.2. Rewrite¶
rewrite.go
handles template rewriting:
Replacer()
: Returns astrings.Replacer
for a given locale and filetranslationPairs_inner()
: Generates replacement pairs for a locale and file
5. Integration¶
The i18n system is integrated into the application code using wrapper functions that automatically look up translations based on the current locale: