diff --git a/src/metabase/automagic_dashboards/core.clj b/src/metabase/automagic_dashboards/core.clj
index 861cc111e8157d2fa983c9b3fe7e2b9f8b649da6..d491a4fccf7ecc3adb3a40e9506fe34ddf121dff 100644
--- a/src/metabase/automagic_dashboards/core.clj
+++ b/src/metabase/automagic_dashboards/core.clj
@@ -30,8 +30,9 @@
             [metabase.related :as related]
             [metabase.sync.analyze.classify :as classify]
             [metabase.util :as u]
-            [puppetlabs.i18n.core :as i18n :refer [tru]]
+            [puppetlabs.i18n.core :as i18n :refer [tru trs]]
             [ring.util.codec :as codec]
+            [schema.core :as s]
             [toucan.db :as db]))
 
 (def ^:private public-endpoint "/auto/dashboard/")
@@ -50,7 +51,7 @@
    :source       table
    :database     (:db_id table)
    :url          (format "%stable/%s" public-endpoint (u/get-id table))
-   :rules-prefix "table"})
+   :rules-prefix ["table"]})
 
 (defmethod ->root (type Segment)
   [segment]
@@ -61,7 +62,7 @@
      :database     (:db_id table)
      :query-filter (-> segment :definition :filter)
      :url          (format "%ssegment/%s" public-endpoint (u/get-id segment))
-     :rules-prefix "table"}))
+     :rules-prefix ["table"]}))
 
 (defmethod ->root (type Metric)
   [metric]
@@ -71,7 +72,7 @@
      :source       table
      :database     (:db_id table)
      :url          (format "%smetric/%s" public-endpoint (u/get-id metric))
-     :rules-prefix "metric"}))
+     :rules-prefix ["metric"]}))
 
 (defmethod ->root (type Field)
   [field]
@@ -81,7 +82,7 @@
      :source       table
      :database     (:db_id table)
      :url          (format "%sfield/%s" public-endpoint (u/get-id field))
-     :rules-prefix "field"}))
+     :rules-prefix ["field"]}))
 
 (defmulti
   ^{:doc "Get a reference for a given model to be injected into a template
@@ -420,14 +421,15 @@
                       (assoc :score         score
                              :dataset_query query))))))))
 
-(def ^:private ^{:arglists '([rule])} rule-specificity
-  (comp (partial transduce (map (comp count ancestors)) +) :applies_to))
+(s/defn ^:private rule-specificity
+  [rule :- rules/Rule]
+  (transduce (map (comp count ancestors)) + (:applies_to rule)))
 
-(defn- matching-rules
+(s/defn ^:private matching-rules
   "Return matching rules orderd by specificity.
    Most specific is defined as entity type specification the longest ancestor
    chain."
-  [rules {:keys [source entity]}]
+  [rules :- [rules/Rule], {:keys [source entity]}]
   (let [table-type (or (:entity_type source) :entity/GenericTable)]
     (->> rules
          (filter (fn [{:keys [applies_to]}]
@@ -479,8 +481,8 @@
   [context _]
   context)
 
-(defn- make-context
-  [root rule]
+(s/defn ^:private make-context
+  [root, rule :- rules/Rule]
   {:pre [(:source root)]}
   (let [source        (:source root)
         tables        (concat [source] (when (instance? (type Table) source)
@@ -524,10 +526,10 @@
            vals
            (apply concat)))
 
-(defn- make-dashboard
-  ([root rule]
+(s/defn ^:private make-dashboard
+  ([root, rule :- rules/Rule]
    (make-dashboard root rule {:tables [(:source root)]}))
-  ([root rule context]
+  ([root, rule :- rules/Rule, context]
    (let [this {"this" (-> root
                           :entity
                           (assoc :full-name (:full-name root)))}]
@@ -540,8 +542,8 @@
          (update :groups (partial fill-templates :string context {}))
          (assoc :refinements (:cell-query root))))))
 
-(defn- apply-rule
-  [root rule]
+(s/defn ^:private apply-rule
+  [root, rule :- rules/Rule]
   (let [context   (make-context root rule)
         dashboard (make-dashboard root rule context)
         filters   (->> rule
@@ -568,7 +570,7 @@
   [entity]
   (let [root      (->root entity)
         rule      (->> root
-                       (matching-rules (rules/get-rules [(:rules-prefix root)]))
+                       (matching-rules (rules/get-rules (:rules-prefix root)))
                        first)
         dashboard (make-dashboard root rule)]
     {:url         (:url root)
@@ -587,9 +589,9 @@
           (take n)
           (map ->related-entity)))))
 
-(defn- indepth
-  [root rule]
-  (->> (rules/get-rules [(:rules-prefix root) (:rule rule)])
+(s/defn ^:private indepth
+  [root, rule :- rules/Rule]
+  (->> (rules/get-rules (concat (:rules-prefix root) [(:rule rule)]))
        (keep (fn [indepth]
                (when-let [[dashboard _] (apply-rule root indepth)]
                  {:title       ((some-fn :short-title :title) dashboard)
@@ -598,8 +600,8 @@
                                        (:rule indepth))})))
        (take max-related)))
 
-(defn- related
-  [root rule]
+(s/defn ^:private related
+  [root, rule :- rules/Rule]
   (let [indepth (indepth root rule)]
     {:indepth indepth
      :tables  (take (- max-related (count indepth)) (others root))}))
@@ -607,40 +609,45 @@
 (defn- automagic-dashboard
   "Create dashboards for table `root` using the best matching heuristics."
   [{:keys [rule show rules-prefix query-filter cell-query full-name] :as root}]
-  (when-let [[dashboard rule] (if rule
-                                (apply-rule root (rules/get-rule rule))
-                                (->> root
-                                     (matching-rules (rules/get-rules [rules-prefix]))
-                                     (keep (partial apply-rule root))
-                                     ;; `matching-rules` returns an `ArraySeq` (via `sort-by`) so
-                                     ;; `first` realises one element at a time (no chunking).
-                                     first))]
-    (log/info (format "Applying heuristic %s to %s." (:rule rule) full-name))
-    (log/info (format "Dimensions bindings:\n%s"
-                      (->> dashboard
-                           :context
-                           :dimensions
-                           (m/map-vals #(update % :matches (partial map :name)))
-                           u/pprint-to-str)))
-    (log/info (format "Using definitions:\nMetrics:\n%s\nFilters:\n%s"
-                      (-> dashboard :context :metrics u/pprint-to-str)
-                      (-> dashboard :context :filters u/pprint-to-str)))
-    (-> (cond-> dashboard
-          (or query-filter cell-query)
-          (assoc :title (str (tru "A closer look at ") full-name)))
-        (populate/create-dashboard (or show max-cards))
-        (assoc :related (-> (related root rule)
-                            (assoc :more (if (and (-> dashboard
-                                                      :cards
-                                                      count
-                                                      (> max-cards))
-                                                  (not= show :all))
-                                           [{:title       (tru "Show more about this")
-                                             :description nil
-                                             :table       (:source root)
-                                             :url         (format "%s#show=all"
-                                                                  (:url root))}]
-                                           [])))))))
+  (if-let [[dashboard rule] (if rule
+                              (apply-rule root (rules/get-rule rule))
+                              (->> root
+                                   (matching-rules (rules/get-rules rules-prefix))
+                                   (keep (partial apply-rule root))
+                                   ;; `matching-rules` returns an `ArraySeq` (via `sort-by`) so
+                                   ;; `first` realises one element at a time (no chunking).
+                                   first))]
+    (do
+      (log/infof (trs "Applying heuristic %s to %s.") (:rule rule) full-name)
+      (log/infof (trs "Dimensions bindings:\n%s")
+                 (->> dashboard
+                      :context
+                      :dimensions
+                      (m/map-vals #(update % :matches (partial map :name)))
+                      u/pprint-to-str))
+      (log/infof (trs "Using definitions:\nMetrics:\n%s\nFilters:\n%s")
+                 (-> dashboard :context :metrics u/pprint-to-str)
+                 (-> dashboard :context :filters u/pprint-to-str))
+      (-> (cond-> dashboard
+            (or query-filter cell-query)
+            (assoc :title (str (tru "A closer look at ") full-name)))
+          (populate/create-dashboard (or show max-cards))
+          (assoc :related (-> (related root rule)
+                              (assoc :more (if (and (-> dashboard
+                                                        :cards
+                                                        count
+                                                        (> max-cards))
+                                                    (not= show :all))
+                                             [{:title       (tru "Show more about this")
+                                               :description nil
+                                               :table       (:source root)
+                                               :url         (format "%s#show=all"
+                                                                    (:url root))}]
+                                             []))))))
+    (throw (ex-info (format (trs "Can't create dashboard for %s") full-name)
+             {:root            root
+              :available-rules (map :rule (or (some-> rule rules/get-rule vector)
+                                              (rules/get-rules rules-prefix)))}))))
 
 (def ^:private ^{:arglists '([card])} table-like?
   (comp empty? #(qp.util/get-in-normalized % [:dataset_query :query :aggregation])))
@@ -698,7 +705,7 @@
                                        (u/get-id card)
                                        (encode-base64-json cell-query))
                                (format "%squestion/%s" public-endpoint (u/get-id card)))
-               :rules-prefix "table"}
+               :rules-prefix ["table"]}
               opts)))
     nil))
 
@@ -731,7 +738,7 @@
                                        (encode-base64-json cell-query))
                                (format "%sadhoc/%s" public-endpoint
                                        (encode-base64-json query)))
-               :rules-prefix "table"}
+               :rules-prefix ["table"]}
               (update opts :cell-query merge-filter-clauses
                       (qp.util/get-in-normalized query [:dataset_query :query :filter])))))
     nil))
diff --git a/src/metabase/automagic_dashboards/populate.clj b/src/metabase/automagic_dashboards/populate.clj
index 82ce00dedfb676b0d93ed72a08debf625510e5db..9b4dacdc38b938e5a2a616e4db5e1c5c442a62c9 100644
--- a/src/metabase/automagic_dashboards/populate.clj
+++ b/src/metabase/automagic_dashboards/populate.clj
@@ -6,6 +6,7 @@
             [metabase.automagic-dashboards.filters :as magic.filters]
             [metabase.models.card :as card]
             [metabase.query-processor.util :as qp.util]
+            [puppetlabs.i18n.core :as i18n :refer [trs]]
             [toucan.db :as db]))
 
 (def ^Long ^:const grid-width
@@ -252,10 +253,10 @@
                                      ;; Height doesn't need to be precise, just some
                                      ;; safe upper bound.
                                      (make-grid grid-width (* n grid-width))]))]
-     (log/info (format "Adding %s cards to dashboard %s:\n%s"
-                       (count cards)
-                       title
-                       (str/join "; " (map :title cards))))
+     (log/infof (trs "Adding %s cards to dashboard %s:\n%s")
+                (count cards)
+                title
+                (str/join "; " (map :title cards)))
      (cond-> dashboard
        (not-empty filters) (magic.filters/add-filters filters max-filters)))))
 
diff --git a/src/metabase/automagic_dashboards/rules.clj b/src/metabase/automagic_dashboards/rules.clj
index 21870cec9214866dca880ace49e77c472c013736..ee4c7fe93f32d2b6af323254b7b81ed66ef3f607 100644
--- a/src/metabase/automagic_dashboards/rules.clj
+++ b/src/metabase/automagic_dashboards/rules.clj
@@ -7,19 +7,20 @@
             [metabase.types]
             [metabase.util :as u]
             [metabase.util.schema :as su]
+            [puppetlabs.i18n.core :as i18n :refer [trs]]
             [schema
              [coerce :as sc]
              [core :as s]]
             [yaml.core :as yaml])
   (:import java.nio.file.Path java.nio.file.FileSystems java.nio.file.FileSystem
-           java.nio.file.Files ))
+           java.nio.file.Files))
 
 (def ^Long ^:const max-score
   "Maximal (and default) value for heuristics scores."
   100)
 
 (def ^:private Score (s/constrained s/Int #(<= 0 % max-score)
-                                    (str "0 <= score <= " max-score)))
+                                    (format (trs "0 <= score <= %s") max-score)))
 
 (def ^:private MBQL [s/Any])
 
@@ -80,7 +81,7 @@
 (def ^:private Visualization [(s/one s/Str "visualization") su/Map])
 
 (def ^:private Width  (s/constrained s/Int #(<= 1 % populate/grid-width)
-                                     (format "1 <= width <= %s"
+                                     (format (trs "1 <= width <= %s")
                                              populate/grid-width)))
 (def ^:private Height (s/constrained s/Int pos?))
 
@@ -186,7 +187,8 @@
           schema
           (partition 2 constraints)))
 
-(def ^:private Rules
+(def Rule
+  "Rules defining an automagic dashboard."
   (constrained-all
    {(s/required-key :title)             s/Str
     (s/required-key :dimensions)        [Dimension]
@@ -201,13 +203,13 @@
     (s/optional-key :groups)            Groups
     (s/optional-key :indepth)           [s/Any]
     (s/optional-key :dashboard_filters) [s/Str]}
-   valid-metrics-references?            "Valid metrics references"
-   valid-filters-references?            "Valid filters references"
-   valid-group-references?              "Valid group references"
-   valid-order-by-references?           "Valid order_by references"
-   valid-dashboard-filters-references?  "Valid dashboard filters references"
-   valid-dimension-references?          "Valid dimension references"
-   valid-breakout-dimension-references? "Valid card dimension references"))
+   valid-metrics-references?            (trs "Valid metrics references")
+   valid-filters-references?            (trs "Valid filters references")
+   valid-group-references?              (trs "Valid group references")
+   valid-order-by-references?           (trs "Valid order_by references")
+   valid-dashboard-filters-references?  (trs "Valid dashboard filters references")
+   valid-dimension-references?          (trs "Valid dimension references")
+   valid-breakout-dimension-references? (trs "Valid card dimension references")))
 
 (defn- with-defaults
   [defaults]
@@ -234,7 +236,7 @@
 
 (def ^:private rules-validator
   (sc/coercer!
-   Rules
+   Rule
    {[s/Str]         ensure-seq
     [OrderByPair]   ensure-seq
     OrderByPair     (fn [x]
@@ -277,7 +279,7 @@
 (def ^:private rules-dir "automagic_dashboards/")
 
 (def ^:private ^{:arglists '([f])} file->entity-type
-  (comp (partial re-find #".+(?=\.yaml)") str (memfn ^Path getFileName)))
+  (comp (partial re-find #".+(?=\.yaml$)") str (memfn ^Path getFileName)))
 
 (defn- load-rule
   [^Path f]
@@ -291,15 +293,21 @@
           (update :applies_to #(or % entity-type))
           rules-validator))
     (catch Exception e
-      (log/error (format "Error parsing %s:\n%s"
-                         (.getFileName f)
-                         (or (some-> e
-                                     ex-data
-                                     (select-keys [:error :value])
-                                     u/pprint-to-str)
-                             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))
       nil)))
 
+(defn- trim-trailing-slash
+  [s]
+  (if (str/ends-with? s "/")
+    (subs s 0 (-> s count dec))
+    s))
+
 (defn- load-rule-dir
   ([dir] (load-rule-dir dir [] {}))
   ([dir path rules]
@@ -307,7 +315,7 @@
      (reduce (fn [rules ^Path f]
                (cond
                  (Files/isDirectory f (into-array java.nio.file.LinkOption []))
-                 (load-rule-dir f (conj path (str (.getFileName f))) rules)
+                 (load-rule-dir f (->> f (.getFileName) str trim-trailing-slash (conj path)) rules)
 
                  (file->entity-type f)
                  (assoc-in rules (concat path [(file->entity-type f) ::leaf]) (load-rule f))