diff --git a/src/metabase/api/alert.clj b/src/metabase/api/alert.clj index 0976d8c8856d9389b7c60b60b9106cd2dc6a065c..276bb3908d377f05d60c943828babbd38f297bb4 100644 --- a/src/metabase/api/alert.clj +++ b/src/metabase/api/alert.clj @@ -14,8 +14,9 @@ [card :refer [Card]] [interface :as mi] [pulse :as pulse :refer [Pulse]]] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [i18n :refer [tru]] + [schema :as su]] [schema.core :as s] [toucan [db :as db] diff --git a/src/metabase/api/automagic_dashboards.clj b/src/metabase/api/automagic_dashboards.clj index 27a2a15930bee77a7423624542471c4be590f069..6856f8e0eff73557af3ee7f9dcdd2fe88fa7a009 100644 --- a/src/metabase/api/automagic_dashboards.clj +++ b/src/metabase/api/automagic_dashboards.clj @@ -17,8 +17,9 @@ [segment :refer [Segment]] [table :refer [Table]]] [metabase.models.query.permissions :as query-perms] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [i18n :refer [tru]] + [schema :as su]] [ring.util.codec :as codec] [schema.core :as s])) diff --git a/src/metabase/api/card.clj b/src/metabase/api/card.clj index 4a8ac62236506c33d13b4a0a7b796143bfea48a5..fc9cf849ab7faf1a2edd506f3eb2b8fd40253e9d 100644 --- a/src/metabase/api/card.clj +++ b/src/metabase/api/card.clj @@ -33,8 +33,9 @@ [cache :as cache] [results-metadata :as results-metadata]] [metabase.sync.analyze.query-results :as qr] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [trs tru]] + [metabase.util + [i18n :refer [trs tru]] + [schema :as su]] [schema.core :as s] [toucan [db :as db] diff --git a/src/metabase/api/common.clj b/src/metabase/api/common.clj index 6e604e7a057c4c8aaf01d30bda8f31cc14a3ab83..ccabf2512fd6b7951f0e3ce4948a16e5c6dcd7cc 100644 --- a/src/metabase/api/common.clj +++ b/src/metabase/api/common.clj @@ -14,8 +14,9 @@ [util :as u]] [metabase.api.common.internal :refer :all] [metabase.models.interface :as mi] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [trs tru]] + [metabase.util + [i18n :as ui18n :refer [trs tru]] + [schema :as su]] [ring.core.protocols :as protocols] [ring.util.response :as response] [schema.core :as s] @@ -75,9 +76,10 @@ [code-or-code-message-pair rest-args] [[code-or-code-message-pair (first rest-args)] (rest rest-args)])] (when-not tst - (throw (if (map? message) - (ex-info (:message message) (assoc message :status-code code)) - (ex-info message {:status-code code})))) + (throw (if (and (map? message) + (not (ui18n/localized-string? message))) + (ui18n/ex-info (:message message) (assoc message :status-code code)) + (ui18n/ex-info message {:status-code code})))) (if (empty? rest-args) tst (recur (first rest-args) (second rest-args) (drop 2 rest-args)))))) @@ -100,7 +102,7 @@ (defn throw-invalid-param-exception "Throw an `ExceptionInfo` that contains information about an invalid API params in the expected format." [field-name message] - (throw (ex-info (tru "Invalid field: {0}" field-name) + (throw (ui18n/ex-info (tru "Invalid field: {0}" field-name) {:status-code 400 :errors {(keyword field-name) message}}))) @@ -216,23 +218,23 @@ ;; #### GENERIC 403 RESPONSE HELPERS ;; If you can't be bothered to write a custom error message -(def ^:private generic-403 +(defn- generic-403 [] [403 (tru "You don''t have permissions to do that.")]) (defn check-403 "Throw a `403` (no permissions) if `arg` is `false` or `nil`, otherwise return as-is." [arg] - (check arg generic-403)) + (check arg (generic-403))) (defmacro let-403 "Bind a form as with `let`; throw a 403 if it is `nil` or `false`." {:style/indent 1} [& body] - `(api-let ~generic-403 ~@body)) + `(api-let (generic-403) ~@body)) (defn throw-403 "Throw a generic 403 (no permissions) error response." [] - (throw (ex-info (tru "You don''t have permissions to do that.") {:status-code 403}))) + (throw (ui18n/ex-info (tru "You don''t have permissions to do that.") {:status-code 403}))) ;; #### GENERIC 500 RESPONSE HELPERS ;; For when you don't feel like writing something useful diff --git a/src/metabase/api/common/internal.clj b/src/metabase/api/common/internal.clj index 7ee3fc4b5d866aa825a41b593e5e6858ea7e4fef..4976ed9e5b45304a0dc46f5197d50438694a3112 100644 --- a/src/metabase/api/common/internal.clj +++ b/src/metabase/api/common/internal.clj @@ -6,8 +6,9 @@ [medley.core :as m] [metabase.config :as config] [metabase.util :as u] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [trs tru]] + [metabase.util + [i18n :as ui18n :refer [tru]] + [schema :as su]] [schema.core :as s])) ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -111,7 +112,7 @@ [^String value] (try (Integer/parseInt value) (catch NumberFormatException _ - (throw (ex-info (format "Not a valid integer: '%s'" value) {:status-code 400}))))) + (throw (ui18n/ex-info (tru "Not a valid integer: ''{0}''" value) {:status-code 400}))))) (def ^:dynamic *auto-parse-types* "Map of `param-type` -> map with the following keys: @@ -218,7 +219,7 @@ [field-name value schema] (try (s/validate schema value) (catch Throwable e - (throw (ex-info (tru "Invalid field: {0}" field-name) + (throw (ui18n/ex-info (tru "Invalid field: {0}" field-name) {:status-code 400 :errors {(keyword field-name) (or (su/api-error-message schema) (:message (ex-data e)) diff --git a/src/metabase/api/dataset.clj b/src/metabase/api/dataset.clj index 22874aa0256942ca0fb303b89dcab8ebde8600fe..196d61793d318a3102c5e79b0e433dfbee3c831a 100644 --- a/src/metabase/api/dataset.clj +++ b/src/metabase/api/dataset.clj @@ -14,8 +14,8 @@ [metabase.util [date :as du] [export :as ex] + [i18n :refer [trs tru]] [schema :as su]] - [puppetlabs.i18n.core :refer [trs tru]] [schema.core :as s])) ;;; -------------------------------------------- Running a Query Normally -------------------------------------------- diff --git a/src/metabase/api/embed.clj b/src/metabase/api/embed.clj index c339c925b3a52c4bccb9954c5ea122e3ac35be08..6cbe57238f0d6529c6e66ca5749a5e40b7aaec1d 100644 --- a/src/metabase/api/embed.clj +++ b/src/metabase/api/embed.clj @@ -31,6 +31,7 @@ [metabase.util :as u] [metabase.util [embed :as eu] + [i18n :refer [tru]] [schema :as su]] [schema.core :as s] [toucan.db :as db])) @@ -253,7 +254,7 @@ (api/check-404 object) (api/check-not-archived object) (api/check (:enable_embedding object) - [400 "Embedding is not enabled for this object."]))) + [400 (tru "Embedding is not enabled for this object.")]))) (def ^:private ^{:arglists '([dashboard-id])} check-embedding-enabled-for-dashboard (partial check-embedding-enabled-for-object Dashboard)) diff --git a/src/metabase/api/geojson.clj b/src/metabase/api/geojson.clj index 96209270ed718351154b52f77ae78b0757bfa51c..6aaf4938748c34a981816c9d43c8cda919488c68 100644 --- a/src/metabase/api/geojson.clj +++ b/src/metabase/api/geojson.clj @@ -5,10 +5,12 @@ [metabase.api.common :refer [defendpoint define-routes]] [metabase.models.setting :as setting :refer [defsetting]] [metabase.util :as u] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [i18n :as ui18n :refer [tru]] + [schema :as su]] [ring.util.response :as rr] - [schema.core :as s]) + [schema.core :as s] + [metabase.util.i18n :as ui18n]) (:import org.apache.commons.io.input.ReaderInputStream)) (def ^:private ^:const ^Integer geojson-fetch-timeout-ms @@ -87,7 +89,7 @@ [key] {key su/NonBlankString} (let [url (or (get-in (custom-geojson) [(keyword key) :url]) - (throw (ex-info (tru "Invalid custom GeoJSON key: {0}" key) + (throw (ui18n/ex-info (tru "Invalid custom GeoJSON key: {0}" key) {:status-code 400})))] ;; TODO - it would be nice if we could also avoid returning our usual cache-busting headers with the response here (-> (rr/response (ReaderInputStream. (io/reader url))) diff --git a/src/metabase/api/public.clj b/src/metabase/api/public.clj index e3758dd68100dbfe043c1fd91c2b4237a75bae29..d483074b0cc133d86202a5cc9ab942940bd004c9 100644 --- a/src/metabase/api/public.clj +++ b/src/metabase/api/public.clj @@ -24,8 +24,8 @@ [params :as params]] [metabase.util [embed :as embed] + [i18n :refer [tru]] [schema :as su]] - [puppetlabs.i18n.core :refer [tru]] [schema.core :as s] [toucan [db :as db] diff --git a/src/metabase/api/pulse.clj b/src/metabase/api/pulse.clj index 0026542367bc3b4ad7d671552ae7db6332d2c0fd..7941a535449464e8080ae222f7773f3f7a56ece6 100644 --- a/src/metabase/api/pulse.clj +++ b/src/metabase/api/pulse.clj @@ -19,9 +19,9 @@ [pulse-channel :refer [channel-types]]] [metabase.pulse.render :as render] [metabase.util + [i18n :refer [tru]] [schema :as su] [urls :as urls]] - [puppetlabs.i18n.core :refer [tru]] [schema.core :as s] [toucan [db :as db] diff --git a/src/metabase/api/routes.clj b/src/metabase/api/routes.clj index bbbb5e900e54d696828c54ee2a26f240b3a53d6e..e5babb2903699e7fe24ab57aa422d204f78694fe 100644 --- a/src/metabase/api/routes.clj +++ b/src/metabase/api/routes.clj @@ -34,7 +34,7 @@ [user :as user] [util :as util]] [metabase.middleware :as middleware] - [puppetlabs.i18n.core :refer [tru]])) + [metabase.util.i18n :refer [tru]])) (def ^:private +generic-exceptions "Wrap ROUTES so any Exception thrown is just returned as a generic 400, to prevent details from leaking in public diff --git a/src/metabase/api/session.clj b/src/metabase/api/session.clj index 22e80070ba80718aa45933a0aee013b716db1678..69a9c6a29ec19f024d22d120b34a5b8503fc1524 100644 --- a/src/metabase/api/session.clj +++ b/src/metabase/api/session.clj @@ -17,9 +17,9 @@ [setting :refer [defsetting]] [user :as user :refer [User]]] [metabase.util + [i18n :as ui18n :refer [trs tru]] [password :as pass] [schema :as su]] - [puppetlabs.i18n.core :refer [trs tru]] [schema.core :as s] [throttle.core :as throttle] [toucan.db :as db])) @@ -56,7 +56,7 @@ (when-not (ldap/verify-password user-info password) ;; Since LDAP knows about the user, fail here to prevent the local strategy to be tried with a possibly ;; outdated password - (throw (ex-info password-fail-message + (throw (ui18n/ex-info password-fail-message {:status-code 400 :errors {:password password-fail-snippet}}))) ;; password is ok, return new session @@ -85,7 +85,7 @@ (email-login username password) ; Then try local authentication ;; If nothing succeeded complain about it ;; Don't leak whether the account doesn't exist or the password was incorrect - (throw (ex-info password-fail-message + (throw (ui18n/ex-info password-fail-message {:status-code 400 :errors {:password password-fail-snippet}})))) @@ -189,10 +189,10 @@ (defn- google-auth-token-info [^String token] (let [{:keys [status body]} (http/post (str "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=" token))] (when-not (= status 200) - (throw (ex-info (tru "Invalid Google Auth token.") {:status-code 400}))) + (throw (ui18n/ex-info (tru "Invalid Google Auth token.") {:status-code 400}))) (u/prog1 (json/parse-string body keyword) (when-not (= (:email_verified <>) "true") - (throw (ex-info (tru "Email is not verified.") {:status-code 400})))))) + (throw (ui18n/ex-info (tru "Email is not verified.") {:status-code 400})))))) ;; TODO - are these general enough to move to `metabase.util`? (defn- email->domain ^String [email] @@ -211,7 +211,7 @@ ;; Use some wacky status code (428 - Precondition Required) so we will know when to so the error screen specific ;; to this situation (throw - (ex-info (tru "You''ll need an administrator to create a Metabase account before you can use Google to log in.") + (ui18n/ex-info (tru "You''ll need an administrator to create a Metabase account before you can use Google to log in.") {:status-code 428})))) (s/defn ^:private google-auth-create-new-user! diff --git a/src/metabase/api/setup.clj b/src/metabase/api/setup.clj index 55e3e0b371ed8831f4d36e24721aac7ffed37de2..944b3e79b194d23a6332c2745029621b83ed6fad 100644 --- a/src/metabase/api/setup.clj +++ b/src/metabase/api/setup.clj @@ -14,8 +14,9 @@ [database :refer [Database]] [session :refer [Session]] [user :as user :refer [User]]] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [i18n :refer [tru]] + [schema :as su]] [schema.core :as s] [toucan.db :as db])) diff --git a/src/metabase/api/table.clj b/src/metabase/api/table.clj index 05378ec5ea0cb8ddde16df45cb4d014f26653d53..9c073d5f1487f68068b3db29f0b15104fd988dd4 100644 --- a/src/metabase/api/table.clj +++ b/src/metabase/api/table.clj @@ -19,7 +19,7 @@ [metabase.sync.field-values :as sync-field-values] [metabase.util.schema :as su] [schema.core :as s] - [puppetlabs.i18n.core :refer [trs tru]] + [metabase.util.i18n :refer [trs tru]] [toucan [db :as db] [hydrate :refer [hydrate]]])) @@ -156,13 +156,13 @@ (pred v))) dimension-options-for-response))) (def ^:private date-default-index - (dimension-index-for-type "type/DateTime" #(= day-str (:name %)))) + (dimension-index-for-type "type/DateTime" #(= (str day-str) (str (:name %))))) (def ^:private numeric-default-index - (dimension-index-for-type "type/Number" #(.contains ^String (:name %) auto-bin-str))) + (dimension-index-for-type "type/Number" #(.contains ^String (str (:name %)) (str auto-bin-str)))) (def ^:private coordinate-default-index - (dimension-index-for-type "type/Coordinate" #(.contains ^String (:name %) auto-bin-str))) + (dimension-index-for-type "type/Coordinate" #(.contains ^String (str (:name %)) (str auto-bin-str)))) (defn- supports-numeric-binning? [driver] (and driver (contains? (driver/features driver) :binning))) diff --git a/src/metabase/api/tiles.clj b/src/metabase/api/tiles.clj index 6a242975a38332ddd7bafc1c803330652a9d9ffa..449f4e39f9db79533ed05100b5c2dda9d475cbf2 100644 --- a/src/metabase/api/tiles.clj +++ b/src/metabase/api/tiles.clj @@ -7,8 +7,9 @@ [query-processor :as qp] [util :as u]] [metabase.api.common :as api] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [tru]]) + [metabase.util + [i18n :refer [tru]] + [schema :as su]]) (:import java.awt.Color java.awt.image.BufferedImage java.io.ByteArrayOutputStream diff --git a/src/metabase/api/user.clj b/src/metabase/api/user.clj index cee471889faf679b4d329c730ee2f6f1b1b30e80..4b3bd9a79b1853eedb78df32567e37f482f79e8e 100644 --- a/src/metabase/api/user.clj +++ b/src/metabase/api/user.clj @@ -9,8 +9,9 @@ [metabase.integrations.ldap :as ldap] [metabase.models.user :as user :refer [User]] [metabase.util :as u] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [i18n :refer [tru]] + [schema :as su]] [schema.core :as s] [toucan [db :as db] diff --git a/src/metabase/automagic_dashboards/populate.clj b/src/metabase/automagic_dashboards/populate.clj index 55df966a4feb900ada67aa8b9a3b2e0e173899a2..b5930f17aed95e2641b515b15daf5921e361af6d 100644 --- a/src/metabase/automagic_dashboards/populate.clj +++ b/src/metabase/automagic_dashboards/populate.clj @@ -9,7 +9,7 @@ [collection :as collection]] [metabase.query-processor.util :as qp.util] [metabase.util :as u] - [puppetlabs.i18n.core :as i18n :refer [trs]] + [metabase.util.i18n :refer [trs]] [toucan.db :as db])) (def ^Long grid-width @@ -275,10 +275,10 @@ ;; Height doesn't need to be precise, just some ;; safe upper bound. (make-grid grid-width (* n grid-width))]))] - (log/infof (trs "Adding %s cards to dashboard %s:\n%s") - (count cards) - title - (str/join "; " (map :title cards))) + (log/info (trs "Adding {0} cards to dashboard {1}:\n{2}" + (count cards) + title + (str/join "; " (map :title cards)))) (cond-> dashboard (not-empty filters) (filters/add-filters filters max-filters))))) diff --git a/src/metabase/automagic_dashboards/rules.clj b/src/metabase/automagic_dashboards/rules.clj index e137acc20333ba3f0b212e88e3fe8f3301f3b351..22b7d3987fc59f598e5d4ee830b731c8edf9f705 100644 --- a/src/metabase/automagic_dashboards/rules.clj +++ b/src/metabase/automagic_dashboards/rules.clj @@ -6,8 +6,9 @@ [metabase.automagic-dashboards.populate :as populate] [metabase.query-processor.util :as qp.util] [metabase.util :as u] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :as i18n :refer [trs]] + [metabase.util + [i18n :refer [trs]] + [schema :as su]] [schema [coerce :as sc] [core :as s]] @@ -298,13 +299,13 @@ rules-validator (as-> rule (assoc rule :specificity (specificity rule))))) (catch Exception e - (log/errorf (trs "Error parsing %s:\n%s") - (.getFileName f) - (or (some-> e - ex-data - (select-keys [:error :value]) - u/pprint-to-str) - e)) + (log/error (trs "Error parsing {0}:\n{1}" + (.getFileName f) + (or (some-> e + ex-data + (select-keys [:error :value]) + u/pprint-to-str) + e))) nil))) (defn- trim-trailing-slash diff --git a/src/metabase/cmd/reset_password.clj b/src/metabase/cmd/reset_password.clj index 944a49c0da62406c15d98e74950799919a8d80d4..ce912ecc8fe3bad72903833f37523f71da8c9329 100644 --- a/src/metabase/cmd/reset_password.clj +++ b/src/metabase/cmd/reset_password.clj @@ -1,7 +1,7 @@ (ns metabase.cmd.reset-password (:require [metabase.db :as mdb] [metabase.models.user :refer [User] :as user] - [puppetlabs.i18n.core :refer [trs]] + [metabase.util.i18n :refer [trs]] [toucan.db :as db])) (defn- set-reset-token! diff --git a/src/metabase/core.clj b/src/metabase/core.clj index dcaa9cc47ed5c1be55f2e14b851c5acc69974ba2..f2fc15fb4cb53c354f330eb1197fc5a732a951be 100644 --- a/src/metabase/core.clj +++ b/src/metabase/core.clj @@ -22,8 +22,8 @@ [metabase.models [setting :as setting] [user :refer [User]]] - [metabase.util.i18n :refer [set-locale]] - [puppetlabs.i18n.core :refer [locale-negotiator trs]] + [metabase.util.i18n :refer [set-locale trs]] + [puppetlabs.i18n.core :refer [locale-negotiator]] [ring.adapter.jetty :as ring-jetty] [ring.middleware [cookies :refer [wrap-cookies]] diff --git a/src/metabase/db.clj b/src/metabase/db.clj index 375451011125f9cf4dec0b707804a923e796038c..336c792924da5539220b8d1bc00e45606760caa2 100644 --- a/src/metabase/db.clj +++ b/src/metabase/db.clj @@ -11,7 +11,7 @@ [config :as config] [util :as u]] [metabase.db.spec :as dbspec] - [puppetlabs.i18n.core :refer [trs]] + [metabase.util.i18n :refer [trs]] [ring.util.codec :as codec] [toucan.db :as db]) (:import com.mchange.v2.c3p0.ComboPooledDataSource diff --git a/src/metabase/db/migrations.clj b/src/metabase/db/migrations.clj index fae7e2ab0780181f8e80d4ce9771c1eac40772a2..b36b050a06b0d96453934d2a790a425cc4bf0c8c 100644 --- a/src/metabase/db/migrations.clj +++ b/src/metabase/db/migrations.clj @@ -30,8 +30,9 @@ [setting :as setting :refer [Setting]] [user :refer [User]]] [metabase.query-processor.util :as qputil] - [metabase.util.date :as du] - [puppetlabs.i18n.core :refer [trs]] + [metabase.util + [date :as du] + [i18n :refer [trs]]] [toucan [db :as db] [models :as models]] @@ -363,9 +364,9 @@ (doseq [group-id non-admin-group-ids] (perms/grant-collection-readwrite-permissions! group-id collection/root-collection)) ;; 2. Create the new collections. - (doseq [[model new-collection-name] {Dashboard (trs "Migrated Dashboards") - Pulse (trs "Migrated Pulses") - Card (trs "Migrated Questions")} + (doseq [[model new-collection-name] {Dashboard (str (trs "Migrated Dashboards")) + Pulse (str (trs "Migrated Pulses")) + Card (str (trs "Migrated Questions"))} :when (db/exists? model :collection_id nil) :let [new-collection (db/insert! Collection :name new-collection-name diff --git a/src/metabase/driver.clj b/src/metabase/driver.clj index 8beb896a480d826e1e7108400c510a0ff3baf60d..c15665872720200567df67121725c4cdc7134618 100644 --- a/src/metabase/driver.clj +++ b/src/metabase/driver.clj @@ -23,8 +23,9 @@ [database :refer [Database]] [setting :refer [defsetting]]] [metabase.sync.interface :as si] - [metabase.util.date :as du] - [puppetlabs.i18n.core :refer [trs tru]] + [metabase.util + [date :as du] + [i18n :refer [trs tru]]] [schema.core :as s] [toucan.db :as db]) (:import clojure.lang.Keyword diff --git a/src/metabase/driver/bigquery.clj b/src/metabase/driver/bigquery.clj index b08ec3f8a192cd2e5200df995101960922ed1abc..4eb74e77f16c14dc576067b6e348feb56e673063 100644 --- a/src/metabase/driver/bigquery.clj +++ b/src/metabase/driver/bigquery.clj @@ -27,14 +27,13 @@ [util :as qputil]] [metabase.util [date :as du] - [honeysql-extensions :as hx]] - [puppetlabs.i18n.core :refer [tru]] + [honeysql-extensions :as hx] + [i18n :refer [tru]]] [toucan.db :as db]) (:import com.google.api.client.googleapis.auth.oauth2.GoogleCredential com.google.api.client.http.HttpRequestInitializer [com.google.api.services.bigquery Bigquery Bigquery$Builder BigqueryScopes] - [com.google.api.services.bigquery.model QueryRequest QueryResponse Table TableCell TableFieldSchema TableList - TableList$Tables TableReference TableRow TableSchema] + [com.google.api.services.bigquery.model QueryRequest QueryResponse Table TableCell TableFieldSchema TableList TableList$Tables TableReference TableRow TableSchema] honeysql.format.ToSql java.sql.Time [java.util Collections Date] diff --git a/src/metabase/driver/crate.clj b/src/metabase/driver/crate.clj index 7957a4388b26f67a688c552c124a9a82f6cc8df1..0f31c8259a046bc2399f6633127210538dfd4a44 100644 --- a/src/metabase/driver/crate.clj +++ b/src/metabase/driver/crate.clj @@ -7,7 +7,7 @@ [util :as u]] [metabase.driver.crate.util :as crate-util] [metabase.driver.generic-sql :as sql] - [puppetlabs.i18n.core :refer [tru]]) + [metabase.util.i18n :refer [tru]]) (:import java.sql.DatabaseMetaData)) (def ^:private ^:const column->base-type diff --git a/src/metabase/driver/druid.clj b/src/metabase/driver/druid.clj index 673e06269d6569ae17cbc5a512eba3a10337fa5d..11018b9cb091567aaf6e06bf62d2d4e194142eac 100644 --- a/src/metabase/driver/druid.clj +++ b/src/metabase/driver/druid.clj @@ -7,8 +7,9 @@ [driver :as driver] [util :as u]] [metabase.driver.druid.query-processor :as qp] - [metabase.util.ssh :as ssh] - [puppetlabs.i18n.core :refer [tru]])) + [metabase.util + [i18n :refer [tru]] + [ssh :as ssh]])) ;;; ### Request helper fns diff --git a/src/metabase/driver/generic_sql/query_processor.clj b/src/metabase/driver/generic_sql/query_processor.clj index 67afd813db4ae1346004361e3f5286cf32e905d7..324a1d4b2b7216b503ce6bc21fc7ae405c037b15 100644 --- a/src/metabase/driver/generic_sql/query_processor.clj +++ b/src/metabase/driver/generic_sql/query_processor.clj @@ -16,8 +16,8 @@ [util :as qputil]] [metabase.util [date :as du] - [honeysql-extensions :as hx]] - [puppetlabs.i18n.core :refer [trs]]) + [honeysql-extensions :as hx] + [i18n :refer [trs]]]) (:import [java.sql PreparedStatement ResultSet ResultSetMetaData SQLException] [java.util Calendar Date TimeZone] [metabase.query_processor.interface AgFieldRef BinnedField DateTimeField DateTimeValue Expression diff --git a/src/metabase/driver/googleanalytics.clj b/src/metabase/driver/googleanalytics.clj index 7d83b9de51cf2ab6c40baf3bc2ecdd1f729e428d..10b9ceed69741666d5c7527395a8234ded275fa2 100644 --- a/src/metabase/driver/googleanalytics.clj +++ b/src/metabase/driver/googleanalytics.clj @@ -7,7 +7,7 @@ [metabase.driver.google :as google] [metabase.driver.googleanalytics.query-processor :as qp] [metabase.models.database :refer [Database]] - [puppetlabs.i18n.core :refer [tru]]) + [metabase.util.i18n :refer [tru]]) (:import com.google.api.client.googleapis.auth.oauth2.GoogleCredential [com.google.api.services.analytics Analytics Analytics$Builder Analytics$Data$Ga$Get AnalyticsScopes] [com.google.api.services.analytics.model Column Columns Profile Profiles Webproperties Webproperty] @@ -228,8 +228,8 @@ ;; if we get a big long message about how we need to enable the GA API, then replace it with a short message about ;; how we need to enable the API (if-let [[_ enable-api-url] (re-find #"Enable it by visiting ([^\s]+) then retry." message)] - (tru "You must enable the Google Analytics API. Use this link to go to the Google Developers Console: {0}" - enable-api-url) + (str (tru "You must enable the Google Analytics API. Use this link to go to the Google Developers Console: {0}" + enable-api-url)) message)) diff --git a/src/metabase/driver/h2.clj b/src/metabase/driver/h2.clj index 85c6252c1c66d9a7d81def03abae7da006dc96f1..c08570eece5b9b9cf448aef5d9a3476f90b2b5ad 100644 --- a/src/metabase/driver/h2.clj +++ b/src/metabase/driver/h2.clj @@ -8,8 +8,9 @@ [metabase.db.spec :as dbspec] [metabase.driver.generic-sql :as sql] [metabase.models.database :refer [Database]] - [metabase.util.honeysql-extensions :as hx] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [honeysql-extensions :as hx] + [i18n :refer [tru]]] [toucan.db :as db])) (def ^:private ^:const column->base-type diff --git a/src/metabase/driver/mongo.clj b/src/metabase/driver/mongo.clj index 7b0c797bd62ae017b4ccbf6d13419a6066793b19..2eadd139720e03f5c455e2e7a708b59e007d721e 100644 --- a/src/metabase/driver/mongo.clj +++ b/src/metabase/driver/mongo.clj @@ -15,7 +15,7 @@ [command :as cmd] [conversion :as conv] [db :as mdb]] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util.i18n :refer [tru]] [schema.core :as s] [toucan.db :as db]) (:import com.mongodb.DB)) diff --git a/src/metabase/driver/oracle.clj b/src/metabase/driver/oracle.clj index 307b91a7581613c1fd0da07d1e9f3b669e26d92a..76d9ba1574a40dd6050b2ea5f6f8753623d26098 100644 --- a/src/metabase/driver/oracle.clj +++ b/src/metabase/driver/oracle.clj @@ -12,8 +12,8 @@ [metabase.driver.generic-sql.query-processor :as sqlqp] [metabase.util [honeysql-extensions :as hx] - [ssh :as ssh]] - [puppetlabs.i18n.core :refer [tru]])) + [i18n :refer [tru]] + [ssh :as ssh]])) (defrecord OracleDriver [] :load-ns true diff --git a/src/metabase/driver/presto.clj b/src/metabase/driver/presto.clj index 381d63a0aad495b4fc0496a8f5f7441116ccc2ed..0d904ca291f4a1c2a0bd39bbb024f2bf2e2e641d 100644 --- a/src/metabase/driver/presto.clj +++ b/src/metabase/driver/presto.clj @@ -22,11 +22,11 @@ [metabase.util [date :as du] [honeysql-extensions :as hx] - [ssh :as ssh]] - [puppetlabs.i18n.core :refer [tru]]) + [i18n :refer [tru]] + [ssh :as ssh]]) (:import java.sql.Time java.util.Date - [metabase.query_processor.interface TimeValue])) + metabase.query_processor.interface.TimeValue)) (defrecord PrestoDriver [] :load-ns true diff --git a/src/metabase/driver/redshift.clj b/src/metabase/driver/redshift.clj index ed40906ac7883a16970f7434737b05475c3b127d..09a8416949a1806763f94999a9e118e0b76177c2 100644 --- a/src/metabase/driver/redshift.clj +++ b/src/metabase/driver/redshift.clj @@ -12,8 +12,8 @@ [postgres :as postgres]] [metabase.util [honeysql-extensions :as hx] - [ssh :as ssh]] - [puppetlabs.i18n.core :refer [tru]])) + [i18n :refer [tru]] + [ssh :as ssh]])) (defn- connection-details->spec "Create a database specification for a redshift database. Opts should include diff --git a/src/metabase/driver/sparksql.clj b/src/metabase/driver/sparksql.clj index 045351ed0804ac005101ce38530e3fb1725ffe84..30287b96a4a398c4f6b6f5acd6e933096b1cb3cc 100644 --- a/src/metabase/driver/sparksql.clj +++ b/src/metabase/driver/sparksql.clj @@ -17,8 +17,9 @@ [metabase.driver.generic-sql.query-processor :as sqlqp] [metabase.models.table :refer [Table]] [metabase.query-processor.util :as qputil] - [metabase.util.honeysql-extensions :as hx] - [puppetlabs.i18n.core :refer [trs tru]]) + [metabase.util + [honeysql-extensions :as hx] + [i18n :refer [trs tru]]]) (:import clojure.lang.Reflector java.sql.DriverManager metabase.query_processor.interface.Field)) diff --git a/src/metabase/driver/sqlite.clj b/src/metabase/driver/sqlite.clj index 95a5ac30bf957aafa2ab8fc16a0605a3e4d6b2fb..a15923faeb3345eace80985a5f299be8b6767ffb 100644 --- a/src/metabase/driver/sqlite.clj +++ b/src/metabase/driver/sqlite.clj @@ -14,8 +14,8 @@ [metabase.driver.generic-sql.query-processor :as sqlqp] [metabase.util [date :as du] - [honeysql-extensions :as hx]] - [puppetlabs.i18n.core :refer [tru]] + [honeysql-extensions :as hx] + [i18n :refer [tru]]] [schema.core :as s]) (:import [java.sql Time Timestamp])) diff --git a/src/metabase/driver/sqlserver.clj b/src/metabase/driver/sqlserver.clj index 328d006a00df1987e75a1e75a6deab32c543e52f..348e28c1a6358472e2bc6d2394bd550326c1fe39 100644 --- a/src/metabase/driver/sqlserver.clj +++ b/src/metabase/driver/sqlserver.clj @@ -9,8 +9,8 @@ [metabase.driver.generic-sql.query-processor :as sqlqp] [metabase.util [honeysql-extensions :as hx] - [ssh :as ssh]] - [puppetlabs.i18n.core :refer [tru]]) + [i18n :refer [tru]] + [ssh :as ssh]]) (:import java.sql.Time)) (defrecord SQLServerDriver [] diff --git a/src/metabase/email.clj b/src/metabase/email.clj index bed7e78cdefe3a68612ff4a26b2144481ffcc931..829f25d2d07a85b5c93cb1181731dc5a7e83e593 100644 --- a/src/metabase/email.clj +++ b/src/metabase/email.clj @@ -2,11 +2,12 @@ (:require [clojure.tools.logging :as log] [metabase.models.setting :as setting :refer [defsetting]] [metabase.util :as u] - [metabase.util.schema :as su] + [metabase.util + [i18n :refer [tru trs]] + [schema :as su]] [postal [core :as postal] [support :refer [make-props]]] - [puppetlabs.i18n.core :refer [tru trs]] [schema.core :as s]) (:import javax.mail.Session)) @@ -72,7 +73,7 @@ {:style/indent 0} [{:keys [subject recipients message-type message]} :- EmailMessage] (when-not (email-smtp-host) - (let [^String msg (tru "SMTP host is not set.")] + (let [^String msg (str (tru "SMTP host is not set."))] (throw (Exception. msg)))) ;; Now send the email (send-email! (smtp-settings) diff --git a/src/metabase/integrations/ldap.clj b/src/metabase/integrations/ldap.clj index 27ba7f9cefbb92c18d28fd65708282b066d8e993..e2a9992b8edd4996e6b0b19b2ab280969d7a1bc2 100644 --- a/src/metabase/integrations/ldap.clj +++ b/src/metabase/integrations/ldap.clj @@ -8,7 +8,7 @@ [setting :as setting :refer [defsetting]] [user :as user :refer [User]]] [metabase.util :as u] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util.i18n :refer [tru]] [toucan.db :as db]) (:import [com.unboundid.ldap.sdk LDAPConnectionPool LDAPException])) diff --git a/src/metabase/integrations/slack.clj b/src/metabase/integrations/slack.clj index 9d130ec3ea48fd1f23ad549b8e52e1e881f37aac..97df78f092a442ec7b71a0ec5bdb8d8b0459303c 100644 --- a/src/metabase/integrations/slack.clj +++ b/src/metabase/integrations/slack.clj @@ -3,7 +3,7 @@ [clj-http.client :as http] [clojure.tools.logging :as log] [metabase.models.setting :as setting :refer [defsetting]] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util.i18n :refer [tru]] [metabase.util :as u])) ;; Define a setting which captures our Slack api token diff --git a/src/metabase/metabot.clj b/src/metabase/metabot.clj index 0134657b8a8b60d812c971e907ec90695d556598..90f25595b0d9a9c42ae4fed61f64d1f240a07a7c 100644 --- a/src/metabase/metabot.clj +++ b/src/metabase/metabot.clj @@ -24,8 +24,8 @@ [setting :as setting :refer [defsetting]]] [metabase.util [date :as du] + [i18n :refer [trs tru]] [urls :as urls]] - [puppetlabs.i18n.core :refer [trs tru]] [throttle.core :as throttle] [toucan.db :as db])) @@ -173,21 +173,21 @@ dispatch-token) varr}))] (fn dispatch* ([] - (keys-description (tru "Here''s what I can {0}:" verb) fn-map)) + (keys-description (str (tru "Here''s what I can {0}:" verb)) fn-map)) ([what & args] (if-let [f (fn-map (keyword what))] (apply f args) - (tru "I don''t know how to {0} `{1}`.\n{2}" - verb - (if (instance? clojure.lang.Named what) - (name what) - what) - (dispatch*))))))) + (str (tru "I don''t know how to {0} `{1}`.\n{2}" + verb + (if (instance? clojure.lang.Named what) + (name what) + what) + (dispatch*)))))))) (defn- format-exception "Format a `Throwable` the way we'd like for posting it on slack." [^Throwable e] - (tru "Uh oh! :cry:\n> {0}" (.getMessage e))) + (str (tru "Uh oh! :cry:\n> {0}" (.getMessage e)))) (defmacro ^:private do-async {:style/indent 0} [& body] `(future (try ~@body @@ -207,7 +207,7 @@ [& _] (let [cards (with-metabot-permissions (filterv mi/can-read? (db/select [Card :id :name :dataset_query :collection_id], {:order-by [[:id :desc]], :limit 20})))] - (tru "Here''s your {0} most recent cards:\n{1}" (count cards) (format-cards cards)))) + (str (tru "Here''s your {0} most recent cards:\n{1}" (count cards) (format-cards cards))))) (defn- card-with-name [card-name] (first (u/prog1 (db/select [Card :id :name], :%lower.name [:like (str \% (str/lower-case card-name) \%)]) @@ -229,7 +229,7 @@ (defn ^:metabot show "Implementation of the `metabot show card <name-or-id>` command." ([] - (tru "Show which card? Give me a part of a card name or its ID and I can show it to you. If you don''t know which card you want, try `metabot list`.")) + (str (tru "Show which card? Give me a part of a card name or its ID and I can show it to you. If you don''t know which card you want, try `metabot list`."))) ([card-id-or-name] (if-let [{card-id :id} (id-or-name->card card-id-or-name)] (do @@ -241,7 +241,7 @@ (slack/post-chat-message! *channel-id* nil attachments))) - (tru "Ok, just a second...")) + (str (tru "Ok, just a second..."))) (throw (Exception. (str (tru "Not Found")))))) ;; If the card name comes without spaces, e.g. (show 'my 'wacky 'card) turn it into a string an recur: (show "my ;; wacky card") diff --git a/src/metabase/middleware.clj b/src/metabase/middleware.clj index 8601269a8d5bda38d4c1988f0d55d8a57b2af2e0..0491fb69ea6641cc55fe19d145ff1be15d8c40e1 100644 --- a/src/metabase/middleware.clj +++ b/src/metabase/middleware.clj @@ -15,8 +15,9 @@ [session :refer [Session]] [setting :refer [defsetting]] [user :as user :refer [User]]] - [metabase.util.date :as du] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [date :as du] + [i18n :as ui18n :refer [tru]]] [toucan.db :as db]) (:import com.fasterxml.jackson.core.JsonGenerator java.sql.SQLException)) @@ -393,11 +394,11 @@ ;; Field validation exceptions. Return those as is (and status-code (seq other-info)) - other-info + (ui18n/localized-strings->strings other-info) ;; If status code was specified but other data wasn't, it's something like a ;; 404. Return message as the (plain-text) body. status-code - message + (str message) ;; Otherwise it's a 500. Return a body that includes exception & filtered ;; stacktrace for debugging purposes :else @@ -412,7 +413,9 @@ #"\s*\n\s*")}))))] {:status (or status-code 500) :headers (cond-> (html-page-security-headers) - (string? body) (assoc "Content-Type" "text/plain")) + (or (string? body) + (ui18n/localized-string? body)) + (assoc "Content-Type" "text/plain")) :body body})) (defn catch-api-exceptions diff --git a/src/metabase/models/card.clj b/src/metabase/models/card.clj index dc2bbe1f138cf893c84887f77d40d366dfc75ce7..9cfb90ddc0dfc8fdb54cd92f5efadec981b7e275 100644 --- a/src/metabase/models/card.clj +++ b/src/metabase/models/card.clj @@ -17,8 +17,9 @@ [revision :as revision]] [metabase.models.query.permissions :as query-perms] [metabase.query-processor.util :as qputil] - [metabase.util.query :as q] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [i18n :as ui18n :refer [tru]] + [query :as q]] [toucan [db :as db] [models :as models]])) @@ -81,12 +82,12 @@ (ids-already-seen source-card-id) (throw - (ex-info (tru "Cannot save Question: source query has circular references.") + (ui18n/ex-info (tru "Cannot save Question: source query has circular references.") {:status-code 400})) :else (recur (or (db/select-one-field :dataset_query Card :id source-card-id) - (throw (ex-info (tru "Card {0} does not exist." source-card-id) + (throw (ui18n/ex-info (tru "Card {0} does not exist." source-card-id) {:status-code 404}))) (conj ids-already-seen source-card-id)))))) diff --git a/src/metabase/models/collection.clj b/src/metabase/models/collection.clj index a3973eea4047e8ce3ebac40bd0c50aca411441de..ea9e3ce215dbe91809a79e077050634e7bb5f8ab 100644 --- a/src/metabase/models/collection.clj +++ b/src/metabase/models/collection.clj @@ -17,8 +17,9 @@ [interface :as i] [permissions :as perms :refer [Permissions]]] [metabase.util :as u] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [trs tru]] + [metabase.util + [i18n :as ui18n :refer [trs tru]] + [schema :as su]] [schema.core :as s] [toucan [db :as db] @@ -42,13 +43,13 @@ (defn- assert-valid-hex-color [^String hex-color] (when (or (not (string? hex-color)) (not (re-matches hex-color-regex hex-color))) - (throw (ex-info (tru "Invalid color") + (throw (ui18n/ex-info (tru "Invalid color") {:status-code 400, :errors {:color (tru "must be a valid 6-character hex color code")}})))) (defn- slugify [collection-name] ;; double-check that someone isn't trying to use a blank string as the collection name (when (str/blank? collection-name) - (throw (ex-info (tru "Collection name cannot be blank!") + (throw (ui18n/ex-info (tru "Collection name cannot be blank!") {:status-code 400, :errors {:name (tru "cannot be blank")}}))) (u/slugify collection-name collection-slug-max-length)) @@ -141,20 +142,20 @@ (when (contains? collection :location) (when-not (valid-location-path? location) (throw - (ex-info (tru "Invalid Collection location: path is invalid.") + (ui18n/ex-info (tru "Invalid Collection location: path is invalid.") {:status-code 400 :errors {:location (tru "Invalid Collection location: path is invalid.")}}))) ;; if this is a Personal Collection it's only allowed to go in the Root Collection: you can't put it anywhere else! (when (contains? collection :personal_owner_id) (when-not (= location "/") (throw - (ex-info (tru "You cannot move a Personal Collection.") + (ui18n/ex-info (tru "You cannot move a Personal Collection.") {:status-code 400 :errors {:location (tru "You cannot move a Personal Collection.")}})))) ;; Also make sure that all the IDs referenced in the Location path actually correspond to real Collections (when-not (all-ids-in-location-path-are-valid? location) (throw - (ex-info (tru "Invalid Collection location: some or all ancestors do not exist.") + (ui18n/ex-info (tru "Invalid Collection location: some or all ancestors do not exist.") {:status-code 404 :errors {:location (tru "Invalid Collection location: some or all ancestors do not exist.")}}))))) @@ -188,7 +189,7 @@ "The special Root Collection placeholder object with some extra details to facilitate displaying it on the FE." [] (assoc root-collection - :name (tru "Our analytics") + :name (str (tru "Our analytics")) :id "root")) (defn- is-root-collection? [x] @@ -616,7 +617,7 @@ ;; double-check and make sure it's not just the existing value getting passed back in for whatever reason (when (api/column-will-change? :personal_owner_id collection-before-updates collection-updates) (throw - (ex-info (tru "You're not allowed to change the owner of a Personal Collection.") + (ui18n/ex-info (tru "You're not allowed to change the owner of a Personal Collection.") {:status-code 400 :errors {:personal_owner_id (tru "You're not allowed to change the owner of a Personal Collection.")}}))) ;; @@ -627,13 +628,13 @@ ;; You also definitely cannot *move* a Personal Collection (when (api/column-will-change? :location collection-before-updates collection-updates) (throw - (ex-info (tru "You're not allowed to move a Personal Collection.") + (ui18n/ex-info (tru "You're not allowed to move a Personal Collection.") {:status-code 400 :errors {:location (tru "You're not allowed to move a Personal Collection.")}}))) ;; You also can't archive a Personal Collection (when (api/column-will-change? :archived collection-before-updates collection-updates) (throw - (ex-info (tru "You cannot archive a Personal Collection.") + (ui18n/ex-info (tru "You cannot archive a Personal Collection.") {:status-code 400 :errors {:archived (tru "You cannot archive a Personal Collection.")}})))) @@ -644,7 +645,7 @@ (when (api/column-will-change? :archived collection-before-updates collection-updates) ;; check to make sure we're not trying to change location at the same time (when (api/column-will-change? :location collection-before-updates collection-updates) - (throw (ex-info (tru "You cannot move a Collection and archive it at the same time.") + (throw (ui18n/ex-info (tru "You cannot move a Collection and archive it at the same time.") {:status-code 400 :errors {:archived (tru "You cannot move a Collection and archive it at the same time.")}}))) ;; ok, go ahead and do the archive/unarchive operation @@ -966,7 +967,7 @@ ;; the same first & last name! This will *ruin* their lives :( (let [{first-name :first_name, last-name :last_name} (db/select-one ['User :first_name :last_name] :id (u/get-id user-or-id))] - (tru "{0} {1}''s Personal Collection" first-name last-name))) + (str (tru "{0} {1}''s Personal Collection" first-name last-name)))) (s/defn user->personal-collection :- CollectionInstance "Return the Personal Collection for `user-or-id`, if it already exists; if not, create it and return it." diff --git a/src/metabase/models/collection_revision.clj b/src/metabase/models/collection_revision.clj index c367c5b14b139462e18b11b2c5f5150906a49c95..f953724f2fac897776df691912a516b4de1702a2 100644 --- a/src/metabase/models/collection_revision.clj +++ b/src/metabase/models/collection_revision.clj @@ -1,7 +1,8 @@ (ns metabase.models.collection-revision (:require [metabase.util :as u] - [metabase.util.date :as du] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [date :as du] + [i18n :refer [tru]]] [toucan [db :as db] [models :as models]])) diff --git a/src/metabase/models/field_values.clj b/src/metabase/models/field_values.clj index 11119715cfdc81348acc941942f7b31947a09c29..fa2daf6528551137015cd2a7b34bc705a9b21a61 100644 --- a/src/metabase/models/field_values.clj +++ b/src/metabase/models/field_values.clj @@ -1,8 +1,9 @@ (ns metabase.models.field-values (:require [clojure.tools.logging :as log] [metabase.util :as u] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [trs]] + [metabase.util + [i18n :refer [trs]] + [schema :as su]] [schema.core :as s] [toucan [db :as db] @@ -63,10 +64,10 @@ (let [total-length (reduce + (map (comp count str) distinct-values))] (u/prog1 (<= total-length total-max-length) - (log/debug (format "Field values total length is %d (max %d)." total-length total-max-length) + (log/debug (trs "Field values total length is {0} (max {1})." total-length total-max-length) (if <> - "FieldValues are allowed for this Field." - "FieldValues are NOT allowed for this Field."))))) + (trs "FieldValues are allowed for this Field.") + (trs "FieldValues are NOT allowed for this Field.")))))) (defn- distinct-values @@ -197,6 +198,6 @@ (doseq [{table-id :table_id, :as field} fields] (when (table-id->is-on-demand? table-id) (log/debug - (format "Field %d '%s' should have FieldValues and belongs to a Database with On-Demand FieldValues updating." + (trs "Field {0} ''{1}'' should have FieldValues and belongs to a Database with On-Demand FieldValues updating." (u/get-id field) (:name field))) (create-or-update-field-values! field))))) diff --git a/src/metabase/models/humanization.clj b/src/metabase/models/humanization.clj index 4a83cb21e1ec1f40ba355dbb5d007e5c8a9d577b..51cbbd7c9cbebc2cdc47615bde20a594b3798f94 100644 --- a/src/metabase/models/humanization.clj +++ b/src/metabase/models/humanization.clj @@ -12,8 +12,9 @@ (:require [clojure.string :as str] [clojure.tools.logging :as log] [metabase.models.setting :as setting :refer [defsetting]] - [metabase.util.infer-spaces :refer [infer-spaces]] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [i18n :refer [tru]] + [infer-spaces :refer [infer-spaces]]] [toucan.db :as db])) (def ^:private ^:const acronyms diff --git a/src/metabase/models/permissions.clj b/src/metabase/models/permissions.clj index 18d69cb696625f715a4710da3ec9307dd209c6b0..9fc5c7438d5037d0b9bd1fc67749ba250fd4f973 100644 --- a/src/metabase/models/permissions.clj +++ b/src/metabase/models/permissions.clj @@ -15,8 +15,8 @@ [permissions-revision :as perms-revision :refer [PermissionsRevision]]] [metabase.util [honeysql-extensions :as hx] + [i18n :as ui18n :refer [tru]] [schema :as su]] - [puppetlabs.i18n.core :refer [tru]] [schema.core :as s] [toucan [db :as db] @@ -79,7 +79,7 @@ [{:keys [group_id]}] (when (and (= group_id (:id (group/admin))) (not *allow-admin-permissions-changes*)) - (throw (ex-info (tru "You cannot create or revoke permissions for the 'Admin' group.") + (throw (ui18n/ex-info (tru "You cannot create or revoke permissions for the ''Admin'' group.") {:status-code 400})))) (defn- assert-valid-object @@ -89,7 +89,7 @@ (not (valid-object-path? object)) (or (not= object "/") (not *allow-root-entries*))) - (throw (ex-info (tru "Invalid permissions object path: ''{0}''." object) + (throw (ui18n/ex-info (tru "Invalid permissions object path: ''{0}''." object) {:status-code 400})))) (defn- assert-valid @@ -555,8 +555,8 @@ Return a 409 (Conflict) if the numbers don't match up." [old-graph new-graph] (when (not= (:revision old-graph) (:revision new-graph)) - (throw (ex-info (str (tru "Looks like someone else edited the permissions and your data is out of date.") - (tru "Please fetch new data and try again.")) + (throw (ui18n/ex-info (str (tru "Looks like someone else edited the permissions and your data is out of date.") + (tru "Please fetch new data and try again.")) {:status-code 409})))) (defn- save-perms-revision! diff --git a/src/metabase/models/permissions_group.clj b/src/metabase/models/permissions_group.clj index 34621c2193e818af21896541cdd826710c5a13f9..bc3072e0897932bd61520057df2eda5cd9a052b0 100644 --- a/src/metabase/models/permissions_group.clj +++ b/src/metabase/models/permissions_group.clj @@ -10,7 +10,7 @@ [clojure.tools.logging :as log] [metabase.models.setting :as setting] [metabase.util :as u] - [puppetlabs.i18n.core :refer [trs tru]] + [metabase.util.i18n :as ui18n :refer [trs tru]] [toucan [db :as db] [models :as models]])) @@ -26,8 +26,8 @@ :name group-name) (u/prog1 (db/insert! PermissionsGroup :name group-name) - (log/info (u/format-color 'green (trs "Created magic permissions group ''{0}'' (ID = {1})" - group-name (:id <>))))))))) + (log/info (u/format-color 'green (trs "Created magic permissions group ''{0}'' (ID = {1})" + group-name (:id <>))))))))) (def ^{:arglists '([])} ^metabase.models.permissions_group.PermissionsGroupInstance all-users @@ -57,7 +57,7 @@ (defn- check-name-not-already-taken [group-name] (when (exists-with-name? group-name) - (throw (ex-info (tru "A group with that name already exists.") {:status-code 400})))) + (throw (ui18n/ex-info (tru "A group with that name already exists.") {:status-code 400})))) (defn- check-not-magic-group "Make sure we're not trying to edit/delete one of the magic groups, or throw an exception." @@ -67,7 +67,7 @@ (admin) (metabot)]] (when (= id (:id magic-group)) - (throw (ex-info (tru "You cannot edit or delete the ''{0}'' permissions group!" (:name magic-group)) + (throw (ui18n/ex-info (tru "You cannot edit or delete the ''{0}'' permissions group!" (:name magic-group)) {:status-code 400}))))) diff --git a/src/metabase/models/permissions_group_membership.clj b/src/metabase/models/permissions_group_membership.clj index 87e90ddd1279fe43655b6c9e67ac7b626cb3a48f..83bf95456c4c91de6e0837dbcc5bc11dc71d0a59 100644 --- a/src/metabase/models/permissions_group_membership.clj +++ b/src/metabase/models/permissions_group_membership.clj @@ -1,7 +1,7 @@ (ns metabase.models.permissions-group-membership (:require [metabase.models.permissions-group :as group] [metabase.util :as u] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util.i18n :as ui18n :refer [tru]] [toucan [db :as db] [models :as models]])) @@ -12,8 +12,8 @@ "Throw an Exception if we're trying to add or remove a user to the MetaBot group." [group-id] (when (= group-id (:id (group/metabot))) - (throw (ex-info (tru "You cannot add or remove users to/from the 'MetaBot' group.") - {:status-code 400})))) + (throw (ui18n/ex-info (tru "You cannot add or remove users to/from the ''MetaBot'' group.") + {:status-code 400})))) (def ^:dynamic ^Boolean *allow-changing-all-users-group-members* "Should we allow people to be added to or removed from the All Users permissions group? @@ -25,14 +25,14 @@ [group-id] (when (= group-id (:id (group/all-users))) (when-not *allow-changing-all-users-group-members* - (throw (ex-info (tru "You cannot add or remove users to/from the 'All Users' group.") + (throw (ui18n/ex-info (tru "You cannot add or remove users to/from the ''All Users'' group.") {:status-code 400}))))) (defn- check-not-last-admin [] (when (<= (db/count PermissionsGroupMembership :group_id (:id (group/admin))) 1) - (throw (ex-info (tru "You cannot remove the last member of the 'Admin' group!") + (throw (ui18n/ex-info (tru "You cannot remove the last member of the ''Admin'' group!") {:status-code 400})))) (defn- pre-delete [{:keys [group_id user_id]}] diff --git a/src/metabase/models/permissions_revision.clj b/src/metabase/models/permissions_revision.clj index 07ea81ff83b71bde58be7cfd7555144581b356a5..d28f3c207c904681185e050ca7164c48f569824f 100644 --- a/src/metabase/models/permissions_revision.clj +++ b/src/metabase/models/permissions_revision.clj @@ -1,7 +1,8 @@ (ns metabase.models.permissions-revision (:require [metabase.util :as u] - [metabase.util.date :as du] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [date :as du] + [i18n :refer [tru]]] [toucan [db :as db] [models :as models]])) diff --git a/src/metabase/models/pulse.clj b/src/metabase/models/pulse.clj index 6d9816825f387beccd93e54c92ed69ab924c7b3a..d3ca3f0e4d293028e963ed6cbdc44c2221949ed0 100644 --- a/src/metabase/models/pulse.clj +++ b/src/metabase/models/pulse.clj @@ -27,8 +27,9 @@ [pulse-card :refer [PulseCard]] [pulse-channel :as pulse-channel :refer [PulseChannel]] [pulse-channel-recipient :refer [PulseChannelRecipient]]] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [i18n :refer [tru]] + [schema :as su]] [schema.core :as s] [toucan [db :as db] diff --git a/src/metabase/models/query/permissions.clj b/src/metabase/models/query/permissions.clj index 5bb219184acd0f3d1be96eff9b1b1efc8be2590c..e8f7f5896d8eb995c45e72ba09a077070066c296 100644 --- a/src/metabase/models/query/permissions.clj +++ b/src/metabase/models/query/permissions.clj @@ -8,8 +8,9 @@ [permissions :as perms]] [metabase.query-processor.util :as qputil] [metabase.util :as u] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [i18n :refer [tru]] + [schema :as su]] [schema.core :as s] [toucan.db :as db])) diff --git a/src/metabase/models/query_execution.clj b/src/metabase/models/query_execution.clj index 4fda12dd6fe27f78de5412dbf33d54592963fa05..e61d70dd73b7c2dbbbee7f0ca160d084c37afd9c 100644 --- a/src/metabase/models/query_execution.clj +++ b/src/metabase/models/query_execution.clj @@ -1,6 +1,6 @@ (ns metabase.models.query-execution (:require [metabase.util :as u] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util.i18n :refer [tru]] [schema.core :as s] [toucan.models :as models])) diff --git a/src/metabase/models/revision.clj b/src/metabase/models/revision.clj index c79fd82e03c8938093b60915c001399394fb0430..e09f733d41f7eeabd6a98745dce2ed56f8da5464 100644 --- a/src/metabase/models/revision.clj +++ b/src/metabase/models/revision.clj @@ -3,8 +3,9 @@ [metabase.models.revision.diff :refer [diff-string]] [metabase.models.user :refer [User]] [metabase.util :as u] - [metabase.util.date :as du] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [date :as du] + [i18n :refer [tru]]] [toucan [db :as db] [hydrate :refer [hydrate]] diff --git a/src/metabase/models/setting.clj b/src/metabase/models/setting.clj index 44dc63ac360b9909a123a57e7800696dd23ed45e..2b7213bd0bace7e7e240f3378e832b8bc6392924 100644 --- a/src/metabase/models/setting.clj +++ b/src/metabase/models/setting.clj @@ -44,8 +44,8 @@ [util :as u]] [metabase.util [date :as du] - [honeysql-extensions :as hx]] - [puppetlabs.i18n.core :refer [trs tru]] + [honeysql-extensions :as hx] + [i18n :as ui18n :refer [trs tru]]] [schema.core :as s] [toucan [db :as db] @@ -75,7 +75,7 @@ (def ^:private SettingDefinition {:name s/Keyword - :description s/Str ; used for docstring and is user-facing in the admin panel + :description s/Any ; description is validated via the macro, not schema :default s/Any :type Type ; all values are stored in DB as Strings, :getter clojure.lang.IFn ; different getters/setters take care of parsing/unparsing @@ -147,7 +147,8 @@ (db/simple-insert! Setting :key settings-last-updated-key, :value current-timestamp-as-string-honeysql) (catch java.sql.SQLException e ;; go ahead and log the Exception anyway on the off chance that it *wasn't* just a race condition issue - (log/error (tru "Error inserting a new Setting:") (with-out-str (jdbc/print-sql-exception-chain e))))))) + (log/error (trs "Error inserting a new Setting: {0}" + (with-out-str (jdbc/print-sql-exception-chain e)))))))) ;; Now that we updated the value in the DB, go ahead and update our cached value as well, because we know about the ;; changes (swap! cache assoc settings-last-updated-key (db/select-one-field :value Setting :key settings-last-updated-key))) @@ -176,7 +177,7 @@ [:> :value last-known-update]]}) (when <> (log/info (u/format-color 'red - (trs "Settings have been changed on another instance, and will be reloaded here."))))))))) + (str (trs "Settings have been changed on another instance, and will be reloaded here.")))))))))) (def ^:private cache-update-check-interval-ms "How often we should check whether the Settings cache is out of date (which requires a DB call)?" @@ -459,7 +460,7 @@ "Register a new Setting with a map of `SettingDefinition` attributes. This is used internally be `defsetting`; you shouldn't need to use it yourself." [{setting-name :name, setting-type :type, default :default, :as setting}] - (u/prog1 (let [setting-type (s/validate Type (or setting-type :string))] + (u/prog1 (let [setting-type (s/validate Type (or setting-type :string))] (merge {:name setting-name :description nil :type setting-type @@ -518,6 +519,33 @@ ;; :refer-clojure :exclude doesn't seem to work in this case (metabase.models.setting/set! setting new-value)))) +(defn- expr-of-sym? [symbols expr] + (when-let [first-sym (and (coll? expr) + (first expr))] + (some #(= first-sym %) symbols))) + +(defn- valid-trs-or-tru? [desc] + (expr-of-sym? ['trs 'tru `trs `tru] desc)) + +(defn- valid-str-of-trs-or-tru? [maybe-str-expr] + (when (expr-of-sym? ['str `str] maybe-str-expr) + ;; When there are several i18n'd sentences, there will probably be a surrounding `str` invocation and a space in + ;; between the sentences, remove those to validate the i18n clauses + (let [exprs-without-strs (remove (every-pred string? str/blank?) (rest maybe-str-expr))] + ;; We should have at lease 1 i18n clause, so ensure `exprs-without-strs` is not empty + (and (seq exprs-without-strs) + (every? valid-trs-or-tru? exprs-without-strs))))) + +(defn- validate-description + "Validates the description expression `desc-expr`, ensuring it contains an i18n form, or a string consisting of 1 or more i18n forms" + [desc] + (when-not (or (valid-trs-or-tru? desc) + (valid-str-of-trs-or-tru? desc)) + (throw (IllegalArgumentException. + (str (trs "defsetting descriptions strings must be `:internal?` or internationalized, found: `{0}`" + (pr-str desc)))))) + desc) + (defmacro defsetting "Defines a new Setting that will be added to the DB at some point in the future. Conveniently can be used as a getter/setter as well: @@ -549,9 +577,12 @@ {:style/indent 1} [setting-symb description & {:as options}] {:pre [(symbol? setting-symb)]} - `(let [setting# (register-setting! (assoc ~options + `(let [desc# ~(if (:internal? options) + description + (validate-description description)) + setting# (register-setting! (assoc ~options :name ~(keyword setting-symb) - :description ~description))] + :description desc#))] (-> (def ~setting-symb (setting-fn setting#)) (alter-meta! merge (metadata-for-setting-fn setting#))))) @@ -586,7 +617,7 @@ v) :is_env_setting (boolean env-value) :env_name (env-var-name setting) - :description (:description setting) + :description (str (:description setting)) :default (or (when env-value (format "Using $%s" (env-var-name setting))) (:default setting))})) diff --git a/src/metabase/models/user.clj b/src/metabase/models/user.clj index 858558ab24621cb0c2054f02718af6fe5428580f..6f2dd9a005130d342700741ba7bbf3ce56e622eb 100644 --- a/src/metabase/models/user.clj +++ b/src/metabase/models/user.clj @@ -13,8 +13,8 @@ [permissions-group-membership :as perm-membership :refer [PermissionsGroupMembership]]] [metabase.util [date :as du] + [i18n :refer [tru]] [schema :as su]] - [puppetlabs.i18n.core :refer [tru]] [schema.core :as s] [toucan [db :as db] diff --git a/src/metabase/plugins.clj b/src/metabase/plugins.clj index 0ee78b8d8fdf7e4d0a30948ff1dda0292274b0e5..a7933a560ee0c73e9c57a7fe60d57c49224a8946 100644 --- a/src/metabase/plugins.clj +++ b/src/metabase/plugins.clj @@ -8,7 +8,7 @@ [metabase [config :as config] [util :as u]] - [puppetlabs.i18n.core :refer [trs]])) + [metabase.util.i18n :refer [trs]])) ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | Java 8 | @@ -43,7 +43,7 @@ :when (and (.isFile file) (.canRead file) (re-find #"\.jar$" (.getPath file)))] - (log/info (u/format-color 'magenta (str (trs "Loading plugin {0}... " file) (u/emoji "🔌")))) + (log/info (u/format-color 'magenta (trs "Loading plugin {0}... " file) (u/emoji "🔌"))) (add-jar-to-classpath! file)))) diff --git a/src/metabase/public_settings.clj b/src/metabase/public_settings.clj index 643c05315696671011415f1d9ba5eb663ecabbcd..34c0c73394cd96da4e83ba4d2637c07c285fe51e 100644 --- a/src/metabase/public_settings.clj +++ b/src/metabase/public_settings.clj @@ -8,9 +8,8 @@ [setting :as setting :refer [defsetting]]] [metabase.public-settings.metastore :as metastore] [metabase.util - [i18n :refer [available-locales-with-names set-locale]] + [i18n :refer [available-locales-with-names set-locale tru]] [password :as password]] - [puppetlabs.i18n.core :refer [tru]] [toucan.db :as db]) (:import [java.util TimeZone UUID])) diff --git a/src/metabase/public_settings/metastore.clj b/src/metabase/public_settings/metastore.clj index ed9a6685141d5b38cbed0f96ca837ffe1944db3e..6d61738fc5df38087d987b1729eca0c7442670d5 100644 --- a/src/metabase/public_settings/metastore.clj +++ b/src/metabase/public_settings/metastore.clj @@ -8,8 +8,9 @@ [config :as config] [util :as u]] [metabase.models.setting :as setting :refer [defsetting]] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [trs tru]] + [metabase.util + [i18n :refer [trs tru]] + [schema :as su]] [schema.core :as s])) (def ^:private ValidToken @@ -48,15 +49,15 @@ ;; slurp will throw a FileNotFoundException for 404s, so in that case just return an appropriate ;; 'Not Found' message (catch java.io.FileNotFoundException e - {:valid false, :status (tru "Unable to validate token.")}) + {:valid false, :status (str (tru "Unable to validate token."))}) ;; if there was any other error fetching the token, log it and return a generic message about the ;; token being invalid. This message will get displayed in the Settings page in the admin panel so ;; we do not want something complicated (catch Throwable e (log/error e (trs "Error fetching token status:")) - {:valid false, :status (tru "There was an error checking whether this token was valid.")}))) + {:valid false, :status (str (tru "There was an error checking whether this token was valid."))}))) fetch-token-status-timeout-ms - {:valid false, :status (tru "Token validation timed out.")}))) + {:valid false, :status (str (tru "Token validation timed out."))}))) (defn- check-embedding-token-is-valid* [token] (when (s/check ValidToken token) diff --git a/src/metabase/pulse.clj b/src/metabase/pulse.clj index aa2d7f8d484f57821d6702b9c4748ffe6ebd1c5b..33a81d5ad66a75f5a17462074b9c36597533c182 100644 --- a/src/metabase/pulse.clj +++ b/src/metabase/pulse.clj @@ -13,9 +13,9 @@ [pulse :refer [Pulse]]] [metabase.pulse.render :as render] [metabase.util + [i18n :refer [trs tru]] [ui-logic :as ui] [urls :as urls]] - [puppetlabs.i18n.core :refer [trs tru]] [schema.core :as s] [toucan.db :as db]) (:import java.util.TimeZone @@ -131,7 +131,7 @@ (goal-met? alert results) :else - (let [^String error-text (tru "Unrecognized alert with condition ''{0}''" alert_condition)] + (let [^String error-text (str (tru "Unrecognized alert with condition ''{0}''" alert_condition))] (throw (IllegalArgumentException. error-text))))) (defmethod should-send-notification? :pulse @@ -188,7 +188,7 @@ (defmethod create-notification :default [_ _ {:keys [channel_type] :as channel}] - (let [^String ex-msg (tru "Unrecognized channel type {0}" (pr-str channel_type))] + (let [^String ex-msg (str (tru "Unrecognized channel type {0}" (pr-str channel_type)))] (throw (UnsupportedOperationException. ex-msg)))) (defmulti ^:private send-notification! diff --git a/src/metabase/pulse/color.clj b/src/metabase/pulse/color.clj index bfe0a38e1610ac7df39e98c4fb049c320c633866..bd35e17e236190c23a897f8f951410305d562f96 100644 --- a/src/metabase/pulse/color.clj +++ b/src/metabase/pulse/color.clj @@ -2,7 +2,7 @@ "Namespaces that uses the Nashorn javascript engine to invoke some shared javascript code that we use to determine the background color of pulse table cells" (:require [cheshire.core :as json] - [puppetlabs.i18n.core :refer [trs]] + [metabase.util.i18n :refer [trs]] [schema.core :as s]) (:import java.io.InputStream [javax.script Invocable ScriptEngine ScriptEngineManager] diff --git a/src/metabase/pulse/render.clj b/src/metabase/pulse/render.clj index b156769977a9025d302e46518458afe17cf89ffb..c720b8166fa77d98fb4aa461ae3d21f12397d642 100644 --- a/src/metabase/pulse/render.clj +++ b/src/metabase/pulse/render.clj @@ -14,9 +14,9 @@ [metabase.util :as u] [metabase.util [date :as du] + [i18n :refer [trs tru]] [ui-logic :as ui-logic] [urls :as urls]] - [puppetlabs.i18n.core :refer [trs tru]] [schema.core :as s]) (:import cz.vutbr.web.css.MediaSpec [java.awt BasicStroke Color Dimension RenderingHints] @@ -548,7 +548,7 @@ (* 2 sparkline-dot-radius) (* 2 sparkline-dot-radius))) (when-not (ImageIO/write image "png" os) ; returns `true` if successful -- see JavaDoc - (let [^String msg (tru "No appropriate image writer found!")] + (let [^String msg (str (tru "No appropriate image writer found!"))] (throw (Exception. msg)))) (.toByteArray os))) @@ -777,7 +777,7 @@ [render-type timezone card {:keys [data error]}] (try (when error - (let [^String msg (tru "Card has errors: {0}" error)] + (let [^String msg (str (tru "Card has errors: {0}" error))] (throw (Exception. msg)))) (case (detect-pulse-card-type card data) :empty (render:empty render-type card data) diff --git a/src/metabase/query_processor/middleware/fetch_source_query.clj b/src/metabase/query_processor/middleware/fetch_source_query.clj index 997d146e13c6bcdf8929272a2954c5f6cb7cfbd1..14fdcb57c6b3a39eefc02187dc54988a42f794d8 100644 --- a/src/metabase/query_processor/middleware/fetch_source_query.clj +++ b/src/metabase/query_processor/middleware/fetch_source_query.clj @@ -6,7 +6,7 @@ [interface :as i] [util :as qputil]] [metabase.util :as u] - [puppetlabs.i18n.core :refer [trs]] + [metabase.util.i18n :refer [trs]] [toucan.db :as db])) (defn- trim-query diff --git a/src/metabase/query_processor/middleware/parameters/sql.clj b/src/metabase/query_processor/middleware/parameters/sql.clj index bfcc36768c311f99f30df3f48c49f06cf78766c2..c30e5c09034f29305ca965aa217e3a8f105d0134 100644 --- a/src/metabase/query_processor/middleware/parameters/sql.clj +++ b/src/metabase/query_processor/middleware/parameters/sql.clj @@ -13,8 +13,8 @@ [metabase.query-processor.middleware.parameters.dates :as date-params] [metabase.util [date :as du] + [i18n :as ui18n :refer [tru]] [schema :as su]] - [puppetlabs.i18n.core :refer [tru]] [schema.core :as s] [toucan.db :as db]) (:import clojure.lang.Keyword @@ -497,8 +497,8 @@ [prefix & segmented-strings] (str/split s begin-pattern)] (when-let [^String msg (and (seq segmented-strings) (not-every? #(str/index-of % delimited-end) segmented-strings) - (tru "Found ''{0}'' with no terminating ''{1}'' in query ''{2}''" - delimited-begin delimited-end s))] + (str (tru "Found ''{0}'' with no terminating ''{1}'' in query ''{2}''" + delimited-begin delimited-end s)))] (throw (IllegalArgumentException. msg))) {:prefix prefix :delimited-strings (for [segmented-string segmented-strings @@ -535,8 +535,8 @@ [s param-key->value] (let [results (parse-params s param-key->value)] (if-let [{:keys [param-key]} (m/find-first no-value-param? results)] - (throw (ex-info (tru "Unable to substitute ''{0}'': param not specified.\nFound: {1}" - (name param-key) (pr-str (map name (keys param-key->value)))) + (throw (ui18n/ex-info (tru "Unable to substitute ''{0}'': param not specified.\nFound: {1}" + (name param-key) (pr-str (map name (keys param-key->value)))) {:status-code 400})) results))) diff --git a/src/metabase/query_processor/middleware/permissions.clj b/src/metabase/query_processor/middleware/permissions.clj index 931bd91917b1d8d4820ecface7e9bdd50babc0cd..3fc51fb0f1ee42ff7cb7b02a3d7889e07c066c7b 100644 --- a/src/metabase/query_processor/middleware/permissions.clj +++ b/src/metabase/query_processor/middleware/permissions.clj @@ -6,8 +6,9 @@ [interface :as mi] [permissions :as perms]] [metabase.models.query.permissions :as query-perms] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util + [i18n :refer [tru]] + [schema :as su]] [schema.core :as s] [toucan.db :as db])) diff --git a/src/metabase/routes.clj b/src/metabase/routes.clj index e1f76ef64c632ef0fde256d30ed11808a4db4665..f0edc0493f2b92f15b26ab204f68f81eb0bd112f 100644 --- a/src/metabase/routes.clj +++ b/src/metabase/routes.clj @@ -16,7 +16,7 @@ [routes :as api]] [metabase.core.initialization-status :as init-status] [metabase.util.embed :as embed] - [puppetlabs.i18n.core :refer [trs *locale*]] + [puppetlabs.i18n.core :refer [*locale*]] [ring.util.response :as resp] [stencil.core :as stencil])) diff --git a/src/metabase/sync/analyze.clj b/src/metabase/sync/analyze.clj index dc2a298bd4fce60430ff6f5ab2cd53d74f57a9c2..9c249104b3a484e890e5d154f8a9af317d32e0cd 100644 --- a/src/metabase/sync/analyze.clj +++ b/src/metabase/sync/analyze.clj @@ -13,8 +13,9 @@ [fingerprint :as fingerprint] #_[table-row-count :as table-row-count]] [metabase.util :as u] - [metabase.util.date :as du] - [puppetlabs.i18n.core :refer [trs]] + [metabase.util + [date :as du] + [i18n :refer [trs]]] [schema.core :as s] [toucan.db :as db])) diff --git a/src/metabase/sync/analyze/fingerprint/fingerprinters.clj b/src/metabase/sync/analyze/fingerprint/fingerprinters.clj index 867a0f9104cca58594ca37b8f4ddf7be744fedb1..b77744c26f933bef6506fcdca6300bc030262719 100644 --- a/src/metabase/sync/analyze/fingerprint/fingerprinters.clj +++ b/src/metabase/sync/analyze/fingerprint/fingerprinters.clj @@ -7,8 +7,9 @@ [metabase.sync.analyze.classifiers.name :as classify.name] [metabase.sync.util :as sync-util] [metabase.util :as u] - [metabase.util.date :as du] - [puppetlabs.i18n.core :as i18n :refer [trs]] + [metabase.util + [date :as du] + [i18n :refer [trs]]] [redux.core :as redux]) (:import com.clearspring.analytics.stream.cardinality.HyperLogLogPlus org.joda.time.DateTime)) @@ -123,7 +124,7 @@ ~transducer (fn [fingerprint#] {:type {~(first field-type) fingerprint#}}))) - (trs "Error generating fingerprint for {0}" (sync-util/name-for-logging field#)))))) + (str (trs "Error generating fingerprint for {0}" (sync-util/name-for-logging field#))))))) (defn- earliest ([] (java.util.Date. Long/MAX_VALUE)) diff --git a/src/metabase/sync/field_values.clj b/src/metabase/sync/field_values.clj index b27d6844879a4a42605545cb10443eec491384d0..24b3c6f8366951647e682d89f81708b475193423 100644 --- a/src/metabase/sync/field_values.clj +++ b/src/metabase/sync/field_values.clj @@ -8,7 +8,7 @@ [interface :as i] [util :as sync-util]] [metabase.util :as u] - [puppetlabs.i18n.core :refer [trs]] + [metabase.util.i18n :refer [trs]] [schema.core :as s] [toucan.db :as db])) diff --git a/src/metabase/sync/sync_metadata.clj b/src/metabase/sync/sync_metadata.clj index ff191a50b30fffcfd1937864a103a0f34f652c09..a610f00fd21a6c8343a8568813a63528c56e2abe 100644 --- a/src/metabase/sync/sync_metadata.clj +++ b/src/metabase/sync/sync_metadata.clj @@ -15,7 +15,7 @@ [metabase-metadata :as metabase-metadata] [sync-timezone :as sync-tz] [tables :as sync-tables]] - [puppetlabs.i18n.core :refer [trs]] + [metabase.util.i18n :refer [trs]] [schema.core :as s])) (defn- sync-fields-summary [{:keys [total-fields updated-fields] :as step-info}] diff --git a/src/metabase/sync/util.clj b/src/metabase/sync/util.clj index b77095bf988be256f7693b8402c6a393988dd24b..40d9a4796cd9c23305ab45ce518cd6fa97843f76 100644 --- a/src/metabase/sync/util.clj +++ b/src/metabase/sync/util.clj @@ -20,8 +20,8 @@ [metabase.sync.interface :as i] [metabase.util [date :as du] + [i18n :refer [trs]] [schema :as su]] - [puppetlabs.i18n.core :refer [trs]] [ring.util.codec :as codec] [schema.core :as s] [taoensso.nippy :as nippy] @@ -262,19 +262,19 @@ (extend-protocol INameForLogging i/DatabaseInstance (name-for-logging [{database-name :name, id :id, engine :engine,}] - (trs "{0} Database {1} ''{2}''" (name engine) (or id "") database-name)) + (str (trs "{0} Database {1} ''{2}''" (name engine) (or id "") database-name))) i/TableInstance (name-for-logging [{schema :schema, id :id, table-name :name}] - (trs "Table {0} ''{1}''" (or id "") (str (when (seq schema) (str schema ".")) table-name))) + (str (trs "Table {0} ''{1}''" (or id "") (str (when (seq schema) (str schema ".")) table-name)))) i/FieldInstance (name-for-logging [{field-name :name, id :id}] - (trs "Field {0} ''{1}''" (or id "") field-name)) + (str (trs "Field {0} ''{1}''" (or id "") field-name))) i/ResultColumnMetadataInstance (name-for-logging [{field-name :name}] - (trs "Field ''{0}''" field-name))) + (str (trs "Field ''{0}''" field-name)))) (defn calculate-hash "Calculate a cryptographic hash on `clj-data` and return that hash as a string" @@ -338,7 +338,7 @@ ([step-name sync-fn log-summary-fn] {:sync-fn sync-fn :step-name step-name - :log-summary-fn log-summary-fn})) + :log-summary-fn (comp str log-summary-fn)})) (defn- datetime->str [datetime] (du/->iso-8601-datetime datetime "UTC")) @@ -348,9 +348,9 @@ [database :- i/DatabaseInstance {:keys [step-name sync-fn log-summary-fn] :as step} :- StepDefinition] (let [start-time (time/now) - results (with-start-and-finish-debug-logging (trs "step ''{0}'' for {1}" - step-name - (name-for-logging database)) + results (with-start-and-finish-debug-logging (str (trs "step ''{0}'' for {1}" + step-name + (name-for-logging database))) #(sync-fn database)) end-time (time/now)] [step-name (assoc results @@ -367,28 +367,28 @@ ;; call. Constructing the log below requires some work, no need to incur that cost debug logging isn't enabled (log/debug (str - (format + (apply format (str "\n#################################################################\n" "# %s\n" "# %s\n" "# %s\n" "# %s\n") - (trs "Completed {0} on {1}" operation (:name database)) - (trs "Start: {0}" (datetime->str start-time)) - (trs "End: {0}" (datetime->str end-time)) - (trs "Duration: {0}" (calculate-duration-str start-time end-time))) - (apply str (for [[step-name {:keys [start-time end-time log-summary-fn] :as step-info}] steps] - (format (str "# ---------------------------------------------------------------\n" + (map str [(trs "Completed {0} on {1}" operation (:name database)) + (trs "Start: {0}" (datetime->str start-time)) + (trs "End: {0}" (datetime->str end-time)) + (trs "Duration: {0}" (calculate-duration-str start-time end-time))])) + (apply str (for [[step-name {:keys [start-time end-time duration log-summary-fn] :as step-info}] steps] + (apply format (str "# ---------------------------------------------------------------\n" "# %s\n" "# %s\n" "# %s\n" "# %s\n" (when log-summary-fn (format "# %s\n" (log-summary-fn step-info)))) - (trs "Completed step ''{0}''" step-name) - (trs "Start: {0}" (datetime->str start-time)) - (trs "End: {0}" (datetime->str end-time)) - (trs "Duration: {0}" (calculate-duration-str start-time end-time))))) + (map str [(trs "Completed step ''{0}''" step-name) + (trs "Start: {0}" (datetime->str start-time)) + (trs "End: {0}" (datetime->str end-time)) + (trs "Duration: {0}" (calculate-duration-str start-time end-time))])))) "#################################################################\n"))) (def ^:private SyncOperationOrStepRunMetadata diff --git a/src/metabase/task.clj b/src/metabase/task.clj index f3e475749d4627249e1ef052eb4d5069f0931c3e..2bb14b8dc1d1b328f12e931e099abbf49c785059 100644 --- a/src/metabase/task.clj +++ b/src/metabase/task.clj @@ -12,7 +12,7 @@ [metabase [db :as mdb] [util :as u]] - [puppetlabs.i18n.core :refer [trs]] + [metabase.util.i18n :refer [trs]] [schema.core :as s]) (:import [org.quartz JobDetail JobKey Scheduler Trigger TriggerKey])) diff --git a/src/metabase/util.clj b/src/metabase/util.clj index df109aeb32100f203fa16fb26b56754e01d3951d..7f99e67b89e9287e65004158e82331e1aaa1e7aa 100644 --- a/src/metabase/util.clj +++ b/src/metabase/util.clj @@ -10,7 +10,7 @@ [clojure.tools.namespace.find :as ns-find] [colorize.core :as colorize] [metabase.config :as config] - [puppetlabs.i18n.core :as i18n :refer [trs]] + [metabase.util.i18n :refer [trs]] [ring.util.codec :as codec]) (:import [java.net InetAddress InetSocketAddress Socket] [java.text Normalizer Normalizer$Form])) diff --git a/src/metabase/util/date.clj b/src/metabase/util/date.clj index 7a84803ba18e08ee317ac3f965c6c3d57c188409..31e2ef008d1a8e0ca4aeb1a4c5a4024af9b6e683 100644 --- a/src/metabase/util/date.clj +++ b/src/metabase/util/date.clj @@ -7,8 +7,9 @@ [clojure.math.numeric-tower :as math] [clojure.tools.logging :as log] [metabase.util :as u] - [metabase.util.schema :as su] - [puppetlabs.i18n.core :refer [trs]] + [metabase.util + [i18n :refer [trs]] + [schema :as su]] [schema.core :as s]) (:import clojure.lang.Keyword [java.sql Time Timestamp] diff --git a/src/metabase/util/embed.clj b/src/metabase/util/embed.clj index d38571bd20f9ae644853635fae04ccec71a3c375..51ec4130c6bca09cb1dbf8f08800554ae554b2d1 100644 --- a/src/metabase/util/embed.clj +++ b/src/metabase/util/embed.clj @@ -9,7 +9,7 @@ [hiccup.core :refer [html]] [metabase.models.setting :as setting] [metabase.public-settings :as public-settings] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util.i18n :refer [tru]] [ring.util.codec :as codec])) ;;; ------------------------------------------------------------ PUBLIC LINKS UTIL FNS ------------------------------------------------------------ diff --git a/src/metabase/util/encryption.clj b/src/metabase/util/encryption.clj index 9962adad8af5317d1babbbeab1fcd022bc5c2b59..24afed693af1f7f3cfdfbfdcb111df797bbfa0f6 100644 --- a/src/metabase/util/encryption.clj +++ b/src/metabase/util/encryption.clj @@ -10,7 +10,7 @@ [clojure.tools.logging :as log] [environ.core :as env] [metabase.util :as u] - [puppetlabs.i18n.core :refer [trs]] + [metabase.util.i18n :refer [trs]] [ring.util.codec :as codec])) (defn secret-key->hash @@ -36,7 +36,8 @@ (trs "Saved credentials encryption is ENABLED for this Metabase instance.") (trs "Saved credentials encryption is DISABLED for this Metabase instance.")) (u/emoji (if default-secret-key "ðŸ”" "🔓")) - (trs "\nFor more information, see") + "\n" + (trs "For more information, see") "https://www.metabase.com/docs/latest/operations-guide/start.html#encrypting-your-database-connection-details-at-rest") (defn encrypt diff --git a/src/metabase/util/i18n.clj b/src/metabase/util/i18n.clj index dd7a4d5768a46d8d6f8aadbd5a95e286c9dd33e3..7302c9c993e79e7a305209391d2a4dc6f3073730 100644 --- a/src/metabase/util/i18n.clj +++ b/src/metabase/util/i18n.clj @@ -1,6 +1,9 @@ (ns metabase.util.i18n - (:require - [puppetlabs.i18n.core :refer [available-locales]]) + (:refer-clojure :exclude [ex-info]) + (:require [cheshire.generate :as json-gen] + [clojure.walk :as walk] + [puppetlabs.i18n.core :as i18n :refer [available-locales]] + [schema.core :as s]) (:import java.util.Locale)) (defn available-locales-with-names @@ -12,3 +15,75 @@ "This sets the local for the instance" [locale] (Locale/setDefault (Locale/forLanguageTag locale))) + +(defrecord UserLocalizedString [ns-str msg args] + java.lang.Object + (toString [_] + (apply i18n/translate ns-str (i18n/user-locale) msg args)) + schema.core.Schema + (explain [this] + (str this))) + +(defrecord SystemLocalizedString [ns-str msg args] + java.lang.Object + (toString [_] + (apply i18n/translate ns-str (i18n/system-locale) msg args)) + s/Schema + (explain [this] + (str this))) + +(defn- localized-to-json + "Write a UserLocalizedString or SystemLocalizedString to the `json-generator`. This is intended for + `json-gen/add-encoder`. Ideallys we'd implement those protocols directly as it's faster, but currently that doesn't + work with Cheshire" + [localized-string json-generator] + (json-gen/write-string json-generator (str localized-string))) + +(json-gen/add-encoder UserLocalizedString localized-to-json) +(json-gen/add-encoder SystemLocalizedString localized-to-json) + +(def LocalizedString + "Schema for user and system localized string instances" + (s/cond-pre UserLocalizedString SystemLocalizedString)) + +(defmacro tru + "Similar to `puppetlabs.i18n.core/tru` but creates a `UserLocalizedString` instance so that conversion to the + correct locale can be delayed until it is needed. The user locale comes from the browser, so conversion to the + localized string needs to be 'late bound' and only occur when the user's locale is in scope. Calling `str` on the + results of this invocation will lookup the translated version of the string." + [msg & args] + `(UserLocalizedString. (namespace-munge *ns*) ~msg ~(vec args))) + +(defmacro trs + "Similar to `puppetlabs.i18n.core/trs` but creates a `SystemLocalizedString` instance so that conversion to the + correct locale can be delayed until it is needed. This is needed as the system locale from the JVM can be + overridden/changed by a setting. Calling `str` on the results of this invocation will lookup the translated version + of the string." + [msg & args] + `(SystemLocalizedString. (namespace-munge *ns*) ~msg ~(vec args))) + +(def ^:private localized-string-checker + "Compiled checker for `LocalizedString`s which is more efficient when used repeatedly like in `localized-string?` + below" + (s/checker LocalizedString)) + +(defn localized-string? + "Returns `true` if `maybe-a-localized-string` is a system or user localized string instance" + [maybe-a-localized-string] + (not (localized-string-checker maybe-a-localized-string))) + +(defn localized-strings->strings + "Walks the datastructure `x` and converts any localized strings to regular string" + [x] + (walk/postwalk (fn [node] + (if (localized-string? node) + (str node) + node)) x)) + +(defn ex-info + "Just like `clojure.core/ex-info` but it is i18n-aware. It will call `str` on `msg` and walk `ex-data` converting any + `SystemLocalizedString` and `UserLocalizedString`s to a regular string" + ([msg ex-data-map] + (clojure.core/ex-info (str msg) (localized-strings->strings ex-data-map))) + ([msg ex-data-map cause] + (clojure.core/ex-info (str msg) (localized-strings->strings ex-data-map) cause))) diff --git a/src/metabase/util/schema.clj b/src/metabase/util/schema.clj index 7e84cbc09db4cd144523e6b283a5a0f81b2f35ec..ae3de5cc8dfc672ed0daec0a2fdea176130587f2 100644 --- a/src/metabase/util/schema.clj +++ b/src/metabase/util/schema.clj @@ -5,7 +5,7 @@ [medley.core :as m] [metabase.util :as u] [metabase.util.password :as password] - [puppetlabs.i18n.core :refer [tru]] + [metabase.util.i18n :refer [tru]] [schema.core :as s])) ;; always validate all schemas in s/defn function declarations. See diff --git a/test/metabase/api/table_test.clj b/test/metabase/api/table_test.clj index d2b67f857a0192c013386c9730f6da873cb67326..9b24e35a4c63f6269f7544b976730f202361200a 100644 --- a/test/metabase/api/table_test.clj +++ b/test/metabase/api/table_test.clj @@ -165,6 +165,7 @@ (defn- default-dimension-options [] (->> #'table-api/dimension-options-for-response var-get + (m/map-vals #(update % :name str)) walk/keywordize-keys)) (defn- query-metadata-defaults [] diff --git a/test/metabase/models/setting_test.clj b/test/metabase/models/setting_test.clj index 6474e20c67236e392d84e3050f9a7a292cd7bd8c..006752d48d590058e6051c4f5769ea8f13f4dc34 100644 --- a/test/metabase/models/setting_test.clj +++ b/test/metabase/models/setting_test.clj @@ -9,8 +9,9 @@ [metabase.test.util :refer :all] [metabase.util [encryption :as encryption] - [encryption-test :as encryption-test]] - [puppetlabs.i18n.core :refer [tru]] + [encryption-test :as encryption-test] + [i18n :refer [tru]]] + [puppetlabs.i18n.core :as i18n] [toucan.db :as db])) ;; ## TEST SETTINGS DEFINITIONS @@ -19,21 +20,24 @@ ;; these tests will fail. FIXME (defsetting test-setting-1 - "Test setting - this only shows up in dev (1)") + "Test setting - this only shows up in dev (1)" + :internal? true) (defsetting test-setting-2 "Test setting - this only shows up in dev (2)" + :internal? true :default "[Default Value]") (defsetting ^:private test-boolean-setting "Test setting - this only shows up in dev (3)" + :internal? true :type :boolean) (defsetting ^:private test-json-setting "Test setting - this only shows up in dev (4)" + :internal? true :type :json) - ;; ## HELPER FUNCTIONS (defn db-fetch-setting @@ -215,10 +219,13 @@ ;; Validate setting description with i18n string (expect - ["Test setting - with i18n"] - (for [{:keys [key description]} (setting/all) - :when (= :test-i18n-setting key)] - description)) + ["TEST SETTING - WITH I18N"] + (let [zz (i18n/string-as-locale "zz")] + (i18n/with-user-locale zz + (doall + (for [{:keys [key description]} (setting/all) + :when (= :test-i18n-setting key)] + description))))) ;;; ------------------------------------------------ BOOLEAN SETTINGS ------------------------------------------------ @@ -285,7 +292,8 @@ ;; (#4178) (setting/defsetting ^:private toucan-name - "Name for the Metabase Toucan mascot.") + "Name for the Metabase Toucan mascot." + :internal? true) (expect "Banana Beak" @@ -339,6 +347,7 @@ (defsetting ^:private test-timestamp-setting "Test timestamp setting" + :internal? true :type :timestamp) (expect @@ -479,6 +488,7 @@ (defsetting ^:private uncached-setting "A test setting that should *not* be cached." + :internal? true :cache? false) ;; make sure uncached setting still saves to the DB diff --git a/test/metabase/publc_settings_test.clj b/test/metabase/publc_settings_test.clj index 22249289c1791d4f04416d1b30683b8aa2314cc3..fc7f82c6bc135ebb8ec450422823a4a88e87ff4a 100644 --- a/test/metabase/publc_settings_test.clj +++ b/test/metabase/publc_settings_test.clj @@ -1,7 +1,8 @@ (ns metabase.publc-settings-test (:require [expectations :refer :all] [metabase.public-settings :as public-settings] - [metabase.test.util :as tu])) + [metabase.test.util :as tu] + [puppetlabs.i18n.core :as i18n :refer [tru trs]])) ;; double-check that setting the `site-url` setting will automatically strip off trailing slashes (expect @@ -35,3 +36,16 @@ (tu/with-temporary-setting-values [site-url nil] (public-settings/site-url "https://localhost:3000") (public-settings/site-url))) + +(expect + "HOST" + (let [zz (i18n/string-as-locale "zz")] + (i18n/with-user-locale zz + (str (:display-name (first (get-in (public-settings/public-settings) [:engines :postgres :details-fields]))))))) + +(expect + [true "HOST"] + (let [zz (i18n/string-as-locale "zz")] + (i18n/with-user-locale zz + [(= zz (i18n/user-locale)) + (tru "Host")]))) diff --git a/test/metabase/util/schema_test.clj b/test/metabase/util/schema_test.clj index ec063f7b779b74c68cfaca4bcf0aa609c0cc3e4b..37cad63c68b97b15bc5fd67c1d006db57a888663 100644 --- a/test/metabase/util/schema_test.clj +++ b/test/metabase/util/schema_test.clj @@ -4,6 +4,7 @@ [expectations :refer :all] [metabase.api.common :as api] [metabase.util.schema :as su] + [puppetlabs.i18n.core :as i18n] [schema.core :as s])) ;; check that the API error message generation is working as intended @@ -11,7 +12,7 @@ (str "value may be nil, or if non-nil, value must satisfy one of the following requirements: " "1) value must be a boolean. " "2) value must be a valid boolean string ('true' or 'false').") - (su/api-error-message (s/maybe (s/cond-pre s/Bool su/BooleanString)))) + (str (su/api-error-message (s/maybe (s/cond-pre s/Bool su/BooleanString))))) ;; check that API error message respects `api-param` when specified (api/defendpoint POST "/:id/dimension" @@ -34,3 +35,15 @@ "\n" "* **`dimension-name`** value must be a non-blank string.") (:doc (meta #'POST_:id_dimension))) + +(defn- ex-info-msg [f] + (try + (f) + (catch clojure.lang.ExceptionInfo e + (.getMessage e)))) + +(expect + #"INTEGER GREATER THAN ZERO" + (let [zz (i18n/string-as-locale "zz")] + (i18n/with-user-locale zz + (ex-info-msg #(s/validate su/IntGreaterThanZero -1))))) diff --git a/test_resources/locales.clj b/test_resources/locales.clj new file mode 100644 index 0000000000000000000000000000000000000000..7b4ba58c4e21af427388e9f0bba6ea4668d5aa92 --- /dev/null +++ b/test_resources/locales.clj @@ -0,0 +1,5 @@ +{ + :locales #{"en" "zz"} + :packages ["metabase"] + :bundle "metabase.Messages" +} diff --git a/test_resources/metabase/Messages_zz.properties b/test_resources/metabase/Messages_zz.properties new file mode 100644 index 0000000000000000000000000000000000000000..7f910db401b5fd17927fc530179bceec5e622103 --- /dev/null +++ b/test_resources/metabase/Messages_zz.properties @@ -0,0 +1,4 @@ +Host=HOST +Integer\ greater\ than\ zero=INTEGER\ GREATER\ THAN ZERO +value\ must\ be\ an\ integer\ greater\ than\ zero.=VALUE\ MUST\ BE\ AN\ INTEGER\ GREATER\ THAN\ ZERO. +Test\ setting\ -\ with\ i18n=TEST\ SETTING\ -\ WITH\ I18N \ No newline at end of file