Utilities to write multilingual Clojure programs, vaguely inspired by GNU gettext.
To install gettext, add the following to your project map as a dependency:
[gettext "0.1.1"]The first step is to mark some strings within your program as being translatable,
by using the gettext function or its shorter alias _:
(ns myprogram.core
(:require [gettext.core :refer [_]]))
(println (_ "Hello, world!"))Note gettext wraps format so
you can pass replacement arguments to the call:
(println (_ "Hello, %s!" "Facundo"))Once the strings are marked for translation, a dictionary is needed to map them to a specific translation:
(ns myprogram.translations.spanish)
(def dictionary {"Hello, world!" "Hola, mundo!"
"Hello, %s" "Hola, %s"})The dictionary to use for translations can be set statically in the resources/config.clj file.
The file should consist of a map with the :gettext-source:
{:gettext-source 'myprogram.translations.spanish/dictionary}Alternatively, the translations dictionary can be bound dynamically using
with-bindings:
(println (_ "Hello, world!")) ; Will use the translation set at project.clj or return the key if none set
(with-bindings {#'gettext.core/*text-source* myprogram.translations.german/dictionary}
(println (_ "Hello, world!"))) ; Will print the german translation instead
When a key is not found in the translations dictionary, or if no dictionary
has been set, the result of the gettext call will be the key string itself.
There are cases when the original string is not enough to properly translate it
to another language, for example, when working with plural forms or with gender.
In those cases pgettext or its alias p_ can be used.
pgettext takes an arbitrary context value as its first argument:
(ns myprogram.core
(:require [gettext.core :refer [p_]]))
(println (p_ {:gender :male} "%s is my friend." "John"))
(println (p_ {:gender :female} "%s is my friend." "Jane"))When defining the translations dictionary, the key string can be mapped to a function that will take the context and decide on the translation:
(ns myprogram.translations.spanish)
(def dictionary {"Hello, world!" "Hola, mundo!"
"Hello, %s" "Hola, %s"
"%s is my friend." #(if (= (:gender %) :female) "%s es mi amiga." "%s es mi amigo.") })pgettext can also be useful even when using a single language, to decouple grammar
logic from app specific logic:
(ns myprogram.translations.english)
(defn starts-with-vowel
[ctx]
(let [vowel? (set "aeiouAEIOU")]
(vowel? (first ctx))))
(def dictionary {"I'm carrying a %" #(if (starts-with-vowel %)
"I'm carrying an %s"
"I'm carrying a %s"))})The function gettext.scan/scan-files takes a clojure file or directory and
extracts all strings that are passed to gettext in any of its flavors. The
strings are packed in a map to facilitate their translation:
(require '[gettext.scan :refer [scan-files]])
(scan-files "/Users/facundo/dev/advenjure/src/advenjure")
; Returns
; {"%s was closed." "%s was closed.",
; "%s was empty." "%s was empty.",
; "%s what?" "%s what?",
; "Bye!" "Bye!",
; "Closed." "Closed."
; ...}Copyright © 2016 Facundo Olano
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.