Trayscale uses the same embedded GNU gettext catalogs under this directory for two runtimes:
- Go strings — gotext loads
po/<lang>/default.podirectly (locale.Get/locale.GetN). - GtkBuilder
.uilabels — at startup a small pure-Go.po→.mocompiler materializes catalogs under$XDG_CACHE_HOME/trayscale-locale/<lang>/LC_MESSAGES/trayscale.mo(or~/.cache/...), thenbindtextdomain("trayscale", …)so GTK translates attributes markedtranslatable="yes"viadgettext. No hostmsgfmtis required at build or run time.
po/<lang>/default.po
en/default.po— English source catalog (msgid == msgstr). Copy this when starting a new language.es/default.po— Spanishpt/default.po— Portugueseja/default.po— Japanese
Language codes follow the usual gettext forms (es, en_US, …). At startup the app picks the best match from LANGUAGE / LC_* / LANG for gotext, and the same preference list drives libintl catalog lookup after locale.SanitizeEnvironment().
- Copy
po/en/default.potopo/<lang>/default.po(for examplepo/fr/default.po). - Set the
Language:header to your language code. - Fill in
msgstrvalues. Leave a string asmsgstr ""to fall back to English. - Rebuild:
go build -o trayscale ./cmd/trayscale(orgo test -vet=all ./...). - Run with your locale, for example
LANGUAGE=fr ./trayscale.
Keep mnemonics in both msgid and msgstr when the UI uses them (for example _Quit → 終了(_Q)), so GTK menus show the underlined accelerator.
Optional extraction helper (install separately):
go run github.com/leonelquinteros/gotext/cli/xgotext@latest -in . -out po/templatesMerge newly extracted msgids into each language file. Strings wrapped in Go look like locale.Get("…") / locale.GetN(…). For Builder XML, mark label (and similar) attributes with translatable="yes".
LANGUAGE is preferred for catalog selection. Bare LANG=ja (or any language without a generated system locale in locale -a) makes glibc/setlocale fail: you get locale: Cannot set LC_* to default locale warnings and GTK/GLib can hang on startup.
At process start Trayscale calls locale.SanitizeEnvironment(): if LANG/LC_* are unsupported it copies the language into LANGUAGE (when unset) and sets LC_ALL to C.UTF-8 / C.utf8 so libc/GTK stay happy while gotext and libintl still load the right catalog via LANGUAGE.
Prefer LANGUAGE=ja (or install the system locale, e.g. ja_JP.UTF-8) when testing.
On hosts that only provide C / C.UTF-8, BindGettext clones C.utf8 into the cache as a synthetic en_US.UTF-8 and sets LOCPATH so libintl still honors LANGUAGE (GNU gettext ignores LANGUAGE in the C locale).
Product brands stay English in every catalog: Trayscale, Tailscale, Taildrop, Mullvad.
Standard networking acronyms stay as the English acronym in UI labels (GNOME NetworkManager, OpenWrt LuCI, Microsoft networking docs, vendor Japanese manuals). Do not invent calques for short controls:
| Term | Practice |
|---|---|
| FQDN | Keep FQDN (docs may say “nombre de dominio completo (FQDN)” / “nome de domínio completamente qualificado” / 完全修飾ドメイン名; UI keeps the acronym) |
| DNS, LAN, NAT, UDP, UPnP, IPv4, IPv6 | Keep the acronym |
| DERP | Keep DERP (Tailscale relay protocol name, like STUN) |
Loanwords / daemon:
| Term | es | pt | ja |
|---|---|---|---|
| Online / Offline | Conectado / Desconectado (GNOME/VPN peer-status practice; preferred over en línea for connection badges) |
Keep Online / Offline (dominant BR peer-status UI loanwords; GNOME sometimes uses conectado/desconectado) |
オンライン / オフライン |
| daemon | demonio (GNOME) |
Keep daemon (Unix loanword in BR tech UIs) |
デーモン |
Wrapped (Go): tray menu labels, notifications/toasts, dialog headings and buttons constructed in Go, sidebar section titles, and other locale.Get call sites under internal/ui and internal/tray.
Builder .ui files: Static title / subtitle / label / tooltip-text / description (and menu label attributes in menu.ui) marked translatable="yes" use GtkBuilder gettext with domain trayscale.
Desktop launcher: dev.deedles.Trayscale.desktop uses Desktop Entry locale keys (Name[es], Comment[ja], …) for es / pt / ja.
Not wrapped yet:
- GSettings schema summaries (
dev.deedles.Trayscale.gschema.xml) - AppStream metadata (
.metainfo.xml) - Proper nouns and dynamic Tailscale data (hostnames, IPs, region names from the control plane)