Skip to content

Commit

Permalink
Improve internal naming and reporting of locale
Browse files Browse the repository at this point in the history
There are three categories of language reference set. 1. Those that are installed, and recognised as matching a locale. "available" 2. Those that are installed, but not recognised. "unrecognised" 3. Those that are not installed.

In order to improve the internal language module, this renames important private functions to better describe their function (e.g. available-language-reference-sets instead of installed-language-reference-sets).

In addition, Hermes currently provides guarantees that there will always be a fallback set of language reference sets so that we can always determine at least one preferred synonym, for example. This means Hermes has to check that the fallback (default) locale can be met with the reference sets that have been installed. In the event of not meeting this requirement, Hermes fails fast, rather than waiting until runtime. This commit improves that error reporting. The alternative is to give a warning, or log an error, but continue, perhaps using one of the known installed language reference sets, and only to fail if that fallback fails. For now, this maintains the fail-fast no magic fallback option, but a fallback, particularly if the user has not explicitly chosen a locale, but we've the system default locale, might be a better option.
  • Loading branch information
wardle committed Sep 19, 2023
1 parent b3f9bba commit 17fd09a
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 39 deletions.
10 changes: 2 additions & 8 deletions src/com/eldrix/hermes/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1247,17 +1247,11 @@
:memberReader member-reader
:memberSearcher (IndexSearcher. member-reader)
:localeMatchFn (lang/make-match-fn st fallback-locale)}]
;; check that we can support the chosen default (fallback) locale; if not -> fail fast
(when-not (seq (lang/match st fallback-locale))
(log/error "No language reference set installed matching requested locale."
{:requested fallback-locale :available (lang/installed-locales st)})
(throw (ex-info "No language reference set installed matching requested locale."
{:requested fallback-locale, :installed (lang/installed-locales st)})))
;; report configuration when appropriate
(when-not quiet (log/info "opening hermes terminology service " root
(assoc manifest :releases (map :term (store/release-information st))
:default-locale fallback-locale
:installed-locales (lang/installed-locales st))))
:installed-locales (lang/available-locales st))))
(map->Svc (assoc svc :mrcmDomainFn (mrcm-domain-fn svc))))))

(defn close [^Closeable svc]
Expand Down Expand Up @@ -1379,7 +1373,7 @@
[^Svc svc {:keys [counts? modules? installed-refsets?] :or {counts? true installed-refsets? false modules? false}}]
(merge
{:releases (map :term (release-information svc))
:locales (lang/installed-locales (.-store svc))}
:locales (lang/available-locales (.-store svc))}
(when counts?
{:components (-> (store/status (.-store svc))
(assoc-in [:indices :descriptions-search] (.numDocs ^IndexReader (.-indexReader svc)))
Expand Down
74 changes: 43 additions & 31 deletions src/com/eldrix/hermes/impl/language.clj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
service installation, there will need to be a choice in how a specific
locale choice is met. "
(:require [clojure.string :as str]
[clojure.tools.logging.readable :as log]
[com.eldrix.hermes.impl.store :as store]
[com.eldrix.hermes.snomed :as snomed]
[com.eldrix.hermes.verhoeff :as verhoeff])
Expand Down Expand Up @@ -161,79 +162,90 @@
(verhoeff/valid? concept-id))
(Long/parseLong concept-id))))

(defn installed-language-reference-sets
"Return a map of installed language reference sets keyed by `Locale`.
(defn available-language-reference-sets
"Return a map of recognized, installed language reference sets keyed by
`Locale`.
For example
{#object[java.util.Locale 0x31e2875 \"en_GB\"] (999001261000000100 900000000000508004),
#object[java.util.Locale 0x7d55b894 \"en_US\"] (900000000000509007)}"
[store]
(let [installed-refsets (store/installed-reference-sets store)]
(filter-language-reference-sets language-reference-sets installed-refsets)))

(defn installed-locales
"Returns a sequence of installed locales."
(defn available-locales
"Returns a sequence of recognised, installed locales."
[store]
(->> (keys (installed-language-reference-sets store))
(->> (keys (available-language-reference-sets store))
(map #(.toLanguageTag ^Locale %))))

(defn- do-match
"Performs a locale match given the installed locales, a language priority
list and a fallback priority list. Returns a list of reference set ids.
Parameters:
- installed : a map of installed reference sets. See `installed-language-reference-sets`
- available : a map of installed reference sets. See `available-language-reference-sets`
- fallback-priority-list : priority list in case none in chosen list available
- language-priority-list : an 'Accept-Language' string (e.g. \"en-GB,en\")
- fallback? : if a match cannot be found, should fallback to fallback list?
By default, there is no fallback."
([installed language-priority-list]
([available language-priority-list]
(if-let [specific-refset-id (parse-accept-language-refset-id language-priority-list)]
(let [installed-refsets (-> installed vals flatten set)]
(let [installed-refsets (-> available vals flatten set)]
(filter installed-refsets [specific-refset-id]))
(let [locales (keys installed) ;; installed locales
(let [locales (keys available) ;; installed locales
priority-list (try (Locale$LanguageRange/parse language-priority-list) (catch Exception _ []))
filtered (Locale/filter priority-list (or locales '()))]
(mapcat #(get installed %) filtered))))
([installed fallback-priority-list language-priority-list]
(do-match installed fallback-priority-list language-priority-list false))
([installed fallback-priority-list language-priority-list fallback?]
(if-let [result (seq (do-match installed language-priority-list))]
(mapcat #(get available %) filtered))))
([available fallback-priority-list language-priority-list]
(do-match available fallback-priority-list language-priority-list false))
([available fallback-priority-list language-priority-list fallback?]
(if-let [result (seq (do-match available language-priority-list))]
result
(when (and fallback? fallback-priority-list)
(do-match installed fallback-priority-list)))))
(do-match available fallback-priority-list)))))

(defn make-match-fn
" Generate a locale matching function to return the best refsets to use given a
'language-priority-list'.
"Generate a locale matching function to return the best reference sets to use
given a 'language-priority-list' such as \"fr,en-US\".
Parameters:
- store : a SNOMED store
Returns:
- a function that takes a single string containing a list of comma-separated
language ranges or a list of language ranges in the form of the
- fallback-priority-list : default locale to use, e.g. \"en-GB\"
Returns a function that takes a single string containing a list of comma-
separated language ranges or a list of language ranges in the form of the
\"Accept-Language \" header defined in RFC 2616. That function will return a
list of best-matched reference identifiers given those priorities. Optionally,
the returned function can take an additional parameter to choose whether to
fallback to the pre-configured fallback-priority-list should there be no match.
This closes over the installed reference sets at the time of function
generation and so does not take into account changes made since
it was generated. "
([store fallback-priority-list]
(let [installed (installed-language-reference-sets store)
default (do-match installed nil (or fallback-priority-list (.toLanguageTag (Locale/getDefault))))]
(when (empty? default)
(throw (ex-info "cannot use requested fallback language priority list with current installed language reference sets"
{:requested fallback-priority-list, :installed installed})))
it was generated.
Throws an exception if the default locale cannot be supported given the
available installed and recognised locales as per language reference sets."
([st fallback-priority-list]
(let [available (available-language-reference-sets st)
default-refset-ids (do-match available nil (or fallback-priority-list (.toLanguageTag (Locale/getDefault))))]
(when (empty? default-refset-ids)
(let [installed (store/installed-language-reference-sets st)
err {:requested fallback-priority-list ;; what was requested
:available (available-locales st) ;; what is available (ie installed and recognized)
:unrecognized (remove language-refset-id->locale installed) ;; what is installed but not recognized
:installed installed}] ;; what is installed
(log/error "No language reference set installed matching requested fallback (default) locale." err)
(log/error "Explicitly choose a default locale or install additional distributions")
(throw (ex-info "No language reference set installed matching requested fallback (default) locale." err))))
(fn
([]
default)
default-refset-ids)
([language-priority-list]
(when (seq language-priority-list)
(do-match installed fallback-priority-list language-priority-list)))
(do-match available fallback-priority-list language-priority-list)))
([language-priority-list fallback?]
(if (and (str/blank? language-priority-list) fallback?)
default
(do-match installed fallback-priority-list language-priority-list)))))))
default-refset-ids
(do-match available fallback-priority-list language-priority-list)))))))
;;(memoize (partial do-match (installed-language-reference-sets store) fallback-priority-list))))

(defn match
Expand Down

0 comments on commit 17fd09a

Please sign in to comment.