diff --git a/README.md b/README.md index 3e18d80..309c761 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A Clojure library designed to provide simple interationalisation of user-facing To use this library in your project, add the following leiningen dependency: - [org.clojars.simon_brooke/internationalisation "1.0.5"] + [org.clojars.simon_brooke/internationalisation "1.0.4"] To use it in your namespace, require: @@ -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 is a `*config*` map, with (currently) two significant keys: +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: -* `: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 +* `*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 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 [*config* {:resource-path "language-files" - :default-language "en-CA"}] - (get-message :pipe "en-GB;q=0.9, fr-FR")) +(binding [*resource-path* "language-files" + *default-language* "en-CA"] + (get-message :pipe "en-GB;q=0.9, fr-FR") +) ``` and ```clojure @@ -116,27 +116,10 @@ In this project you will find two very simple example files, which should give y ## Documentation -Documentation can be found here. It may be generated by running +Documentation 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 diff --git a/doc/intro.md b/doc/intro.md index 3e18d80..309c761 100644 --- a/doc/intro.md +++ b/doc/intro.md @@ -6,7 +6,7 @@ A Clojure library designed to provide simple interationalisation of user-facing To use this library in your project, add the following leiningen dependency: - [org.clojars.simon_brooke/internationalisation "1.0.5"] + [org.clojars.simon_brooke/internationalisation "1.0.4"] To use it in your namespace, require: @@ -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 is a `*config*` map, with (currently) two significant keys: +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: -* `: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 +* `*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 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 [*config* {:resource-path "language-files" - :default-language "en-CA"}] - (get-message :pipe "en-GB;q=0.9, fr-FR")) +(binding [*resource-path* "language-files" + *default-language* "en-CA"] + (get-message :pipe "en-GB;q=0.9, fr-FR") +) ``` and ```clojure @@ -116,27 +116,10 @@ In this project you will find two very simple example files, which should give y ## Documentation -Documentation can be found here. It may be generated by running +Documentation 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 diff --git a/docs/cloverage/codecov.json b/docs/cloverage/codecov.json index bf9bb45..08624a5 100644 --- a/docs/cloverage/codecov.json +++ b/docs/cloverage/codecov.json @@ -2,19 +2,17 @@ {"scot/weft/i18n/core.clj": [null, 1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 1, - null, null, null, null, 1, null, 1, null, 1, null, 1, null, null, 1, - null, null, null, null, null, null, null, null, null, null, null, - null, 1, null, 1, null, 1, null, null, null, null, null, true, 159, - null, true, 159, 11, 22, 22, 22, 9, null, 24, 24, null, 24, 24, 10, - null, 25, 25, 20, 20, null, 20, 5, 26, 20, 20, 0, 0, null, 0, 20, - 20, null, 11, null, 0, 0, null, null, 1, null, null, null, null, - null, null, null, null, 10, 10, 10, 10, null, 10, 10, 0, null, null, - 1, null, null, null, 24, 24, null, 11, null, null, null, 1, null, - null, null, null, null, null, null, null, null, null, 25, 25, 15, - 15, 15, 25, 25, 25, 6, 19, 10, 10, 10, 10, 5, 5, 5, null, null, 1, - null, null, null, null, null, null, null, null, null, null, null, - null, null, 9, 9, 9, 10, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, true, 7, - null, 0, 0, 0, null, null, 2, 2, null, null, 1, null, null, null, - null, null, null, null, null, null, 1, null, 1, null, null, null, - null, null, null, null, null, null, null, 1, 8, 8, null, 7, 7, 7, 7, - null, 5, 5]}} + null, null, null, null, 1, null, 1, null, 1, null, null, null, null, + null, null, null, null, null, null, null, null, 1, null, 1, null, 1, + null, null, null, null, null, true, 119, null, true, 119, 6, 16, 16, + 16, 8, null, 18, 18, null, 18, 18, 9, null, 19, 19, 15, 15, null, + 15, 4, 20, 15, 15, 0, 0, null, 0, 15, 15, null, 10, null, 0, 0, + null, null, 1, null, null, null, null, null, null, null, null, 6, 6, + 5, 5, null, 5, 5, 1, null, null, 1, null, null, null, 13, 13, null, + 7, null, null, null, 1, null, null, null, null, null, null, null, + null, null, null, 13, 13, 8, 8, 8, 13, 13, 13, 2, 11, 5, 5, 5, 5, 3, + 3, 3, null, null, 1, null, null, null, null, null, null, null, null, + null, null, null, null, null, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, + 3, 3, null, 1, null, null, 1, null, null, null, null, null, null, + null, null, null, 1, null, 1, null, null, null, null, null, null, + null, null, null, 1, 3, null, 1, null, 1]}} diff --git a/docs/cloverage/coverage.xml b/docs/cloverage/coverage.xml index 1412116..51eff4d 100644 --- a/docs/cloverage/coverage.xml +++ b/docs/cloverage/coverage.xml @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/cloverage/index.html b/docs/cloverage/index.html index 8e6bf1b..7592895 100644 --- a/docs/cloverage/index.html +++ b/docs/cloverage/index.html @@ -16,26 +16,26 @@ scot.weft.i18n.core
338
75
-81.84 % + style="width:90.73569482288828%; + float:left;"> 333
34
+90.74 %
94
3
9
-91.51 % -23524106 + style="width:92.47311827956989%; + float:left;"> 86
2
5
+94.62 % +2162393 Totals: -81.84 % +90.74 % -91.51 % +94.62 % diff --git a/docs/cloverage/scot/weft/i18n/core.clj.html b/docs/cloverage/scot/weft/i18n/core.clj.html index 8554b08..01c1586 100644 --- a/docs/cloverage/scot/weft/i18n/core.clj.html +++ b/docs/cloverage/scot/weft/i18n/core.clj.html @@ -77,7 +77,7 @@ 024    "The default path within the resources space on which translation files 
- 025     will be sought. Deprecated, prefer `(:resource-path *config*)`." + 025     will be sought."
026    "i18n") @@ -89,7 +89,7 @@ 028  (def ^:dynamic *default-language*
- 029    "The default language to seek. Deprecated, prefer `(:default-language *config*)`." + 029    "The default language to seek."
030    (-> (locale/get-default) locale/to-language-tag)) @@ -97,617 +97,560 @@ 031  
- - 032  (def ^:dynamic *config* -
- - 033    "Extensible configuration for i18n." -
- - 034    {:default-language (-> (locale/get-default) locale/to-language-tag) -
- - 035     :resource-path "i18n"}) -
- - 036   -
- 037  (def accept-language-grammar + 032  (def accept-language-grammar
- 038    "Grammar for `Accept-Language` headers" + 033    "Grammar for `Accept-Language` headers"
- 039    "HEADER := SPECIFIER | SPECIFIERS; + 034    "HEADER := SPECIFIER | SPECIFIERS;
- 040    SPECIFIERS:= SPECIFIER | SPECIFIER SPEC-SEP SPECIFIERS; + 035    SPECIFIERS:= SPECIFIER | SPECIFIER SPEC-SEP SPECIFIERS;
- 041    SPEC-SEP := #',\\s*'; + 036    SPEC-SEP := #',\\s*';
- 042    SPECIFIER := LANGUAGE-TAG | LANGUAGE-TAG Q-SEP Q-VALUE; + 037    SPECIFIER := LANGUAGE-TAG | LANGUAGE-TAG Q-SEP Q-VALUE;
- 043    LANGUAGE-TAG := PRIMARY-TAG | PRIMARY-TAG '-' SUB-TAGS; + 038    LANGUAGE-TAG := PRIMARY-TAG | PRIMARY-TAG '-' SUB-TAGS;
- 044    PRIMARY-TAG := #'[a-zA-Z]+'; + 039    PRIMARY-TAG := #'[a-zA-Z]+';
- 045    SUB-TAGS := SUB-TAG | SUB-TAG '-' SUB-TAGS; + 040    SUB-TAGS := SUB-TAG | SUB-TAG '-' SUB-TAGS;
- 046    SUB-TAG := #'[a-zA-Z0-9]+'; + 041    SUB-TAG := #'[a-zA-Z0-9]+';
- 047    Q-SEP := #';\\s*q=' + 042    Q-SEP := #';\\s*q='
- 048    Q-VALUE := '1' | #'0.[0-9]+';") + 043    Q-VALUE := '1' | #'0.[0-9]+';")
- 049   + 044  
- 050  (def parse-accept-language-header + 045  (def parse-accept-language-header
- 051    "Parse an `Accept-Language` header" + 046    "Parse an `Accept-Language` header"
- 052    (insta/parser accept-language-grammar)) + 047    (insta/parser accept-language-grammar))
- 053   + 048  
- 054  (defn generate-accept-languages + 049  (defn generate-accept-languages
- 055    "From a `parse-tree` generated by the `language-specifier-grammar`, generate + 050    "From a `parse-tree` generated by the `language-specifier-grammar`, generate
- 056    a list of maps each having a `:language` key, a `:preference` key and a + 051    a list of maps each having a `:language` key, a `:preference` key and a
- 057    `:qualifier` key." + 052    `:qualifier` key."
- 058    {:doc/format :markdown} + 053    {:doc/format :markdown}
- 059    [parse-tree] + 054    [parse-tree]
- 060    (if + 055    (if
- 061     (nil? parse-tree) + 056     (nil? parse-tree)
- 062      nil + 057      nil
- 063      (case + 058      (case
- 064       (first parse-tree) + 059       (first parse-tree)
- 065        :HEADER (generate-accept-languages (second parse-tree)) + 060        :HEADER (generate-accept-languages (second parse-tree))
- 066        :SPECIFIERS (cons + 061        :SPECIFIERS (cons
- 067                     (generate-accept-languages (second parse-tree)) + 062                     (generate-accept-languages (second parse-tree))
- 068                     (when (>= (count parse-tree) 3) + 063                     (when (>= (count parse-tree) 3)
- 069                       (generate-accept-languages (nth parse-tree 3)))) + 064                       (generate-accept-languages (nth parse-tree 3))))
- 070        :SPEC-SEP nil + 065        :SPEC-SEP nil
- 071        :SPECIFIER (assoc + 066        :SPECIFIER (assoc
- 072                    (generate-accept-languages (second parse-tree)) + 067                    (generate-accept-languages (second parse-tree))
- 073                    :preference + 068                    :preference
- 074                    (if + 069                    (if
- 075                     (>= (count parse-tree) 3) + 070                     (>= (count parse-tree) 3)
- 076                      (generate-accept-languages (nth parse-tree 3)) + 071                      (generate-accept-languages (nth parse-tree 3))
- 077                      1)) + 072                      1))
- 078        :LANGUAGE-TAG (if + 073        :LANGUAGE-TAG (if
- 079                       (>= (count parse-tree) 3) + 074                       (>= (count parse-tree) 3)
- 080                        (assoc + 075                        (assoc
- 081                         (generate-accept-languages (second parse-tree)) + 076                         (generate-accept-languages (second parse-tree))
- 082                         :qualifier + 077                         :qualifier
- 083                         (generate-accept-languages (nth parse-tree 3))) + 078                         (generate-accept-languages (nth parse-tree 3)))
- 084                        (generate-accept-languages (second parse-tree))) + 079                        (generate-accept-languages (second parse-tree)))
- 085        :PRIMARY-TAG {:language (second parse-tree) :qualifier "*"} + 080        :PRIMARY-TAG {:language (second parse-tree) :qualifier "*"}
- 086        :SUB-TAGS (if + 081        :SUB-TAGS (if
- 087                   (>= (count parse-tree) 3) + 082                   (>= (count parse-tree) 3)
- 088                    (str + 083                    (str
- 089                     (generate-accept-languages (second parse-tree)) + 084                     (generate-accept-languages (second parse-tree))
- 090                     "-" + 085                     "-"
- 091                     (generate-accept-languages (nth parse-tree 3))) + 086                     (generate-accept-languages (nth parse-tree 3)))
- 092                    (generate-accept-languages (second parse-tree))) + 087                    (generate-accept-languages (second parse-tree)))
- 093        :SUB-TAG (second parse-tree) + 088        :SUB-TAG (second parse-tree)
- 094        :Q-SEP nil + 089        :Q-SEP nil
- 095        :Q-VALUE (read-string (second parse-tree)) + 090        :Q-VALUE (read-string (second parse-tree))
- 096        ;; default + 091        ;; default
- 097        (do + 092        (do
- 098          (timbre/error "Unable to parse header.") + 093          (timbre/error "Unable to parse header.")
- 099          nil)))) + 094          nil)))) +
+ + 095   +
+ + 096  (defn acceptable-languages +
+ + 097    "Generate an ordered list of acceptable languages, most-preferred first. +
+ + 098   +
+ + 099    * `accept-language-header` should be the value of an RFC2616 `Accept-Language` header.
100  
- - 101  (defn acceptable-languages + + 101    Returns a list of maps as generated by `generate-accept-languages`, in descending order
- 102    "Generate an ordered list of acceptable languages, most-preferred first. -
- - 103   + 102    of preference."
- 104    * `accept-language-header` should be the value of an RFC2616 `Accept-Language` header. -
- - 105   + 103    {:doc/format :markdown}
- 106    Returns a list of maps as generated by `generate-accept-languages`, in descending order -
- - 107    of preference." -
- - 108    {:doc/format :markdown} -
- - 109    [accept-language-header] + 104    [accept-language-header]
- 110    (let [parse-tree (parse-accept-language-header accept-language-header)] + 105    (let [parse-tree (parse-accept-language-header accept-language-header)]
- 111      (if (vector? parse-tree) + 106      (if (vector? parse-tree)
- 112        (reverse + 107        (reverse
- 113         (sort-by + 108         (sort-by
- 114          :preference + 109          :preference
- 115          (generate-accept-languages + 110          (generate-accept-languages
- 116           parse-tree))) + 111           parse-tree)))
- - 117        (timbre/error "Failed to parse Accept-Language header '" accept-language-header "':\n" (str parse-tree))))) + + 112        (timbre/error "Failed to parse Accept-Language header '" accept-language-header "':\n" (str parse-tree)))))
- 118   + 113  
- 119   + 114  
- 120  (defn slurp-resource + 115  (defn slurp-resource
- 121    "Slurp the resource of this name and return its contents as a string; but if it doesn't + 116    "Slurp the resource of this name and return its contents as a string; but if it doesn't
- 122     exist log the fact and return nil, rather than throwing an exception." + 117     exist log the fact and return nil, rather than throwing an exception."
- 123    [name] + 118    [name]
- 124    (try + 119    (try
- 125      (slurp (io/resource name)) + 120      (slurp (io/resource name))
- 126      (catch Exception _ + 121      (catch Exception _
- 127        (timbre/warn (str "Resource at " name " does not exist.")) + 122        (timbre/error (str "Resource at " name " does not exist."))
- 128        nil))) + 123        nil)))
- 129   + 124  
- 130   + 125  
- 131  (defn find-language-file-name + 126  (defn find-language-file-name
- 132    "Find the name of a messages file on this resource path which matches this `language-spec`. + 127    "Find the name of a messages file on this resource path which matches this `language-spec`. +
+ + 128   +
+ + 129    * `language-spec` should be either a map as generated by `generate-accept-languages`, or +
+ + 130    else a string; +
+ + 131    * `resource-path` should be the path name of the directory in which message files are stored, +
+ + 132    within the resources on the classpath.
133  
- 134    * `language-spec` should be either a map as generated by `generate-accept-languages`, or + 134    Returns the name of an appropriate file if any is found, else nil."
- 135    else a string; + 135    {:doc/format :markdown}
- 136    * `resource-path` should be the path name of the directory in which message files are stored, -
- - 137    within the resources on the classpath. -
- - 138   -
- - 139    Returns the name of an appropriate file if any is found, else nil." -
- - 140    {:doc/format :markdown} -
- - 141    [language-spec resource-path] + 136    [language-spec resource-path]
- 142    (let [file-path (when + 137    (let [file-path (when
- 143                     (string? language-spec) + 138                     (string? language-spec)
- 144                      (join + 139                      (join
- 145                       java.io.File/separator + 140                       java.io.File/separator
- 146                       [resource-path (str language-spec ".edn")])) + 141                       [resource-path (str language-spec ".edn")]))
- 147          contents (when file-path (slurp-resource file-path))] + 142          contents (when file-path (slurp-resource file-path))]
- 148      (cond + 143      (cond
- 149        contents + 144        contents
- 150        file-path + 145        file-path
- 151        (map? language-spec) + 146        (map? language-spec)
- 152        (or + 147        (or
- 153         (find-language-file-name + 148         (find-language-file-name
- 154          (str (:language language-spec) "-" (:qualifier language-spec)) + 149          (str (:language language-spec) "-" (:qualifier language-spec))
- 155          resource-path) + 150          resource-path)
- 156         (find-language-file-name + 151         (find-language-file-name
- 157          (:language language-spec) + 152          (:language language-spec)
- 158          resource-path))))) + 153          resource-path)))))
- 159   + 154   +
+ + 155   +
+ + 156  (defn raw-get-messages +
+ + 157    "Return the most acceptable messages collection we have given this `accept-language-header`. +
+ + 158    Do not use this function directly, use the memoized variant `get-messages`, as performance +
+ + 159    will be very much better.
160  
- - 161  (defn raw-get-messages + + 161    * `accept-language-header` should be the value of an RFC2616 `Accept-Language` header;
- 162    "Return the most acceptable messages collection we have given this `accept-language-header`. + 162    * `resource-path` should be the fully-qualified path name of the directory in which
- 163    Do not use this function directly, use the memoized variant `get-messages`, as performance + 163    message files are stored;
- 164    will be very much better. + 164    * `default-locale` should be a locale specifier to use if no acceptable locale can be +
+ + 165    identified.
- 165   + 166  
- 166    * `accept-language-header` should be the value of an RFC2616 `Accept-Language` header; + 167    Returns a map of message keys to strings; if no useable file is found, returns nil."
- 167    * `resource-path` should be the fully-qualified path name of the directory in which + 168    {:doc/format :markdown}
- 168    message files are stored; -
- - 169    * `default-locale` should be a locale specifier to use if no acceptable locale can be -
- - 170    identified. -
- - 171   -
- - 172    Returns a map of message keys to strings; if no useable file is found, returns nil." -
- - 173    {:doc/format :markdown} -
- - 174    [^String accept-language-header ^String resource-path ^String default-locale] + 169    [^String accept-language-header ^String resource-path ^String default-locale]
- 175    (let [file-paths (remove -
- - 176                      empty? -
- - 177                      (map -
- - 178                       #(find-language-file-name % resource-path) -
- - 179                       (acceptable-languages accept-language-header))) -
- - 180          default-path (join java.io.File/separator + 170    (let [file-path (first
- 181                 [resource-path + 171                     (remove +
+ + 172                      nil? +
+ + 173                      (map
- 182                  (str default-locale ".edn")]) + 174                       #(find-language-file-name % resource-path) +
+ + 175                       (acceptable-languages accept-language-header))))] +
+ + 176      (timbre/debug (str "Found i18n file at '" file-path "'")) +
+ + 177      (try +
+ + 178        (read-string +
+ + 179         (slurp-resource +
+ + 180          (or +
+ + 181           file-path +
+ + 182           (join java.io.File/separator +
+ + 183                 [resource-path +
+ + 184                  (str default-locale ".edn")])))) +
+ + 185        (catch Exception any +
+ + 186          (timbre/error (str "Failed to load internationalisation because " (.getMessage any))) +
+ + 187          nil)))) +
+ + 188   +
+ + 189  (def get-messages +
+ + 190    "Return the most acceptable messages collection we have given this `accept-language-header` +
+ + 191   +
+ + 192    * `accept-language-header` should be the value of an RFC2616 `Accept-Language` header; +
+ + 193    * `resource-path` should be the fully-qualified path name of the directory in which +
+ + 194    message files are stored; +
+ + 195    * `default-locale` should be a locale specifier to use if no acceptable locale can be +
+ + 196    identified. +
+ + 197   +
+ + 198    Returns a map of message keys to strings.; if no useable file is found, returns nil." +
+ + 199    (memoize raw-get-messages)) +
+ + 200   +
+ + 201  (def get-message +
+ + 202    "Return the message keyed by this `token` from the most acceptable messages collection   +
+ + 203     we have given this `accept-language-header`. +
+ + 204      +
+ + 205     * `token` should be a clojure keyword identifying the message to be retrieved; +
+ + 206     * `accept-language-header` should be the value of an RFC2616 `Accept-Language` header; +
+ + 207     * `resource-path` should be the fully-qualified path name of the directory in which +
+ + 208       message files are stored; +
+ + 209     * `default-locale` should be a locale specifier to use if no acceptable locale can be +
+ + 210       identified." +
+ + 211    (fn ([^Keyword token ^String accept-language-header ^String resource-path ^String default-locale] +
+ + 212         ((get-messages accept-language-header resource-path default-locale) token)) +
+ + 213      ([^Keyword token ^String accept-language-header]
- 183          paths (concat file-paths (list default-path)) -
- - 184          text (first  -
- - 185                (remove empty? -
- - 186                       (map -
- - 187                        slurp-resource -
- - 188                        paths)))] -
- - 189      (if text -
- - 190        (try -
- - 191          (read-string text) + 214       (get-message token accept-language-header *resource-path* *default-language*))
- 192          (catch Exception any + 215      ([^Keyword token]
- - 193            (timbre/error  "Failed to load internationalisation because " -
- - 194                           (.getName (.getClass any)) -
- - 195                           (.getMessage any)) -
- - 196            nil)) -
- - 197        ;; else -
- - 198        (doall -
- - 199          (timbre/error "No valid i18n files found, not even default. Tried" paths) -
- - 200          nil)))) -
- - 201   -
- - 202  (def get-messages -
- - 203    "Return the most acceptable messages collection we have given this `accept-language-header` -
- - 204   -
- - 205    * `accept-language-header` should be the value of an RFC2616 `Accept-Language` header; -
- - 206    * `resource-path` should be the fully-qualified path name of the directory in which -
- - 207    message files are stored; -
- - 208    * `default-locale` should be a locale specifier to use if no acceptable locale can be -
- - 209    identified. -
- - 210   -
- - 211    Returns a map of message keys to strings.; if no useable file is found, returns nil." -
- - 212    (memoize raw-get-messages)) -
- - 213   -
- - 214  (def get-message -
- - 215    "Return the message keyed by this `token` from the most acceptable messages collection   -
- - 216     we have given this `accept-language-header`, if passed, or the current default language  -
- - 217     otherwise. If no message is found, return the token. -
- - 218      -
- - 219     * `token` should be a clojure keyword identifying the message to be retrieved; -
- - 220     * `accept-language-header` should be the value of an RFC2616 `Accept-Language` header; -
- - 221     * `resource-path` should be the fully-qualified path name of the directory in which -
- - 222       message files are stored; -
- - 223     * `default-locale` should be a locale specifier to use if no acceptable locale can be -
- - 224       identified." -
- - 225    (fn ([^Keyword token ^String accept-language-header ^String resource-path ^String default-locale] -
- - 226         (let [message (token (get-messages accept-language-header resource-path default-locale))] -
- - 227           (or message (name token)))) -
- - 228      ([^Keyword token ^String accept-language-header] -
- - 229       (get-message token  -
- - 230                    accept-language-header  -
- - 231                    (or (:resource-path *config*) *resource-path*)  -
- - 232                    (or (:default-language *config*) *default-language*))) -
- - 233      ([^Keyword token] -
- - 234       (get-message token  -
- - 235                    (or (:default-language *config*) *default-language*))))) + + 216       (get-message token nil *resource-path* *default-language*))))
diff --git a/docs/codox/index.html b/docs/codox/index.html index 95276b2..9250ac2 100644 --- a/docs/codox/index.html +++ b/docs/codox/index.html @@ -1,3 +1,3 @@ -Internationalisation 1.0.5

Internationalisation 1.0.5

Released under the Eclipse Public License

Internationalisation library for Clojure.

Installation

To install, add the following dependency to your project or build file:

[org.clojars.simon_brooke/internationalisation "1.0.5"]

Topics

Namespaces

\ No newline at end of file +Internationalisation 1.0.3-SNAPSHOT

Internationalisation 1.0.3-SNAPSHOT

Released under the Eclipse Public License

Internationalisation library for Clojure.

Installation

To install, add the following dependency to your project or build file:

[org.clojars.simon_brooke/internationalisation "1.0.3-SNAPSHOT"]

Topics

Namespaces

\ No newline at end of file diff --git a/docs/codox/intro.html b/docs/codox/intro.html index 8fa9f1c..a580553 100644 --- a/docs/codox/intro.html +++ b/docs/codox/intro.html @@ -1,10 +1,10 @@ -internationalisation

internationalisation

+internationalisation

internationalisation

A Clojure library designed to provide simple interationalisation of user-facing messages.

Usage

To use this library in your project, add the following leiningen dependency:

-
[org.clojars.simon_brooke/internationalisation "1.0.5"]
+
[org.clojars.simon_brooke/internationalisation "1.0.4"]
 

To use it in your namespace, require:

[scot.weft.i18n.core :refer [get-message get-messages]]
@@ -45,15 +45,16 @@
 
 (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 is a *config* map, with (currently) two significant keys:

+

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:

    -
  • :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.
  • +
  • *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 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

-
(binding [*config* {:resource-path "language-files"
-                    :default-language "en-CA"}]
-    (get-message :pipe "en-GB;q=0.9, fr-FR"))
+
(binding [*resource-path* "language-files"
+          *default-language* "en-CA"]
+    (get-message :pipe "en-GB;q=0.9, fr-FR")
+)
 

and

(get-message :pipe "en-GB;q=0.9, fr-FR" "language-files" "en-CA")
@@ -78,19 +79,9 @@
 {:pipe "Ceci n'est pas une pipe."}
 

Documentation

-

Documentation can be found here. It may be generated by running

+

Documentation 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. -
  3. To read per-language keys/messages from database tables;
  4. -
  5. potentially, to read per-language keys/messages from other sources.
  6. -
-

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

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

\ No newline at end of file diff --git a/docs/codox/scot.weft.i18n.core.html b/docs/codox/scot.weft.i18n.core.html index d0ca178..085edec 100644 --- a/docs/codox/scot.weft.i18n.core.html +++ b/docs/codox/scot.weft.i18n.core.html @@ -1,30 +1,30 @@ -scot.weft.i18n.core documentation

scot.weft.i18n.core

Internationalisation.

*config*

dynamic

Extensible configuration for i18n.

*default-language*

dynamic

The default language to seek. Deprecated, prefer (:default-language *config*).

*resource-path*

dynamic

The default path within the resources space on which translation files will be sought. Deprecated, prefer (:resource-path *config*).

accept-language-grammar

Grammar for Accept-Language headers

acceptable-languages

(acceptable-languages accept-language-header)

Generate an ordered list of acceptable languages, most-preferred first.

+scot.weft.i18n.core documentation

scot.weft.i18n.core

Internationalisation.

*default-language*

dynamic

The default language to seek.

*resource-path*

dynamic

The default path within the resources space on which translation files will be sought.

accept-language-grammar

Grammar for Accept-Language headers

acceptable-languages

(acceptable-languages accept-language-header)

Generate an ordered list of acceptable languages, most-preferred first.

  • accept-language-header should be the value of an RFC2616 Accept-Language header.
-

Returns a list of maps as generated by generate-accept-languages, in descending order of preference.

find-language-file-name

(find-language-file-name language-spec resource-path)

Find the name of a messages file on this resource path which matches this language-spec.

+

Returns a list of maps as generated by generate-accept-languages, in descending order of preference.

find-language-file-name

(find-language-file-name language-spec resource-path)

Find the name of a messages file on this resource path which matches this language-spec.

  • language-spec should be either a map as generated by generate-accept-languages, or else a string;
  • resource-path should be the path name of the directory in which message files are stored, within the resources on the classpath.
-

Returns the name of an appropriate file if any is found, else nil.

generate-accept-languages

(generate-accept-languages parse-tree)

From a parse-tree generated by the language-specifier-grammar, generate a list of maps each having a :language key, a :preference key and a :qualifier key.

get-message

Return the message keyed by this token from the most acceptable messages collection
we have given this accept-language-header, if passed, or the current default language otherwise. If no message is found, return the token.

+

Returns the name of an appropriate file if any is found, else nil.

generate-accept-languages

(generate-accept-languages parse-tree)

From a parse-tree generated by the language-specifier-grammar, generate a list of maps each having a :language key, a :preference key and a :qualifier key.

get-message

Return the message keyed by this token from the most acceptable messages collection
we have given this accept-language-header.

  • 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;
  • resource-path should be the fully-qualified path name of the directory in which message files are stored;
  • default-locale should be a locale specifier to use if no acceptable locale can be identified.
  • -

get-messages

Return the most acceptable messages collection we have given this accept-language-header

+

get-messages

Return the most acceptable messages collection we have given this accept-language-header

  • accept-language-header should be the value of an RFC2616 Accept-Language header;
  • resource-path should be the fully-qualified path name of the directory in which message files are stored;
  • default-locale should be a locale specifier to use if no acceptable locale can be identified.
-

Returns a map of message keys to strings.; if no useable file is found, returns nil.

parse-accept-language-header

Parse an Accept-Language header

raw-get-messages

(raw-get-messages accept-language-header resource-path default-locale)

Return the most acceptable messages collection we have given this accept-language-header. Do not use this function directly, use the memoized variant get-messages, as performance will be very much better.

+

Returns a map of message keys to strings.; if no useable file is found, returns nil.

parse-accept-language-header

Parse an Accept-Language header

raw-get-messages

(raw-get-messages accept-language-header resource-path default-locale)

Return the most acceptable messages collection we have given this accept-language-header. Do not use this function directly, use the memoized variant get-messages, as performance will be very much better.

  • accept-language-header should be the value of an RFC2616 Accept-Language header;
  • resource-path should be the fully-qualified path name of the directory in which message files are stored;
  • default-locale should be a locale specifier to use if no acceptable locale can be identified.
-

Returns a map of message keys to strings; if no useable file is found, returns nil.

slurp-resource

(slurp-resource name)

Slurp the resource of this name and return its contents as a string; but if it doesn’t exist log the fact and return nil, rather than throwing an exception.

\ No newline at end of file +

Returns a map of message keys to strings; if no useable file is found, returns nil.

slurp-resource

(slurp-resource name)

Slurp the resource of this name and return its contents as a string; but if it doesn’t exist log the fact and return nil, rather than throwing an exception.

\ No newline at end of file diff --git a/project.clj b/project.clj index 576e379..0053280 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject org.clojars.simon_brooke/internationalisation "1.0.5" +(defproject org.clojars.simon_brooke/internationalisation "1.0.4" :cloverage {:output "docs/cloverage" :codecov? true :emma-xml? true} diff --git a/resources/i18n/en-GB.edn b/resources/i18n/en-GB.edn index a05de3b..3d1133c 100644 --- a/resources/i18n/en-GB.edn +++ b/resources/i18n/en-GB.edn @@ -1,3 +1,3 @@ ;;;; This is a British English translation file. -{:pipe "This is not a pipe."} +{:pipe "This is not a pipe"} diff --git a/src/scot/weft/i18n/core.clj b/src/scot/weft/i18n/core.clj index b074c2a..0e2117c 100644 --- a/src/scot/weft/i18n/core.clj +++ b/src/scot/weft/i18n/core.clj @@ -22,18 +22,13 @@ (def ^:dynamic *resource-path* "The default path within the resources space on which translation files - will be sought. Deprecated, prefer `(:resource-path *config*)`." + will be sought." "i18n") (def ^:dynamic *default-language* - "The default language to seek. Deprecated, prefer `(:default-language *config*)`." + "The default language to seek." (-> (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; @@ -124,7 +119,7 @@ (try (slurp (io/resource name)) (catch Exception _ - (timbre/warn (str "Resource at " name " does not exist.")) + (timbre/error (str "Resource at " name " does not exist.")) nil))) @@ -172,31 +167,23 @@ 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-paths (remove - empty? + (let [file-path (first + (remove + nil? (map #(find-language-file-name % resource-path) - (acceptable-languages accept-language-header))) - default-path (join java.io.File/separator + (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 [resource-path - (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 "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) + (str default-locale ".edn")])))) + (catch Exception any + (timbre/error (str "Failed to load internationalisation because " (.getMessage any))) nil)))) (def get-messages @@ -213,8 +200,7 @@ (def get-message "Return the message keyed by this `token` from the most acceptable messages collection - we have given this `accept-language-header`, if passed, or the current default language - otherwise. If no message is found, return the token. + we have given this `accept-language-header`. * `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; @@ -223,13 +209,8 @@ * `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] - (let [message (token (get-messages accept-language-header resource-path default-locale))] - (or message (name token)))) + ((get-messages accept-language-header resource-path default-locale) token)) ([^Keyword token ^String accept-language-header] - (get-message token - accept-language-header - (or (:resource-path *config*) *resource-path*) - (or (:default-language *config*) *default-language*))) + (get-message token accept-language-header *resource-path* *default-language*)) ([^Keyword token] - (get-message token - (or (:default-language *config*) *default-language*))))) \ No newline at end of file + (get-message token nil *resource-path* *default-language*)))) \ No newline at end of file diff --git a/test/scot/weft/i18n/test/core.clj b/test/scot/weft/i18n/test/core.clj index 91004e0..de3038d 100644 --- a/test/scot/weft/i18n/test/core.clj +++ b/test/scot/weft/i18n/test/core.clj @@ -1,8 +1,7 @@ (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 [*config* - *default-language* + [scot.weft.i18n.core :refer [*default-language* acceptable-languages generate-accept-languages get-message @@ -207,7 +206,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 (= @@ -216,25 +215,9 @@ (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 [*config* (assoc *config* :default-language "fr-FR")] - (is (= "Ceci n'est pas une pipe." (get-message :pipe))) + (binding [*default-language* "en-GB"] + (is (= "This is not a pipe" (get-message :pipe))) (is (= - "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)))))) + "Ceci n'est pas une pipe." (get-message :pipe "en-GB;q=0.9, fr-FR"))) + (is (= "это не труба." (get-message :pipe "de-DE" "i18n" "ru"))))))