i18n
Internationalisation for Boundary apps: marker-based translation, locale chain resolution, EDN catalogues, and Ring middleware. Follows ADR-013.
Key namespaces
| Namespace | Purpose |
|---|---|
|
Pure |
|
|
|
|
|
|
|
|
|
Integrant |
Marker syntax
;; Simple lookup
[:t :user/sign-in]
;; With interpolation params
[:t :user/greeting {:name "Alice"}]
;; With plural count (4th element)
[:t :user/items {:n 3} 3]
Markers are resolved by boundary.i18n.shell.render/resolve-markers during render. They can appear anywhere in a Hiccup tree. Strings pass through unchanged.
Locale chain
wrap-i18n builds an ordered locale chain from the Ring request:
User locale —
[:session :user :language]Tenant locale —
[:tenant :settings :language]Default locale from Integrant config
Hard fallback:
:en
Missing keys degrade to (str key) (e.g. "user/sign-in"). They never throw.
Catalogue format
{:user/sign-in "Sign In"
:user/greeting {:one "Hello {name}" :many "Hello everyone"}
:user/items {:zero "No items" :one "{n} item" :many "{n} items"}
:common/button-cancel "Cancel"}
Key namespacing conventions: :common/* for shared UI strings, :user/*, :admin/*, :search/*, etc. per module. English and Dutch catalogues are included out of the box.
Integrant configuration
;; config.edn
:boundary/i18n {:catalogue-path "boundary/i18n/translations"
:default-locale :en
:dev? true} ; omit or false in production
The component is injected into :boundary/http-handler as :i18n. With :dev? true, resolved markers are wrapped in [:span {:data-i18n "..."}] for browser inspection.
Middleware
boundary.i18n.shell.middleware/wrap-i18n injects into every Ring request:
| Key | Description |
|---|---|
|
Ordered vector of locales to try, e.g. |
|
Translation function — |
;; In a handler
(let [t (get request :i18n/t identity)]
(t :user/sign-in)
(t :user/greeting {:name "Alice"})
(t :user/items {:n 3} 3))
Adding a locale
Add
libs/i18n/resources/boundary/i18n/translations/fr.edn.Update
load-cataloguedefault locales or pass it explicitly from config.Run
bb i18n:missingto see which keys need translating.
Babashka tooling
# Find a key by substring or exact keyword
bb i18n:find "Sign in"
bb i18n:find :user/sign-in
# Scan core/ui.clj files for unexternalised string literals (CI gate)
bb i18n:scan
# Report translation gaps (keys in en.edn missing from other locales)
bb i18n:missing
# Report catalogue keys not referenced in any source file
bb i18n:unused
Testing
clojure -M:test:db/h2 :i18n
# Unit tests only
clojure -M:test:db/h2 :i18n --focus-meta :unit