A lightweight i18n solution for use with re-frame. It’s inspired by Tower, a Clojure/Script i18n & L10n library.

Update: later found the Tongue library (by Nikita Prokopov).

i18n code

(ns examplens.i18n
  (:require [examplens.translations :refer [translations]]))

(def i18n-not-found-key "*I18N-KEY*")
(def i18n-not-found-fallback-lc "*I18N-FB-LC*")

(defn- _t
 "Returns translation for key based on supplied locale."
 [lc key]
 {:pre [(not (nil? key))]
 :post [(not (nil? %))]}

 (let [fb-lc (:fallback-lc translations)
       td (:dictionary translations)
       tdk (get td key)]
   (if (nil? tdk)
      (if (and (not (nil? lc)) (contains? tdk lc))
        (get tdk lc)
        (if (contains? tdk fb-lc)
          (get tdk fb-lc)

(def t
  "Returns memoized translation for key (based on supplied locale)."
  (memoize _t))

Example translations code

(ns examplens.translations)

(def translations
  "Table with translations per locale."
  {:fallback-lc :en-US
   {:home-title {:en-US "Welcome"
                  :nl-NL "Welkom"}
    :home-subtitle {:en-US "Time to start building the site."
                     :nl-NL "Tijd om de site te maken."}
    :home-content {:en-US "The content for the home page."
                    :nl-NL "De inhoud voor de home pagina."}}})

The re-frame related code

;; In the re-frame database a :lc field is
;; defined (with an initial value).
... {:lc :nl-NL} ...

;; Handler.
  (fn [db [lc]]
    (assoc db :lc lc)))

;; A subscription handler is set up.
  (fn [db _]
    (reaction (:lc @db))))

;; Consumption in a re-frame component (when the locale
;; changes the view is automatically updated).
(defn home []
  (let [lc (f/subscribe [:lc])]
    (fn []
         [:h1 (i18n/t @lc :home-title)]]
         [:h3 (i18n/t @lc :home-subtitle)]]
         [:p (i18n/t @lc :home-content)]]])))