Last active
March 24, 2019 20:44
-
-
Save robert-stuttaford/50acaa23986a52281f15982baa4922c9 to your computer and use it in GitHub Desktop.
Language translations for Datomic entities with fallback to base entity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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