Contents · Localization

pkgproxy design: localization

How errors, log messages and setup guidance are translated. Part of the pkgproxy design, whose index says which file holds each § number.

22. Localization: translatable errors and setup guidance

Every user-visible string goes through go-i18n’s l10n.D domain (internal/l10n). The .po catalog is maintained with go tool i18n scan|merge|check -C internal/l10n. The scanner follows constants passed to Tr/TrN/Errorf, so a shared message can live in a const. It extracts a comment next to a call as a translator comment only when the comment starts with TRANSLATORS:, so ordinary code and doc comments stay out of the catalog.

  • Errors are built with l10n.D.Errorf, never fmt.Errorf or errors.New. That includes sentinels (var errX = l10n.D.Errorf("...")): Errorf returns a distinct pointer, so errors.Is identity is preserved. Error() stays English, so logs and tests are locale-stable. go-cmdr’s error printer detects i18n.LocalizedError and shows the translated text, wrapped chain included.
  • Wording: a failed action reads cannot <verb> ..., never “failed to …”, “could not …” or a bare gerund wrapper (“creating X: %w”). Errors that describe a state (“malformed field line”) stay as they are. Package prefixes (apt:, lxdprovision:) are kept. Daemon log messages and HTTP error bodies use the same wording. HTTP error bodies are not translated, because HTTP clients carry no locale. That includes the payloads of a protocol: the message of an error result that snapd shows as it is (§11.7), the store-style error list a refused snapd request is answered with (§21) and the plain-text body offline.PlainAnswer gives other clients. They are English, each a whole sentence (or a format with the snap and revision in it) kept in one place, never a fragment glued to another or to a translated string, so that a message is the same whatever locale the daemon runs in and tests and people can match on it. The only error spliced into one is that of the assertion chain check, whose Error() is English by the rule above.
  • Log messages are translated by l10n.NewLogHandler, which wraps the daemon’s slog handler. It translates each record’s message and renders error-valued attributes through D.LocalizedError, using the locale of the record’s context when it has one and the daemon’s otherwise. Attribute keys and other values stay as they are, so log lines keep the same machine-readable shape in every locale. Log messages are never passed to Tr in source, so each call site wraps its message in i18n.MarkForTranslation(...) for the scanner to extract. A message that comes from a library at run time (the checksum database client’s) passes through untranslated. The snap’s daemon runs under systemd, normally in the C locale, so in that deployment logs stay English unless the service is given a locale.
  • Setup guidance (pkgproxyctl setup * on a TTY, and the daemon’s startup banner) is assembled with guide (cmd/pkgproxy/guide.go): translated prose paragraphs, reflowed to 74 columns, interleaved with verbatim, computed code blocks. A msgid is one unwrapped paragraph of prose with %s/%q placeholders for paths and names. Commands, config and YAML are never part of a msgid, so translators never see shell, and changing a command never invalidates a translation. Piped (| sh) output stays untranslated, except for the messages the scripts themselves print (shellFail), which are translated and shell-quoted (§14.2).