feat(ar1-admin): messaggi errore Pydantic tradotti in italiano umano

Ora il toast error mostra es. 'Versione: deve essere nel formato X.Y.Z
(esempio: 1.1.0)' invece di 'version: String should match pattern
^\d+\.\d+\.\d+$'.

2 nuove utility:
  - FIELD_LABELS_IT: mapping field name -> label IT (version -> 'Versione',
    kind -> 'Tipo regola', subject -> 'Oggetto', body_html -> 'Corpo HTML',
    validity_days -> 'Validita', ecc. — 20 campi mappati)
  - translatePydanticMsg(msg, type, ctx): riconosce i type Pydantic comuni:
      * string_pattern_mismatch + ctx.pattern semver -> 'deve essere nel
        formato X.Y.Z (esempio: 1.1.0)'
      * missing -> 'campo obbligatorio mancante'
      * string_type / int_type / bool_type -> 'deve essere stringa/intero/bool'
      * greater_than_equal / less_than_equal -> 'deve essere almeno N / al
        massimo N' (usando ctx.ge / ctx.le)
      * string_too_short / too_long -> 'troppo corto/lungo (min/max N caratteri)'
      * value_error -> rimuove il prefisso 'Value error, '
      * fallback: msg originale (non rompe nulla per casi non mappati)

formatErrorDetail ora usa entrambi: estrae l'ultimo loc (field name piu
preciso), lo traduce via FIELD_LABELS_IT, concatena col msg tradotto.
This commit is contained in:
BFLOWS
2026-04-23 15:27:30 +02:00
parent cad839aea0
commit ac1c18c737

View File

@@ -59,15 +59,65 @@ const PEC_KIND_OPTIONS = [
]; ];
// Normalizza il campo detail che puo essere string o array Pydantic [{loc, msg, type}] // Mapping field name -> label italiano umano
const FIELD_LABELS_IT = {
version: 'Versione',
layout_config: 'Configurazione layout',
activate_now: 'Attiva subito',
kind: 'Tipo regola',
offset_days: 'Giorni offset',
recurring_interval_days: 'Intervallo ricorrenza',
enabled: 'Attiva',
description: 'Descrizione',
subject: 'Oggetto',
body_html: 'Corpo HTML',
body_text: 'Corpo testo',
validity_days: 'Validita',
popup_dismiss_hours: 'Ore dismiss pop-up',
company_document_category_id: 'Categoria documento',
company_ids: 'ID aziende',
only_expired: 'Solo scadute',
only_missing: 'Solo senza AR1',
dry_run: 'Simulazione',
};
// Traduce messaggi Pydantic comuni in italiano umano
const translatePydanticMsg = (msg, pydanticType, ctx) => {
if (!msg) return '';
const m = String(msg);
// semver pattern
if (m.includes("should match pattern") && ctx?.pattern?.includes('\\d+\\.\\d+\\.\\d+')) {
return 'deve essere nel formato X.Y.Z (esempio: 1.1.0)';
}
// string pattern generico
if (pydanticType === 'string_pattern_mismatch') return `formato non valido (atteso: ${ctx?.pattern || 'pattern specifico'})`;
// tipi base
if (pydanticType === 'missing') return 'campo obbligatorio mancante';
if (pydanticType === 'string_type') return 'deve essere una stringa';
if (pydanticType === 'int_type' || pydanticType === 'int_parsing') return 'deve essere un numero intero';
if (pydanticType === 'bool_type') return 'deve essere vero o falso';
// range
if (pydanticType === 'greater_than_equal') return `deve essere almeno ${ctx?.ge}`;
if (pydanticType === 'less_than_equal') return `deve essere al massimo ${ctx?.le}`;
if (pydanticType === 'string_too_short') return `troppo corto (minimo ${ctx?.min_length || ''} caratteri)`;
if (pydanticType === 'string_too_long') return `troppo lungo (massimo ${ctx?.max_length || ''} caratteri)`;
// value error (validator custom)
if (pydanticType === 'value_error') return m.replace(/^Value error,\s*/, '');
// fallback: lascio msg originale
return m;
};
// Normalizza il campo detail che puo essere string o array Pydantic [{loc, msg, type, ctx}]
const formatErrorDetail = (detail, fallback) => { const formatErrorDetail = (detail, fallback) => {
if (!detail) return fallback || 'Errore'; if (!detail) return fallback || 'Errore';
if (typeof detail === 'string') return detail; if (typeof detail === 'string') return detail;
if (Array.isArray(detail)) { if (Array.isArray(detail)) {
return detail.map(e => { return detail.map(e => {
const loc = Array.isArray(e.loc) ? e.loc.filter(x => x !== 'body').join('.') : ''; const locArr = Array.isArray(e.loc) ? e.loc.filter(x => x !== 'body') : [];
const msg = e.msg || e.message || JSON.stringify(e); const lastLoc = locArr[locArr.length - 1];
return loc ? `${loc}: ${msg}` : msg; const fieldLabel = FIELD_LABELS_IT[lastLoc] || lastLoc || '';
const msg = translatePydanticMsg(e.msg, e.type, e.ctx);
return fieldLabel ? `${fieldLabel}: ${msg}` : msg;
}).join('; '); }).join('; ');
} }
if (typeof detail === 'object') return JSON.stringify(detail); if (typeof detail === 'object') return JSON.stringify(detail);
@@ -75,7 +125,7 @@ const formatErrorDetail = (detail, fallback) => {
}; };
/** /**
* Ar1AdminConfig — configurazione AR1 per superadmin. (build 1776950637) * Ar1AdminConfig — configurazione AR1 per superadmin. (build 1776950842)
* URL: /ar1-admin * URL: /ar1-admin
* *
* 5 sezioni (TabView): * 5 sezioni (TabView):