Merge branch 'develop'
This commit is contained in:
commit
d515763cda
35
README.md
35
README.md
|
@ -62,21 +62,21 @@ For example:
|
|||
(get-message :pipe "de-DE" "i18n" "ru")
|
||||
```
|
||||
|
||||
So how does this work? When one calls `(get-message token accept-language-header)`, how does it know where to find resources? The answer is that there are two dynamic variables:
|
||||
So how does this work? When one calls
|
||||
`(get-message token accept-language-header)`, how does it know where to find resources? The answer is that there is a `*config*` map, with (currently) two significant keys:
|
||||
|
||||
* `*resource-path*`, the default path within the resources space on which
|
||||
translation files will be sought. Initialised to `i18n`.
|
||||
* `*default-language*`, the language tag for the language to use when no
|
||||
* `:resource-path`, whose value should be a string representation of the default
|
||||
path within the resources space on which translation files will be sought. Initialised to `i18n`.
|
||||
* `:default-language`, the language tag for the language to use when no
|
||||
otherwise suitable language can be identified. Initialised to the default
|
||||
language of the runtime session, so this may well be different on your
|
||||
machine from someone elses running identical software.
|
||||
|
||||
Thus
|
||||
```clojure
|
||||
(binding [*resource-path* "language-files"
|
||||
*default-language* "en-CA"]
|
||||
(get-message :pipe "en-GB;q=0.9, fr-FR")
|
||||
)
|
||||
(binding [*config* {:resource-path "language-files"
|
||||
:default-language "en-CA"}]
|
||||
(get-message :pipe "en-GB;q=0.9, fr-FR"))
|
||||
```
|
||||
and
|
||||
```clojure
|
||||
|
@ -116,10 +116,27 @@ In this project you will find two very simple example files, which should give y
|
|||
|
||||
## Documentation
|
||||
|
||||
Documentation may be generated by running
|
||||
Documentation can be found here. It may be generated by running
|
||||
|
||||
lein codox
|
||||
|
||||
## Future direction
|
||||
|
||||
It's likely that in future configuration will be extended
|
||||
|
||||
1. To read per-language keys/messages from CSV files;
|
||||
2. To read per-language keys/messages from database tables;
|
||||
3. potentially, to read per-language keys/messages from other sources.
|
||||
|
||||
Pull requests implementing any of these things will be welcomed.
|
||||
|
||||
## Deprecated features
|
||||
|
||||
There are still two dynamic configuration variables, `*default-language*`
|
||||
and `*resource-path*`, but these are now superceded by the `*config*` map,
|
||||
which is extensible. Consequently, if you are using these configuration
|
||||
variables in production, you should bind `*config*` to `nil`.
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2017 Simon Brooke
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
(defproject org.clojars.simon_brooke/internationalisation "1.0.4"
|
||||
(defproject org.clojars.simon_brooke/internationalisation "1.0.5-SNAPSHOT"
|
||||
:cloverage {:output "docs/cloverage"
|
||||
:codecov? true
|
||||
:emma-xml? true}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
;;;; This is a British English translation file.
|
||||
|
||||
{:pipe "This is not a pipe"}
|
||||
{:pipe "This is not a pipe."}
|
||||
|
|
|
@ -22,13 +22,18 @@
|
|||
|
||||
(def ^:dynamic *resource-path*
|
||||
"The default path within the resources space on which translation files
|
||||
will be sought."
|
||||
will be sought. Deprecated, prefer `(:resource-path *config*)`."
|
||||
"i18n")
|
||||
|
||||
(def ^:dynamic *default-language*
|
||||
"The default language to seek."
|
||||
"The default language to seek. Deprecated, prefer `(:default-language *config*)`."
|
||||
(-> (locale/get-default) locale/to-language-tag))
|
||||
|
||||
(def ^:dynamic *config*
|
||||
"Extensible configuration for i18n."
|
||||
{:default-language (-> (locale/get-default) locale/to-language-tag)
|
||||
:resource-path "i18n"})
|
||||
|
||||
(def accept-language-grammar
|
||||
"Grammar for `Accept-Language` headers"
|
||||
"HEADER := SPECIFIER | SPECIFIERS;
|
||||
|
@ -119,7 +124,7 @@
|
|||
(try
|
||||
(slurp (io/resource name))
|
||||
(catch Exception _
|
||||
(timbre/error (str "Resource at " name " does not exist."))
|
||||
(timbre/warn (str "Resource at " name " does not exist."))
|
||||
nil)))
|
||||
|
||||
|
||||
|
@ -167,23 +172,31 @@
|
|||
Returns a map of message keys to strings; if no useable file is found, returns nil."
|
||||
{:doc/format :markdown}
|
||||
[^String accept-language-header ^String resource-path ^String default-locale]
|
||||
(let [file-path (first
|
||||
(remove
|
||||
nil?
|
||||
(let [file-paths (remove
|
||||
empty?
|
||||
(map
|
||||
#(find-language-file-name % resource-path)
|
||||
(acceptable-languages accept-language-header))))]
|
||||
(timbre/debug (str "Found i18n file at '" file-path "'"))
|
||||
(try
|
||||
(read-string
|
||||
(slurp-resource
|
||||
(or
|
||||
file-path
|
||||
(join java.io.File/separator
|
||||
(acceptable-languages accept-language-header)))
|
||||
default-path (join java.io.File/separator
|
||||
[resource-path
|
||||
(str default-locale ".edn")]))))
|
||||
(str default-locale ".edn")])
|
||||
paths (concat file-paths (list default-path))
|
||||
text (first
|
||||
(remove empty?
|
||||
(map
|
||||
slurp-resource
|
||||
paths)))]
|
||||
(if text
|
||||
(try
|
||||
(read-string text)
|
||||
(catch Exception any
|
||||
(timbre/error (str "Failed to load internationalisation because " (.getMessage any)))
|
||||
(timbre/error "Failed to load internationalisation because "
|
||||
(.getName (.getClass any))
|
||||
(.getMessage any))
|
||||
nil))
|
||||
;; else
|
||||
(doall
|
||||
(timbre/error "No valid i18n files found, not even default. Tried" paths)
|
||||
nil))))
|
||||
|
||||
(def get-messages
|
||||
|
@ -200,7 +213,8 @@
|
|||
|
||||
(def get-message
|
||||
"Return the message keyed by this `token` from the most acceptable messages collection
|
||||
we have given this `accept-language-header`.
|
||||
we have given this `accept-language-header`, if passed, or the current default language
|
||||
otherwise. If no message is found, return the token.
|
||||
|
||||
* `token` should be a clojure keyword identifying the message to be retrieved;
|
||||
* `accept-language-header` should be the value of an RFC2616 `Accept-Language` header;
|
||||
|
@ -209,8 +223,13 @@
|
|||
* `default-locale` should be a locale specifier to use if no acceptable locale can be
|
||||
identified."
|
||||
(fn ([^Keyword token ^String accept-language-header ^String resource-path ^String default-locale]
|
||||
((get-messages accept-language-header resource-path default-locale) token))
|
||||
(let [message (token (get-messages accept-language-header resource-path default-locale))]
|
||||
(or message (name token))))
|
||||
([^Keyword token ^String accept-language-header]
|
||||
(get-message token accept-language-header *resource-path* *default-language*))
|
||||
(get-message token
|
||||
accept-language-header
|
||||
(or (:resource-path *config*) *resource-path*)
|
||||
(or (:default-language *config*) *default-language*)))
|
||||
([^Keyword token]
|
||||
(get-message token nil *resource-path* *default-language*))))
|
||||
(get-message token
|
||||
(or (:default-language *config*) *default-language*)))))
|
|
@ -1,7 +1,8 @@
|
|||
(ns ^{:doc "Tests for Internationalisation."
|
||||
:author "Simon Brooke"} scot.weft.i18n.test.core
|
||||
(:require [clojure.test :refer [deftest is testing]]
|
||||
[scot.weft.i18n.core :refer [*default-language*
|
||||
[scot.weft.i18n.core :refer [*config*
|
||||
*default-language*
|
||||
acceptable-languages
|
||||
generate-accept-languages
|
||||
get-message
|
||||
|
@ -206,7 +207,7 @@
|
|||
(testing "Top level functionality"
|
||||
(is
|
||||
(=
|
||||
"This is not a pipe"
|
||||
"This is not a pipe."
|
||||
(:pipe (get-messages "en-GB, fr-FR;q=0.9" "i18n" "en-GB"))))
|
||||
(is
|
||||
(=
|
||||
|
@ -215,9 +216,25 @@
|
|||
(is
|
||||
(= nil (get-messages "xx-XX;q=0.5, yy-YY" "i18n" "zz-ZZ"))
|
||||
"If no usable file is found, an exception should not be thrown.")
|
||||
(binding [*default-language* "en-GB"]
|
||||
(is (= "This is not a pipe" (get-message :pipe)))
|
||||
(binding [*config* (assoc *config* :default-language "fr-FR")]
|
||||
(is (= "Ceci n'est pas une pipe." (get-message :pipe)))
|
||||
(is
|
||||
(=
|
||||
"Ceci n'est pas une pipe." (get-message :pipe "en-GB;q=0.9, fr-FR")))
|
||||
(is (= "это не труба." (get-message :pipe "de-DE" "i18n" "ru"))))))
|
||||
"This is not a pipe." (get-message :pipe "en-GB, fr-FR;q=0.9")))
|
||||
(is (= "это не труба." (get-message :pipe "de-DE" "i18n" "ru")))
|
||||
(is (= "froboz" (get-message :froboz)))))
|
||||
(testing "Final fall through if no suitable language found"
|
||||
(binding [*config* (assoc *config* :default-language "de-DE")]
|
||||
;; there is no 'de-DE' language resource in the resources,
|
||||
;; and that's exactly why we've chosen it for this test.
|
||||
(is (= "pipe" (get-message :pipe)))))
|
||||
(testing "Deprecated variables still work"
|
||||
(binding [*config* nil
|
||||
*default-language* "en-GB"]
|
||||
(is (= "This is not a pipe." (get-message :pipe)))
|
||||
(is
|
||||
(= "Ceci n'est pas une pipe."
|
||||
(get-message :pipe "en-GB;q=0.9, fr-FR"))))
|
||||
(binding [*config* nil
|
||||
*default-language* "ru"]
|
||||
(is (= "это не труба." (get-message :pipe))))))
|
||||
|
|
Loading…
Reference in a new issue