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:
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user