Skip to content

Instantly share code, notes, and snippets.

@robert-stuttaford
Last active March 24, 2019 20:44
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save robert-stuttaford/50acaa23986a52281f15982baa4922c9 to your computer and use it in GitHub Desktop.
Save robert-stuttaford/50acaa23986a52281f15982baa4922c9 to your computer and use it in GitHub Desktop.
Language translations for Datomic entities with fallback to base entity
(ns cognician.db.translate
(:require [datomic.api :as d])
(:import [clojure.lang MapEntry]
[datomic.query EntityMap]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Language
(def default-language :en-GB)
(def default-language? #{nil :default default-language})
(def ^:dynamic *language* default-language)
(def languages
[["English (United Kingdom)" :en-GB]
["English (United States)" :en-US]
["Chinese" :zh-CN]
["Dutch (Standard)" :nl]
["French (Standard)" :fr]
["German (Standard)" :de]
["Hungarian" :hu]
["Indonesian" :id]
["Italian (Standard)" :it]
["Korean" :ko]
["Polish" :pl]
["Portuguese" :pt]
["Russian" :ru]
["Spanish" :es]])
(def valid-language? (set (map second languages)))
(defmacro with-language [language & body]
`(binding [*language* (or ~language ~*language*)]
~@body))
(defn wrap-language
"Detects `lang=code-as-string` in query string and updates user if found.
Wraps with `with-language` to support calls to `translate`."
[handler]
(fn [request]
(let [user (:user-entity request)]
(let [language (:user/language user)
chosen-language (some-> (get-in request [:query-params "lang"])
keyword
valid-language?)]
(when (and chosen-language (not= chosen-language language))
(d/transact-async (:datomic-conn request)
[[:db/add (:db/id user) :user/language chosen-language]]))
(with-language (or chosen-language language default-language)
(handler request))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Translations
(defn get-translated-key [entity translation k]
(if (= :db/id k)
(get entity k)
(or (get translation k)
(get entity k))))
(defprotocol ITranslatedEntity
(source-entity [_]))
(deftype TranslatedEntity [entity translation]
ITranslatedEntity
(source-entity [_]
entity)
clojure.lang.ILookup
(valAt [_ k]
(get-translated-key entity translation k))
(valAt [_ k not-found]
(or (get-translated-key entity translation k) not-found))
clojure.lang.Associative
(containsKey [_ k]
(.containsKey entity k))
(entryAt [_ k]
(MapEntry. k (get-translated-key entity translation k))))
(extend-type datomic.query.EntityMap
ITranslatedEntity
(source-entity [this]
this))
(defn get-translation [entity language]
(let [db (d/entity-db entity)]
(->> language
(d/q '[:find ?translation .
:in $ ?base ?language
:where
[?base :meta/translations ?translation]
[?translation :meta/language ?language]]
db
(:db/id entity))
(d/entity db))))
(defn translate
([entity] (translate entity *language*))
([entity language]
(if-let [translation (get-translation entity language)]
(TranslatedEntity. entity translation)
entity)))
;; usage
;; base
;; {:db/id base-entity-id :content/key1 "hello" :content/key2 "bye", :meta/translations #{translation-entity-id}}
;; translation
;; {:db/id translation-entity-id, :content/key1 "bonjour", :meta/language :fr}
;; (:content/key1 base-entity) ; "hello"
;; (:content/key2 base-entity) ; "bye"
;; (:content/key1 (get-translation base-entity :fr)) ;; bonjour
;; (:content/key2 (get-translation base-entity :fr)) ;; bye
;; (:content/key1 (source-entity (get-translation base-entity :fr)) ;; hello
;; (:content/key1 (source-entity base-entity) ;; hello
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment