-
-
Save terjesb/c62b4ae1f9791365bdb05d41efd206ee 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