diff --git a/bin/i18n/src/i18n/create_artifacts.clj b/bin/i18n/src/i18n/create_artifacts.clj index 23cf7de8ad3bf2ef0b01ed6ef0a83bfb57fe0834..26187f0aafba778bd5b6b767e767712aa98832be 100644 --- a/bin/i18n/src/i18n/create_artifacts.clj +++ b/bin/i18n/src/i18n/create_artifacts.clj @@ -25,6 +25,9 @@ (u/announce "Artifacts for locale %s created successfully." (pr-str locale)))) (defn- create-artifacts-for-all-locales! [] + ;; Empty directory in case some locales were removed + (u/delete-file-if-exists! backend/target-directory) + (u/delete-file-if-exists! frontend/target-directory) (doseq [locale (i18n/locales)] (create-artifacts-for-locale! locale))) diff --git a/bin/i18n/src/i18n/create_artifacts/frontend.clj b/bin/i18n/src/i18n/create_artifacts/frontend.clj index dd94a35e7a8f076157af51dbb3fb99fda50f349c..3e326b754c09304a16f9c3d5766698529b4e21b0 100644 --- a/bin/i18n/src/i18n/create_artifacts/frontend.clj +++ b/bin/i18n/src/i18n/create_artifacts/frontend.clj @@ -43,7 +43,7 @@ (defn- i18n-map [locale] (->i18n-map (i18n/po-contents locale))) -(def ^:private target-directory +(def target-directory (u/filename u/project-root-directory "resources" "frontend_client" "app" "locales")) (defn- target-filename [locale] diff --git a/src/metabase/server/routes/index.clj b/src/metabase/server/routes/index.clj index daab2cd3006c602e571097092169ca21f5fb3487..3f5719f960e01afdfe77af74bf349e411c3f8890 100644 --- a/src/metabase/server/routes/index.clj +++ b/src/metabase/server/routes/index.clj @@ -44,8 +44,8 @@ (when-not (= locale-name "en") (try (slurp (or (io/resource (localization-json-file-name locale-name)) - (when-let [parent-locale (i18n/parent-locale locale-name)] - (io/resource (localization-json-file-name (str parent-locale)))) + (when-let [fallback-locale (i18n/fallback-locale locale-name)] + (io/resource (localization-json-file-name (str fallback-locale)))) ;; don't try to i18n the Exception message below, we have no locale to translate it to! (throw (FileNotFoundException. (format "Locale '%s' not found." locale-name))))) (catch Throwable e diff --git a/src/metabase/util/i18n.clj b/src/metabase/util/i18n.clj index f3eac252a2dbaac76b7efb0d4b05eccd7a3c72df..a685dc2531d625d16ba27cb36d2787a935bd2f8e 100644 --- a/src/metabase/util/i18n.clj +++ b/src/metabase/util/i18n.clj @@ -14,9 +14,9 @@ (p/import-vars [impl available-locale? + fallback-locale locale normalized-locale-string - parent-locale translate]) (def ^:dynamic *user-locale* diff --git a/src/metabase/util/i18n/impl.clj b/src/metabase/util/i18n/impl.clj index 52de2f33fba4367b4869c784c6431f14da7a0bd4..9416f4e135bcb1fa3fc8e8411008d7e139fbbb36 100644 --- a/src/metabase/util/i18n/impl.clj +++ b/src/metabase/util/i18n/impl.clj @@ -57,14 +57,45 @@ (when-let [locale (locale locale-or-name)] (LocaleUtils/isAvailableLocale locale)))) -(defn parent-locale - "For langugage + country Locales, returns the language-only Locale. Otherwise returns `nil`. +(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)))) + +(def ^{:arglists '([])} available-locale-names + "Return sorted set of available locales, as Strings. - (parent-locale \"en/US\") ; -> #object[java.util.Locale 0x79301688 \"en\"]" + (available-locale-names) ; -> #{\"en\" \"nl\" \"pt-BR\" \"zh\"}" + (let [locales (delay (available-locale-names*))] + (fn [] @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*)) + +(defn 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 `nil` if no fallback Locale can be found or the input is invalid. + + (fallback-locale \"en_US\") ; -> #locale\"en\" + (fallback-locale \"pt\") ; -> #locale\"pt_BR\" + (fallback-locale \"pt_PT\") ; -> #locale\"pt_BR\"" ^Locale [locale-or-name] (when-let [a-locale (locale locale-or-name)] - (when (seq (.getCountry a-locale)) - (locale (.getLanguage a-locale))))) + (find-fallback-locale a-locale))) (defn- locale-edn-resource "The resource URL for the edn file containing translations for `locale-or-name`. These files are built by the @@ -106,9 +137,9 @@ (or (when (= (.getLanguage a-locale) "en") format-string) (translated-format-string* a-locale format-string) - (when-let [parent-locale (parent-locale a-locale)] - (log/tracef "No translated string found, trying parent locale %s" (pr-str parent-locale)) - (translated-format-string* parent-locale format-string)) + (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)) format-string))) (defn- message-format ^MessageFormat [locale-or-name ^String format-string] @@ -144,18 +175,6 @@ (log/errorf e "Invalid format string %s" (pr-str format-string)) format-string)))))) -(defn- available-locale-names* - [] - (log/info "Reading available locales from locales.clj...") - (some-> (io/resource "locales.clj") slurp edn/read-string :locales set)) - -(def ^{:arglists '([])} available-locale-names - "Return set of available locales, as Strings. - - (available-locale-names) ; -> #{\"nl\" \"pt\" \"en\" \"zh\"}" - (let [locales (delay (available-locale-names*))] - (fn [] @locales))) - ;; We can't fetch the system locale until the application DB has been initiailized. Once that's done, we don't need to ;; do the check anymore -- swapping out the getter fn with the simpler one speeds things up substantially (def ^:private site-locale-from-setting-fn diff --git a/test/metabase/util/i18n/impl_test.clj b/test/metabase/util/i18n/impl_test.clj index 0194552d2dfbbe6dff6854dbc8b5d965af19faaf..c28f153b5c86fdb76100254ab5dcfc2b5bd33528 100644 --- a/test/metabase/util/i18n/impl_test.clj +++ b/test/metabase/util/i18n/impl_test.clj @@ -55,18 +55,21 @@ (is (= expected (impl/available-locale? locale)))))) -(deftest parent-locale-test - (doseq [[locale expected] {nil nil - :es nil - "es" nil +(deftest fallback-locale-test + (doseq [[locale expected] {nil nil + :es nil + "es" nil (Locale/forLanguageTag "es") nil - "es-MX" (Locale/forLanguageTag "es") - "es_MX" (Locale/forLanguageTag "es") - :es/MX (Locale/forLanguageTag "es") - (Locale/forLanguageTag "es-MX") (Locale/forLanguageTag "es")}] + "es-MX" (Locale/forLanguageTag "es") + "es_MX" (Locale/forLanguageTag "es") + :es/MX (Locale/forLanguageTag "es") + (Locale/forLanguageTag "es-MX") (Locale/forLanguageTag "es") + ;; 0.39 changed pt to pt_BR (metabase#15630) + "pt" (Locale/forLanguageTag "pt-BR") + "pt-PT" (Locale/forLanguageTag "pt-BR")}] (testing locale (is (= expected - (impl/parent-locale locale)))))) + (impl/fallback-locale locale)))))) (deftest graceful-fallback-test (testing "If a resource bundle doesn't exist, we should gracefully fall back to English"