Closed cjohansen closed 3 years ago
Having thought some more on this, I guess what I'm asking for is that instead of tongue's translate
only ever returning strings, it would allow dictionaries to hold arbitrary data structures, and walk them to translate any strings within.
In the current implementation, all the internals are private, thus compiling tongue's building blocks on the inside to achieve this is not possible (as far as I can tell). I can see at least two ways to solve this, and would gladly PR either, maybe you have other suggestions as well:
str/replace
in tongue.core/translate
with something that accepts any data structure, walks it and performs the string replace on strings within. For string values this will not incur much more overhead than an extra if
. This would make arbitrary data as dictionary values a tongue feature.str/replace
calls in tongue.core/translate
with something of an extension point, and making translate
public - allowing users to bolt on this functionality on their own.I’m not big fan of a walking arbitrary data structures. It might go into places you don’t want it to go.
There’s another issue similar to yours: https://github.com/tonsky/tongue/issues/2
I’ll think more about what could be done. Seems like a useful feature, I even had to work around it once already too.
I think supporting this would be genuinely useful, but providing an extension point would make it easy to offer as an opt-in feature.
I imagine something like this would work (not really tested, just sketched real fast):
(defn interpolate-string [s dicts locale args]
(str/replace s #"\{(\d+)\}" (fn [[_ n]]
(let [idx (dec (parse-long n))
arg (nth args idx)]
(format-argument dicts locale arg)))))
(defmulti interpolate (fn [v dicts locale args] (type v)))
(defmethod interpolate :default [s dicts locale args] (interpolate-string s dicts locale args))
(defn translate [dicts locale key & args]
(let [t (lookup-template dicts locale key)
v (if (fn? t) (apply t args) t)]
(interpolate v dicts locale args)))
This would provide an extension point for consumers to allow the data types they care about. The specs would have to be adjusted for this though.
Hi, how about solving this by passing function as a translation which can return arbitrary Clojure data. The example above would be:
:email/footer (fn [url] [:p "See our mega cool site " [:a {:href url} "over here!"]])
Only when a string is returned, parameters in {} are replaced. It currently works fine but it breaks the spec which we don't use. If one want to use parameter replacement inside a string, one could always call the replacement function from tongue directly.
Hi there 👋 I came back to discuss this only to find my own issue from 4 years ago 😄
Tongue currently enforces translation maps to only contain strings. I really want it to contain hiccup. Suggested solution: introduce an interpolation protocol. Make the current interpolation implementation the protocol implementation for String, and do nothing else. This way Tongue can be extended to translate non-string values, without Tongue offering anything other than string translations out of the box.
@tonsky If you think this is an acceptable solution, I will gladly code it up in a PR.
So this is what we’ll end up with for dictionary definition:
Looks good, let’s do this!
Just 4 years after the original issue 🙈
This is a question more than an issue, please let me know if there's a better place to do this.
Working with react-like components it would sometimes be useful if translation keys could contain "markup":
Poor example, but links are the typical use case: you sometimes want to place them in sentences, but breaking the sentence into three parts for i18n is awkward and inflexible across languages.
I was thinking maybe one could device a solution with
clojure.walk
, but quickly realized I would need to make recursive calls to the translating function, and inside the custom functions you no longer have access to the locale...Another problem with this approach is that Tongue's specs aren't thrilled with it.
Any thoughts on this? Feels like it would need some help from tongue to pull this through, but maybe there's another way I haven't thought of.