Created
November 5, 2019 05:05
-
-
Save isaksky/d1a5f3a1873dc853f04821392118b756 to your computer and use it in GitHub Desktop.
Handling translations in ClojureScript
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 front.utilities.i18n-impl | |
(:require [front.i18n])) | |
(defn build-domain-index [text-vec] | |
(let [by-msgid (atom (transient {})) | |
by-msg (atom (transient {}))] | |
(doseq [{:keys [s_message s_message_id] :as text} text-vec] | |
(when-not (clojure.string/blank? s_message_id) | |
(swap! by-msgid assoc! s_message_id text)) | |
(when-not (clojure.string/blank? s_message) | |
(swap! by-msg assoc! s_message text))) | |
{:msgid->text (persistent! @by-msgid) | |
:msg->text (persistent! @by-msg)})) | |
(defn build-text-index [text-ary] | |
(let [text-vec (js->clj text-ary :keywordize-keys true) | |
domain->texts (group-by :s_domain text-vec)] | |
(into | |
{} | |
(for [[k v] domain->texts] | |
[k (build-domain-index v)])))) | |
(defn interpolate-vars [text-str args] | |
(reduce-kv | |
(fn [acc k v] | |
(cond | |
(keyword? k) | |
(.replace acc (str "{" (name k) "}") (str v)) | |
:else acc)) | |
text-str | |
args)) | |
(defn interpret-text [text msg args] | |
(let [count-arg (get args :count) | |
base-text-str | |
(cond | |
count-arg | |
(let [text-i18n (get text :texts_i18n)] | |
(case count-arg | |
;; FIXME - this assumes the locale uses Plural rule 1, like English. | |
;; See: https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals#List_of_Plural_Rules | |
1 (or (first text-i18n) (:s_message text) (first msg)) | |
(or (nth text-i18n 1) (:s_message_plural text) (last msg)))) | |
:else | |
(get-in | |
text | |
[:texts_i18n 0] | |
(get text :s_message (first msg))))] | |
(interpolate-vars (or base-text-str "") args))) | |
(defn get-text [text-index msg args] | |
(let [e (nth msg 0) | |
msg (if (qualified-keyword? e) | |
(subvec msg 1) | |
msg) | |
text-entry (cond | |
(qualified-keyword? e) | |
(get-in text-index [(namespace e) :msgid->text (name e)]) | |
:else | |
(get-in text-index ["" :msg->text e]))] | |
(interpret-text text-entry msg args))) | |
(defn create-translator [text-ary] | |
(let [text-index (build-text-index text-ary)] | |
(reify front.i18n/ITranslate | |
(-get-text [_ msg args] | |
(get-text text-index msg args))))) |
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 front.i18n | |
(:require | |
[cljs.env :as env] | |
[cljs.analyzer :as ana] | |
[cljs.tagged-literals :as tlit])) | |
;; Adapted from:: https://github.com/thheller/cljs-i18n-api/blob/master/src/cljs/i18n.clj | |
(def vec-conj (fnil conj [])) | |
(defmacro tr | |
"Macro that makes strings available for translation (added to icx_master as a compile step). | |
msg is a literal string or vector. | |
args are :key/value pairs (optional) to inject into the translated string | |
if the the argument count is uneven the last argument will be treated as a dynamic map | |
Examples: | |
Plain string. Use this form if no context/domain is needed to translate. | |
(tr \"Ok\") | |
A vector, where the first member is a qualified keyword. The namespace of the keyword | |
will be set as the domain, and the message_id will be the name, giving some context | |
to the translator. It will also be stable over time, even if the string (last arg) | |
changes. | |
(tr [:employee.pto-request/approve \"Approve\"]) | |
;; Pluralization, interpolation: (the :count keyword is special) | |
(tr [:common/pony-brag-message \"I have {count} pony\" \"I have {count} ponies\"] :count 2) | |
" | |
[msg & args] | |
(let [msg (if (or (string? msg) (not (seqable? msg))) | |
[msg] | |
msg) | |
_ (when-not | |
(every? | |
#(or (string? %1) (qualified-keyword? %1)) | |
msg) | |
(throw (ex-info "Only string literals or qualified keywords supported for the translation string." | |
{:translation-string msg}))) | |
kv-args | |
(if (even? (count args)) | |
args | |
(butlast args)) | |
map-arg | |
(when (odd? (count args)) | |
(last args)) | |
static-args | |
(-> (apply hash-map kv-args) | |
(cond-> | |
(map? map-arg) | |
(merge map-arg))) | |
current-ns | |
(-> &env :ns :name) | |
string-data | |
{:msg msg | |
:ns current-ns | |
:args (into [] (keys static-args)) | |
:line line | |
:column column}] | |
;; thheller recommends storing it like this (and not globally), | |
;; or it will break with caching enabled for the build. | |
(when env/*compiler* | |
(swap! env/*compiler* update-in [::ana/namespaces current-ns ::strings] vec-conj string-data)) | |
`(tr* | |
~msg | |
~(cond | |
(or (nil? map-arg) | |
(= map-arg static-args)) | |
static-args | |
(and (empty? static-args) | |
(some? map-arg)) | |
map-arg | |
:else | |
`(merge ~static-args ~map-arg))))) |
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 front.i18n | |
(:require-macros [front.i18n])) | |
(defprotocol ITranslate | |
(-get-text [this msg args])) | |
(defonce translate-ref (atom nil)) | |
(defn set-translator! [tx] | |
{:pre [(satisfies? ITranslate tx)]} | |
(reset! translate-ref tx)) | |
(defn tr* | |
"do not use directly, accesse via the `tr` macro" | |
[msg args] | |
(if-some [tx @translate-ref] | |
(-get-text tx msg args) | |
(some #(when (string? %1) %1) msg))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment