improve generic error validation messages

This commit is contained in:
Josh Hawkins 2026-01-25 18:47:31 -06:00
parent dd0c497fd3
commit f534c7216a
5 changed files with 16 additions and 21 deletions

View File

@ -68,10 +68,10 @@ export function ConfigForm({
formContext, formContext,
i18nNamespace, i18nNamespace,
}: ConfigFormProps) { }: ConfigFormProps) {
const { t } = useTranslation([ const { t, i18n } = useTranslation([
i18nNamespace || "common", i18nNamespace || "common",
"views/settings", "views/settings",
"validation", "config/validation",
]); ]);
const [showAdvanced, setShowAdvanced] = useState(false); const [showAdvanced, setShowAdvanced] = useState(false);
@ -117,7 +117,7 @@ export function ConfigForm({
); );
// Create error transformer for user-friendly error messages // Create error transformer for user-friendly error messages
const errorTransformer = useMemo(() => createErrorTransformer(t), [t]); const errorTransformer = useMemo(() => createErrorTransformer(i18n), [i18n]);
const handleChange = useCallback( const handleChange = useCallback(
(e: IChangeEvent) => { (e: IChangeEvent) => {

View File

@ -2,7 +2,7 @@
// Maps JSON Schema validation keywords to user-friendly messages // Maps JSON Schema validation keywords to user-friendly messages
import type { ErrorTransformer } from "@rjsf/utils"; import type { ErrorTransformer } from "@rjsf/utils";
import type { TFunction } from "i18next"; import type { i18n as I18n } from "i18next";
export interface ErrorMessageMap { export interface ErrorMessageMap {
[keyword: string]: string | ((params: Record<string, unknown>) => string); [keyword: string]: string | ((params: Record<string, unknown>) => string);
@ -51,7 +51,9 @@ export const defaultErrorMessages: ErrorMessageMap = {
* Creates an error transformer function for RJSF * Creates an error transformer function for RJSF
* Transforms technical JSON Schema errors into user-friendly messages * Transforms technical JSON Schema errors into user-friendly messages
*/ */
export function createErrorTransformer(t: TFunction): ErrorTransformer { export function createErrorTransformer(i18n: I18n): ErrorTransformer {
const t = i18n.t.bind(i18n);
const getDefaultMessage = ( const getDefaultMessage = (
errorType: string, errorType: string,
params: Record<string, unknown>, params: Record<string, unknown>,
@ -117,31 +119,22 @@ export function createErrorTransformer(t: TFunction): ErrorTransformer {
let message: string | undefined; let message: string | undefined;
const missingTranslation = "__missing_translation__";
// Try field-specific validation message first // Try field-specific validation message first
if (fieldPath) { if (fieldPath) {
const fieldKey = `${fieldPath}.validation.${errorType}`; const fieldKey = `${fieldPath}.validation.${errorType}`;
const translated = t(fieldKey, { if (i18n.exists(fieldKey)) {
...normalizedParams, message = t(fieldKey, normalizedParams);
ns: ["config"],
defaultValue: missingTranslation,
});
if (translated !== fieldKey && translated !== missingTranslation) {
message = translated;
} }
} }
// Fall back to generic validation message // Fall back to generic validation message
if (!message) { if (!message) {
const genericKey = errorType; const genericKey = errorType;
const translated = t(genericKey, { if (i18n.exists(genericKey, { ns: "config/validation" })) {
...normalizedParams, message = t(genericKey, {
ns: ["validation"], ...normalizedParams,
defaultValue: missingTranslation, ns: ["config/validation"],
}); });
if (translated !== genericKey && translated !== missingTranslation) {
message = translated;
} }
} }

View File

@ -72,6 +72,7 @@ i18n
"config/semantic_search", "config/semantic_search",
"config/face_recognition", "config/face_recognition",
"config/lpr", "config/lpr",
"config/validation",
], ],
defaultNS: "common", defaultNS: "common",

View File

@ -95,6 +95,7 @@ const globalSectionConfigs: Record<
"tls_client_key", "tls_client_key",
"tls_insecure", "tls_insecure",
], ],
liveValidate: true,
}, },
database: { database: {
i18nNamespace: "config/database", i18nNamespace: "config/database",