| Lower-level implementation functions for  | (ns metabase.util.i18n.impl (:require [clojure.java.io :as io] [clojure.string :as str] [clojure.tools.reader.edn :as edn] [metabase.plugins.classloader :as classloader] [metabase.util.i18n.plural :as i18n.plural] [metabase.util.log :as log] [potemkin.types :as p.types]) (:import (java.text MessageFormat) (java.util Locale) (org.apache.commons.lang3 LocaleUtils))) | 
| (set! *warn-on-reflection* true) | |
| Protocol for anything that can be coerced to a  | (p.types/defprotocol+ CoerceToLocale
  (locale ^java.util.Locale [this]
    "Coerce `this` to a `java.util.Locale`.")) | 
| Normalize a locale string to the canonical format. (normalized-locale-string "EN-US") ;-> "en_US" Returns  | (defn normalized-locale-string
  ^String [s]
  {:pre [((some-fn nil? string?) s)]}
  #_{:clj-kondo/ignore [:discouraged-var]}
  (when (string? s)
    (when-let [[_ language country] (re-matches #"^(\w{2})(?:[-_](\w{2}))?$" s)]
      (let [language (str/lower-case language)]
        (if country
          (str language \_ (some-> country str/upper-case))
          language))))) | 
| (extend-protocol CoerceToLocale
  nil
  (locale [_] nil)
  Locale
  (locale [this] this)
  String
  (locale [^String s]
    (some-> (normalized-locale-string s) LocaleUtils/toLocale))
  ;; Support namespaced keywords like `:en/US` and `:en/UK` because we can
  clojure.lang.Keyword
  (locale [this]
    (locale (if-let [namespce (namespace this)]
              (str namespce \_ (name this))
              (name this))))) | |
| True if  | (defn available-locale?
  [locale-or-name]
  (boolean
   (when-let [locale (locale locale-or-name)]
     (LocaleUtils/isAvailableLocale locale)))) | 
| (defn- available-locale-names* [] (log/info "Reading available locales from locales.clj...") (some-> (io/resource "locales.clj") slurp edn/read-string :locales (->> (apply sorted-set)))) | |
| Return sorted set of available locales, as Strings. (available-locale-names) ; -> #{"en" "nl" "pt-BR" "zh"} | (let [locales (delay (available-locale-names*))]
  (defn available-locale-names
    []
    @locales)) | 
| (defn- find-fallback-locale*
  ^Locale [^Locale a-locale]
  (some (fn [locale-name]
          (let [try-locale (locale locale-name)]
            ;; The language-only Locale is tried first by virtue of the
            ;; list being sorted.
            (when (and (= (.getLanguage try-locale) (.getLanguage a-locale))
                       (not (= try-locale a-locale)))
              try-locale)))
        (available-locale-names))) | |
| (def ^:private ^{:arglists '([a-locale])} find-fallback-locale
  (memoize find-fallback-locale*)) | |
| Find a translated fallback Locale in the following order:
  1) If it is a language + country Locale, try the language-only Locale
  2) If the language-only Locale isn't translated or the input is a language-only Locale,
     find the first language + country Locale we have a translation for.
 Return  (fallback-locale "en_US") ; -> #locale"en" (fallback-locale "pt") ; -> #locale"pt_BR" (fallback-locale "ptPT") ; -> #locale"ptBR" Note: this logic should be kept in sync with the one in  | (defn fallback-locale
  ^Locale [locale-or-name]
  (when-let [a-locale (locale locale-or-name)]
    (find-fallback-locale a-locale))) | 
| The resource URL for the edn file containing translations for  (locale-edn-resources "es") ;-> #object[java.net.URL "file:/home/cam/metabase/resources/metabase/es.edn"] | (defn- locale-edn-resource
  ^java.net.URL [locale-or-name]
  (when-let [a-locale (locale locale-or-name)]
    (let [locale-name (-> (normalized-locale-string (str a-locale))
                          (str/replace #"_" "-"))
          filename    (format "i18n/%s.edn" locale-name)]
      (io/resource filename (classloader/the-classloader))))) | 
| (defn- translations* [a-locale]
  (when-let [resource (locale-edn-resource a-locale)]
    (edn/read-string (slurp resource)))) | |
| Fetch a map of original untranslated message format string -> translated message format string for  (translations "es") ;-> {:headers { ... } :messages {"Username" "Nombre Usuario", ...}} | (def ^:private ^{:arglists '([locale-or-name])} translations
  (comp (memoize translations*) locale)) | 
| Find the translated version of  
 | (defn- translated-format-string*
  ^String [locale-or-name format-string n]
  (when (seq format-string)
    (when-let [locale (locale locale-or-name)]
      (when-let [translations (translations locale)]
        (when-let [string-or-strings (get-in translations [:messages format-string])]
          (if (string? string-or-strings)
            ;; Only a singular form defined; ignore `n`
            string-or-strings
            (if-let [plural-forms-header (get-in translations [:headers "Plural-Forms"])]
              (get string-or-strings (i18n.plural/index plural-forms-header n))
              ;; Fall-back to singular if no header is present
              (first string-or-strings)))))))) | 
| Find the translated version of  | (defn- translated-format-string
  ^String [locale-or-name format-string {:keys [n format-string-pl]}]
  (when-let [a-locale (locale locale-or-name)]
    (or (when (= (.getLanguage a-locale) "en")
          (if (or (nil? n) (= n 1))
            format-string
            format-string-pl))
        (translated-format-string* a-locale format-string n)
        (when-let [fallback-locale (fallback-locale a-locale)]
          (log/tracef "No translated string found, trying fallback locale %s" (pr-str fallback-locale))
          (translated-format-string* fallback-locale format-string n))
        format-string))) | 
| (defn- message-format ^MessageFormat [locale-or-name ^String format-string pluralization-opts]
  (or (when-let [a-locale (locale locale-or-name)]
        (when-let [^String translated (translated-format-string a-locale format-string pluralization-opts)]
          (MessageFormat. translated a-locale)))
      (MessageFormat. format-string))) | |
| Find the translated version of  
 Will attempt to translate  (translate "es-MX" "must be {0} characters or less" 140) ; -> "deben tener 140 caracteres o menos" | (defn translate
  ([locale-or-name ^String format-string]
   (translate locale-or-name format-string []))
  ([locale-or-name ^String format-string args]
   (translate locale-or-name format-string args {}))
  ([locale-or-name ^String format-string args pluralization-opts]
   (when (seq format-string)
     (try
       (.format (message-format locale-or-name format-string pluralization-opts) (to-array args))
       (catch Throwable e
         ;; Not translating this string to prevent an unfortunate stack overflow. If this string happened to be the one
         ;; that had the typo, we'd just recur endlessly without logging an error.
         (log/errorf e "Unable to translate string %s to %s" (pr-str format-string) (str locale-or-name))
         (try
           (.format (MessageFormat. format-string) (to-array args))
           (catch Throwable _
             (log/errorf e "Invalid format string %s" (pr-str format-string))
             format-string))))))) | 
| Whether we're currently inside a call to [[site-locale-from-setting]], so we can prevent infinite recursion. | (def ^:private ^:dynamic *in-site-locale-from-setting* false) | 
| Fetch the value of the  | (defn site-locale-from-setting
  []
  (when-let [get-value-of-type (resolve 'metabase.models.setting/get-value-of-type)]
    (when (bound? get-value-of-type)
      ;; make sure we don't try to recursively fetch the site locale when we're actively in the process of fetching it,
      ;; otherwise that will cause infinite loops if we try to log anything... see #32376
      (when-not *in-site-locale-from-setting*
        (binding [*in-site-locale-from-setting* true]
          ;; if there is an error fetching the Setting, e.g. if the app DB is in the process of shutting down, then just
          ;; return nil.
          (try
            (get-value-of-type :string :site-locale)
            (catch Exception _
              nil))))))) | 
| (defmethod print-method Locale [locale ^java.io.Writer writer] ((get-method print-dup Locale) locale writer)) | |
| (defmethod print-dup Locale [locale ^java.io.Writer writer] (.write writer (format "#locale %s" (pr-str (str locale))))) | |