🐛 Fix lost translation strings (#5846)

* 🐛 Fix lost translation strings

* 🐛 Fix form error management internal issues and inconsistencies

* 📎 Add better validation conditons for ::sm/text schema

* 🐛 Add better touched detection mechanism for input and textarea

---------

Co-authored-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
Eva Marco 2025-02-14 12:51:14 +01:00 committed by GitHub
parent 6bb7fa26f4
commit a7ed5228d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 111 additions and 44 deletions

View file

@ -963,7 +963,6 @@
{:title "string" {:title "string"
:description "not whitespace string" :description "not whitespace string"
:gen/gen (sg/word-string) :gen/gen (sg/word-string)
:error/code "errors.invalid-text"
:error/fn :error/fn
(fn [{:keys [value schema]}] (fn [{:keys [value schema]}]
(let [{:keys [max min] :as props} (properties schema)] (let [{:keys [max min] :as props} (properties schema)]
@ -971,16 +970,23 @@
(and (string? value) (and (string? value)
(number? max) (number? max)
(> (count value) max)) (> (count value) max))
["errors.field-max-length" max] {:code ["errors.field-max-length" max]}
(and (string? value) (and (string? value)
(number? min) (number? min)
(< (count value) min)) (< (count value) min))
["errors.field-min-length" min] {:code ["errors.field-min-length" min]}
(and (string? value)
(str/empty? value))
{:code "errors.field-missing"}
(and (string? value) (and (string? value)
(str/blank? value)) (str/blank? value))
"errors.field-not-all-whitespace")))}}) {:code "errors.field-not-all-whitespace"}
:else
{:code "errors.invalid-text"})))}})
(register! (register!
{:type ::password {:type ::password

View file

@ -61,7 +61,7 @@
[:validation :email-as-password] [:validation :email-as-password]
(swap! form assoc-in [:errors :password] (swap! form assoc-in [:errors :password]
{:code "errors.email-as-password"}) {:message (tr "errors.email-as-password")})
(st/emit! (ntf/error (tr "errors.generic"))))))) (st/emit! (ntf/error (tr "errors.generic")))))))

View file

@ -33,6 +33,8 @@
more-classes (get props :class) more-classes (get props :class)
auto-focus? (get props :auto-focus? false) auto-focus? (get props :auto-focus? false)
data-testid (d/nilv data-testid input-name)
form (or form (mf/use-ctx form-ctx)) form (or form (mf/use-ctx form-ctx))
type' (mf/use-state input-type) type' (mf/use-state input-type)
@ -45,7 +47,9 @@
(= @type' "email")) (= @type' "email"))
placeholder (when is-text? (or placeholder label)) placeholder (when is-text? (or placeholder label))
touched? (get-in @form [:touched input-name]) touched? (and (contains? (:data @form) input-name)
(get-in @form [:touched input-name]))
error (get-in @form [:errors input-name]) error (get-in @form [:errors input-name])
value (get-in @form [:data input-name] "") value (get-in @form [:data input-name] "")
@ -153,6 +157,14 @@
children]) children])
(cond (cond
(and touched? (:message error) show-error)
(let [message (:message error)]
[:div {:id (dm/str "error-" input-name)
:class (stl/css :error)
:data-testid (dm/str data-testid "-error")}
message])
;; FIXME: DEPRECATED
(and touched? (:code error) show-error) (and touched? (:code error) show-error)
(let [code (:code error)] (let [code (:code error)]
[:div {:id (dm/str "error-" input-name) [:div {:id (dm/str "error-" input-name)
@ -173,7 +185,9 @@
focus? (mf/use-state false) focus? (mf/use-state false)
touched? (get-in @form [:touched input-name]) touched? (and (contains? (:data @form) input-name)
(get-in @form [:touched input-name]))
error (get-in @form [:errors input-name]) error (get-in @form [:errors input-name])
value (get-in @form [:data input-name] "") value (get-in @form [:data input-name] "")
@ -211,6 +225,9 @@
[:label {:class (stl/css :textarea-label)} label] [:label {:class (stl/css :textarea-label)} label]
[:> :textarea props] [:> :textarea props]
(cond (cond
(and touched? (:message error))
[:span {:class (stl/css :error)} (:message error)]
(and touched? (:code error)) (and touched? (:code error))
[:span {:class (stl/css :error)} (tr (:code error))] [:span {:class (stl/css :error)} (tr (:code error))]

View file

@ -59,7 +59,7 @@
[:map {:title "EmailChangeForm"} [:map {:title "EmailChangeForm"}
[:email-1 ::sm/email] [:email-1 ::sm/email]
[:email-2 ::sm/email]] [:email-2 ::sm/email]]
[:fn {:error/code "errors.invalid-email-confirmation" [:fn {:error/fn #(tr "errors.invalid-email-confirmation")
:error/field :email-2} :error/field :email-2}
(fn [data] (fn [data]
(let [email-1 (:email-1 data) (let [email-1 (:email-1 data)

View file

@ -21,10 +21,10 @@
(case (:code (ex-data error)) (case (:code (ex-data error))
:old-password-not-match :old-password-not-match
(swap! form assoc-in [:errors :password-old] (swap! form assoc-in [:errors :password-old]
{:code "errors.wrong-old-password"}) {:message (tr "errors.wrong-old-password")})
:email-as-password :email-as-password
(swap! form assoc-in [:errors :password-1] (swap! form assoc-in [:errors :password-1]
{:code "errors.email-as-password"}) {:message (tr "errors.email-as-password")})
(let [msg (tr "generic.error")] (let [msg (tr "generic.error")]
(st/emit! (ntf/error msg))))) (st/emit! (ntf/error msg)))))

View file

@ -10,43 +10,78 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.util.i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[cuerdas.core :as str] [cuerdas.core :as str]
[malli.core :as m] [malli.core :as m]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
;; --- Handlers Helpers ;; --- Handlers Helpers
(defn- translate-code
[code]
(if (vector? code)
(tr (nth code 0) (i18n/c (nth code 1)))
(tr code)))
(defn- handle-error-fn
[props problem]
(let [v-fn (:error/fn props)
result (v-fn problem)]
(if (string? result)
{:message result}
{:message (or (some-> (get result :code)
(translate-code))
(get result :message)
(tr "errors.invalid-data"))})))
(defn- handle-error-message
[props]
{:message (get props :error/message)})
(defn- handle-error-code
[props]
(let [code (get props :error/code)]
{:message (translate-code code)}))
(defn- interpret-schema-problem (defn- interpret-schema-problem
[acc {:keys [schema in value] :as problem}] [acc {:keys [schema in value type] :as problem}]
(let [props (merge (m/type-properties schema) (let [props (m/properties schema)
(m/properties schema)) tprops (m/type-properties schema)
field (or (first in) (:error/field props))] field (or (first in)
(:error/field props))]
(if (contains? acc field) (if (contains? acc field)
acc acc
(cond (cond
(nil? value) (nil? field)
(assoc acc field {:code "errors.field-missing"}) acc
(contains? props :error/code) (or (= type :malli.core/missing-key)
(assoc acc field {:code (:error/code props)}) (nil? value))
(assoc acc field {:message (tr "errors.field-missing")})
;; --- CHECK on schema props
(contains? props :error/fn)
(assoc acc field (handle-error-fn props problem))
(contains? props :error/message) (contains? props :error/message)
(assoc acc field {:code (:error/message props)}) (assoc acc field (handle-error-message props))
(contains? props :error/fn) (contains? props :error/code)
(let [v-fn (:error/fn props) (assoc acc field (handle-error-code props))
code (v-fn problem)]
(assoc acc field {:code code}))
(contains? props :error/validators) ;; --- CHECK on type props
(let [validators (:error/validators props) (contains? tprops :error/fn)
props (reduce #(%2 %1 value) props validators)] (assoc acc field (handle-error-fn tprops problem))
(assoc acc field {:code (d/nilv (:error/code props) "errors.invalid-data")}))
(contains? tprops :error/message)
(assoc acc field (handle-error-message tprops))
(contains? tprops :error/code)
(assoc acc field (handle-error-code tprops))
:else :else
(assoc acc field {:code "errors.invalid-data"}))))) (assoc acc field {:message (tr "errors.invalid-data")})))))
(defn- use-rerender-fn (defn- use-rerender-fn
[] []
@ -177,21 +212,6 @@
;; --- Helper Components ;; --- Helper Components
(mf/defc field-error
[{:keys [form field type]
:as props}]
(let [{:keys [message] :as error} (dm/get-in form [:errors field])
touched? (dm/get-in form [:touched field])
show? (and touched? error message
(cond
(nil? type) true
(keyword? type) (= (:type error) type)
(ifn? type) (type (:type error))
:else false))]
(when show?
[:ul
[:li {:key (:code error)} (tr (:message error))]])))
(defn error-class (defn error-class
[form field] [form field]
(when (and (dm/get-in form [:errors field]) (when (and (dm/get-in form [:errors field])

View file

@ -1230,6 +1230,18 @@ msgstr "Something wrong has happened."
msgid "errors.invalid-color" msgid "errors.invalid-color"
msgstr "Invalid color" msgstr "Invalid color"
#: src/app/util/forms.cljs
msgid "errors.invalid-data"
msgstr "Invalid data"
#: src/app/util/forms.cljs
msgid "errors.field-missing"
msgstr "Empty field"
#: src/app/util/forms.cljs
msgid "errors.invalid-text"
msgstr "Invalid text"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs, src/app/main/ui/auth/recovery_request.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs, src/app/main/ui/auth/recovery_request.cljs
#, unused #, unused
msgid "errors.invalid-email" msgid "errors.invalid-email"

View file

@ -1231,6 +1231,18 @@ msgstr "Ha ocurrido algún error."
msgid "errors.invalid-color" msgid "errors.invalid-color"
msgstr "Color no válido" msgstr "Color no válido"
#: src/app/util/forms.cljs
msgid "errors.invalid-data"
msgstr "Datos no válidos"
#: src/app/util/forms.cljs
msgid "errors.field-missing"
msgstr "Campo vacio"
#: src/app/util/forms.cljs
msgid "errors.invalid-text"
msgstr "Texto no válido"
#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs, src/app/main/ui/auth/recovery_request.cljs #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs, src/app/main/ui/auth/recovery_request.cljs
#, unused #, unused
msgid "errors.invalid-email" msgid "errors.invalid-email"