Files
nis2-agile/public/js/i18n.js
T
DevEnv nis2-agileandClaude Opus 4.8 79ca72fba9 [FEAT] ISO-readiness: Audit interni (§9.2) + Riesame Direzione (§9.3) + Calendario scadenze (mig.055-056, v1.22.0)
Rilascio unico (3 moduli) costruito in flotta parallela + verifica avversariale.

MODULO A — Audit interni (ISO 27001 §9.2, mig.055): internal_audits + internal_audit_items;
codice AUD-NNN; checklist pre-popolata (clausole 4-10 + Annex A applicabili dal SoA);
esiti per riga; apertura non conformità collegata a NCR/CAPA (source polimorfico);
report HTML stampabile. InternalAuditController + internal-audits.html.

MODULO B — Riesame di Direzione (ISO 27001 §9.3, mig.056): management_reviews +
management_review_decisions; codice RD-AAAA-NN; INPUT aggregati automaticamente dai
moduli (gather: NC/CAPA, audit interni, rischi+trattamenti, KPI/score, obiettivi, formazione,
stakeholder, normative, scadenze), congelati nello snapshot all'approvazione; decisioni;
verbale stampabile. ManagementReviewController + management-review.html.

MODULO C — Calendario unico scadenze: aggregatore SOLA LETTURA (nessuna migrazione) di tutte
le scadenze (incidenti/policy/rischi/NC-CAPA/formazione/stakeholder/audit/riesame/review_schedule),
griglia mensile + lista + filtri + deep-link. CalendarController + calendario.html.

Integrazione: router (3 controller+actionMap), api.js, sidebar V2+legacy, help.js (3 sezioni),
i18n (IT+EN), review_schedule ENUM += internal_audit. v1.22.0 + sw cache + cache-buster 20260630.

Verifica flotta (28 agenti, 4 dim + avversariale): 9 finding confermati, TUTTI corretti —
MAJOR gatherRisks (risk_treatments.organization_id inesistente → JOIN risks); nextCode numerico
(no dup >99/anno); footer report audit con etichetta "ISO buona prassi, non obbligo"; help 24→25
clausole; ARIA tab/calendario; focus modali; tasti su celle calendario; rimossi helper api morti.

Smoke prod OK (calendario 18 eventi, audit AUD-001 25 item + report + audit→NCR + audit→calendario,
riesame gather/decisione/approve/report, gatherRisks ora available); org 151 ripulita. Additivo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 07:48:49 +02:00

499 lines
38 KiB
JavaScript

/**
* NIS2 Agile - i18n (Internationalization)
*
* Sistema di traduzione leggero IT/EN.
* - Lingua di default: 'it'
* - Salva preferenza in localStorage
* - Applica traduzioni via attributo data-i18n
* - Funzione t('chiave') per traduzione programmatica
*/
const I18n = (function () {
'use strict';
let _lang = 'it';
// ── Dizionario traduzioni ─────────────────────────────────────────
const _dict = {
// ── Navigazione / Sidebar ──────────────────────
'nav.main': { it: 'Principale', en: 'Main' },
'nav.dashboard': { it: 'Dashboard', en: 'Dashboard' },
'nav.gap_analysis': { it: 'Gap Analysis', en: 'Gap Analysis' },
'nav.framework': { it: 'Misure e Requisiti', en: 'Measures & Requirements' },
// ── Modello Organizzativo SGSI (ISO 27001) ──
'nav.isms': { it: 'Modello SGSI (ISO 27001)', en: 'ISMS Model (ISO 27001)' },
'isms.title': { it: 'Modello Organizzativo SGSI (ISO 27001)', en: 'ISMS Organizational Model (ISO 27001)' },
'isms.export': { it: 'Esporta SGSI', en: 'Export ISMS' },
'isms.intro.title': { it: 'Costruisci il tuo Sistema di Gestione della Sicurezza delle Informazioni.', en: 'Build your Information Security Management System.' },
'isms.intro.body': { it: 'Procedura guidata ISO/IEC 27001:2022 (clausole 4-10 + Statement of Applicability). Lo Statement of Applicability viene pre-popolato dalle tue risposte alla Gap Analysis NIS2. Attiva le estensioni cloud ISO 27017/27018 dove pertinenti.', en: 'Guided ISO/IEC 27001:2022 procedure (clauses 4-10 + Statement of Applicability). The Statement of Applicability is pre-populated from your NIS2 Gap Analysis answers. Enable the ISO 27017/27018 cloud extensions where relevant.' },
'isms.disclaimer': { it: 'Strumento di supporto e pre-audit: non costituisce una certificazione ISO 27001 né un parere professionale vincolante. Gli obblighi normativi in Italia derivano da NIS2 / D.Lgs. 138/2024 / Determinazioni ACN; ISO 27001/27017/27018 sono best practice.', en: 'Support and pre-audit tool: it is not an ISO 27001 certification nor binding professional advice. Regulatory obligations in Italy stem from NIS2 / Legislative Decree 138/2024 / ACN Determinations; ISO 27001/27017/27018 are best practices.' },
'isms.step1': { it: 'Contesto', en: 'Context' },
'isms.step2': { it: 'Leadership', en: 'Leadership' },
'isms.step3': { it: 'Risk', en: 'Risk' },
'isms.step4': { it: 'SoA', en: 'SoA' },
'isms.step5': { it: 'Documenti', en: 'Documents' },
'isms.step6': { it: 'Monitoraggio', en: 'Monitoring' },
'isms.save_next': { it: 'Salva e continua', en: 'Save and continue' },
'isms.next': { it: 'Continua', en: 'Continue' },
'isms.s1.title': { it: '1. Contesto e Ambito (cl. 4)', en: '1. Context and Scope (cl. 4)' },
'isms.s1.scope': { it: 'Ambito del SGSI (scope statement)', en: 'ISMS scope statement' },
'isms.s1.ctx_int': { it: 'Contesto interno', en: 'Internal context' },
'isms.s1.ctx_ext': { it: 'Contesto esterno', en: 'External context' },
'isms.s1.parties': { it: 'Parti interessate (una per riga)', en: 'Interested parties (one per line)' },
'isms.s1.boundaries': { it: 'Confini del SGSI', en: 'ISMS boundaries' },
'isms.s1.exclusions': { it: 'Esclusioni motivate', en: 'Justified exclusions' },
'isms.s1.cloud': { it: 'Estensioni cloud (attivano i controlli condizionali nel SoA)', en: 'Cloud extensions (enable conditional controls in the SoA)' },
'isms.s1.uses_cloud': { it: 'Usiamo servizi cloud pubblici (Microsoft 365, AWS, Google...) → ISO 27017', en: 'We use public cloud services (Microsoft 365, AWS, Google...) → ISO 27017' },
'isms.s1.cloud_provider': { it: 'Siamo fornitori di servizi cloud → ISO 27017 (lato provider)', en: 'We are cloud service providers → ISO 27017 (provider side)' },
'isms.s1.pii_cloud': { it: 'Trattiamo dati personali (PII) in cloud pubblico come responsabili → ISO 27018', en: 'We process personal data (PII) in public cloud as processors → ISO 27018' },
'isms.s2.title': { it: '2. Leadership: ruoli e responsabilità (cl. 5)', en: '2. Leadership: roles and responsibilities (cl. 5)' },
'isms.s2.hint': { it: 'Definisci i ruoli del SGSI con la matrice RACI (Responsible, Accountable, Consulted, Informed).', en: 'Define the ISMS roles with the RACI matrix (Responsible, Accountable, Consulted, Informed).' },
'isms.s2.add': { it: 'Aggiungi ruolo', en: 'Add role' },
'isms.s3.title': { it: '3. Risk: metodologia e obiettivi (cl. 6)', en: '3. Risk: methodology and objectives (cl. 6)' },
'isms.s3.method': { it: 'Metodologia di risk assessment', en: 'Risk assessment methodology' },
'isms.s3.objectives': { it: 'Obiettivi del SGSI (uno per riga)', en: 'ISMS objectives (one per line)' },
'isms.s3.link': { it: 'Il registro dei rischi vive nel modulo Rischi: questo passo ne dichiara la metodologia.', en: 'The risk register lives in the Risks module: this step declares its methodology.' },
'isms.s4.title': { it: '4. Statement of Applicability', en: '4. Statement of Applicability' },
'isms.s4.hint': { it: 'I controlli sono pre-popolati dalle risposte NIS2 dove esiste corrispondenza (badge "da NIS2"). Conferma applicabilità, stato e motivazione per ciascuno.', en: 'Controls are pre-populated from NIS2 answers where a mapping exists ("from NIS2" badge). Confirm applicability, status and justification for each.' },
'isms.s4.derive': { it: 'Ri-deriva dal NIS2', en: 'Re-derive from NIS2' },
'isms.s5.title': { it: '5. Documented Information (cl. 7-8)', en: '5. Documented Information (cl. 7-8)' },
'isms.s5.aigen': { it: 'Genera bozza con AI', en: 'Generate draft with AI' },
'isms.s6.title': { it: '6. Monitoraggio e Miglioramento (cl. 9-10)', en: '6. Monitoring and Improvement (cl. 9-10)' },
'isms.s6.hint': { it: 'Gli audit interni e le non conformità si gestiscono nei moduli esistenti: Audit & Report e NCR/CAPA.', en: 'Internal audits and non-conformities are managed in the existing modules: Audit & Reports and NCR/CAPA.' },
'isms.s6.readiness': { it: 'Completamento del SGSI', en: 'ISMS completion' },
'nav.management': { it: 'Gestione', en: 'Management' },
'nav.org_chart': { it: 'Organigramma', en: 'Org Chart' },
'nav.competences': { it: 'Competenze', en: 'Skills' },
'nav.raci': { it: 'Matrice RACI', en: 'RACI Matrix' },
'org.title': { it: 'Organigramma', en: 'Org Chart' },
'org.subtitle': { it: 'Ruoli, responsabilità e organi di governance', en: 'Roles, responsibilities and governance bodies' },
'org.new_role': { it: 'Nuovo ruolo', en: 'New role' },
'comp.title': { it: 'Competenze', en: 'Skills' },
'raci.title': { it: 'Matrice RACI', en: 'RACI Matrix' },
'nav.review_schedule': { it: 'Scadenziario', en: 'Review schedule' },
'nav.stakeholders': { it: 'Stakeholder', en: 'Stakeholders' },
'nav.stk_activities': { it: 'Attività stakeholder', en: 'Stakeholder activities' },
'rev.title': { it: 'Scadenziario', en: 'Review Schedule' },
'stk.title': { it: 'Stakeholder', en: 'Stakeholders' },
'sact.title': { it: 'Attività stakeholder', en: 'Stakeholder activities' },
'nav.calendar': { it: 'Calendario', en: 'Calendar' },
'cal.title': { it: 'Calendario scadenze', en: 'Deadlines calendar' },
'nav.internal_audits': { it: 'Audit interni', en: 'Internal audits' },
'iaud.title': { it: 'Audit interni', en: 'Internal audits' },
'nav.management_review': { it: 'Riesame di Direzione', en: 'Management Review' },
'mreview.title': { it: 'Riesame di Direzione', en: 'Management Review' },
'nav.risks': { it: 'Rischi', en: 'Risks' },
'nav.incidents': { it: 'Incidenti', en: 'Incidents' },
'nav.policies': { it: 'Policy', en: 'Policies' },
'nav.supply_chain': { it: 'Supply Chain', en: 'Supply Chain' },
'nav.operations': { it: 'Operativo', en: 'Operations' },
'nav.training': { it: 'Formazione', en: 'Training' },
'nav.assets': { it: 'Inventario', en: 'Inventory' },
'nav.audit': { it: 'Audit & Report', en: 'Audit & Reports' },
'nav.system': { it: 'Sistema', en: 'System' },
'nav.settings': { it: 'Impostazioni', en: 'Settings' },
'nav.help': { it: 'Guida', en: 'Help' },
'nav.logout': { it: 'Esci', en: 'Logout' },
// ── Dashboard ──────────────────────────────────
'dashboard.title': { it: 'Dashboard', en: 'Dashboard' },
'dashboard.compliance_score': { it: 'Punteggio Compliance', en: 'Compliance Score' },
'dashboard.open_risks': { it: 'Rischi Aperti', en: 'Open Risks' },
'dashboard.active_incidents': { it: 'Incidenti Attivi', en: 'Active Incidents' },
'dashboard.overdue_tasks': { it: 'Scadenze Superate', en: 'Overdue Tasks' },
'dashboard.recent_activity': { it: 'Attivita\' Recente', en: 'Recent Activity' },
'dashboard.upcoming_deadlines': { it: 'Prossime Scadenze', en: 'Upcoming Deadlines' },
'dashboard.risk_heatmap': { it: 'Mappa Rischi', en: 'Risk Heatmap' },
'dashboard.quick_actions': { it: 'Azioni Rapide', en: 'Quick Actions' },
// ── Assessment ─────────────────────────────────
'assessment.title': { it: 'Gap Analysis NIS2', en: 'NIS2 Gap Analysis' },
'assessment.new': { it: 'Nuovo Assessment', en: 'New Assessment' },
'assessment.start': { it: 'Inizia Assessment', en: 'Start Assessment' },
'assessment.complete': { it: 'Completa', en: 'Complete' },
'assessment.results': { it: 'Risultati Assessment', en: 'Assessment Results' },
'assessment.ai_analysis': { it: 'Analisi AI', en: 'AI Analysis' },
'assessment.generate_ncr': { it: 'Genera Non Conformita\' dai Gap', en: 'Generate NCRs from Gaps' },
'assessment.categories': { it: 'Categorie', en: 'Categories' },
'assessment.progress': { it: 'Avanzamento', en: 'Progress' },
'assessment.not_implemented': { it: 'Non Implementato', en: 'Not Implemented' },
'assessment.partial': { it: 'Parzialmente Implementato', en: 'Partially Implemented' },
'assessment.implemented': { it: 'Implementato', en: 'Implemented' },
'assessment.not_applicable': { it: 'Non Applicabile', en: 'Not Applicable' },
// ── Rischi ─────────────────────────────────────
'risks.title': { it: 'Gestione Rischi', en: 'Risk Management' },
'risks.fair_tab': { it: 'Quantitativo (FAIR)', en: 'Quantitative (FAIR)' },
'risks.kri_tab': { it: 'KRI', en: 'KRI' },
'risks.new': { it: 'Nuovo Rischio', en: 'New Risk' },
'risks.matrix': { it: 'Matrice Rischi', en: 'Risk Matrix' },
'risks.likelihood': { it: 'Probabilita\'', en: 'Likelihood' },
'risks.impact': { it: 'Impatto', en: 'Impact' },
'risks.treatment': { it: 'Trattamento', en: 'Treatment' },
'risks.mitigate': { it: 'Mitigare', en: 'Mitigate' },
'risks.accept': { it: 'Accettare', en: 'Accept' },
'risks.transfer': { it: 'Trasferire', en: 'Transfer' },
'risks.avoid': { it: 'Evitare', en: 'Avoid' },
// ── Incidenti ──────────────────────────────────
'incidents.title': { it: 'Gestione Incidenti', en: 'Incident Management' },
'incidents.new': { it: 'Nuovo Incidente', en: 'New Incident' },
'incidents.early_warning': { it: 'Early Warning (24h)', en: 'Early Warning (24h)' },
'incidents.notification': { it: 'Notifica (72h)', en: 'Notification (72h)' },
'incidents.final_report': { it: 'Report Finale (1 mese)', en: 'Final Report (1 month)' },
'incidents.timeline': { it: 'Cronologia', en: 'Timeline' },
'incidents.severity': { it: 'Gravita\'', en: 'Severity' },
'incidents.low': { it: 'Bassa', en: 'Low' },
'incidents.medium': { it: 'Media', en: 'Medium' },
'incidents.high': { it: 'Alta', en: 'High' },
'incidents.critical': { it: 'Critica', en: 'Critical' },
// ── Policy ─────────────────────────────────────
'policies.title': { it: 'Gestione Policy', en: 'Policy Management' },
'policies.new': { it: 'Nuova Policy', en: 'New Policy' },
'policies.ai_generate': { it: 'Genera con AI', en: 'Generate with AI' },
'policies.approve': { it: 'Approva', en: 'Approve' },
'policies.draft': { it: 'Bozza', en: 'Draft' },
'policies.review': { it: 'In Revisione', en: 'Under Review' },
'policies.approved': { it: 'Approvata', en: 'Approved' },
'policies.published': { it: 'Pubblicata', en: 'Published' },
// ── Supply Chain ───────────────────────────────
'supply_chain.title': { it: 'Sicurezza Supply Chain', en: 'Supply Chain Security' },
'supply_chain.new_supplier': { it: 'Nuovo Fornitore', en: 'New Supplier' },
'supply_chain.risk_overview': { it: 'Panoramica Rischi', en: 'Risk Overview' },
'supply_chain.assess': { it: 'Valuta Fornitore', en: 'Assess Supplier' },
// ── Formazione ─────────────────────────────────
'training.title': { it: 'Formazione e Awareness', en: 'Training & Awareness' },
'training.courses': { it: 'Corsi', en: 'Courses' },
'training.my_assignments': { it: 'I Miei Corsi', en: 'My Assignments' },
'training.assign': { it: 'Assegna Corso', en: 'Assign Course' },
'training.completed': { it: 'Completato', en: 'Completed' },
'training.overdue': { it: 'Scaduto', en: 'Overdue' },
// ── Asset ──────────────────────────────────────
'assets.title': { it: 'Inventario', en: 'Inventory' },
'assets.import_btn': { it: 'Importa', en: 'Import' },
'assets.new': { it: 'Nuovo Asset', en: 'New Asset' },
'assets.dependency_map': { it: 'Mappa Dipendenze', en: 'Dependency Map' },
'assets.hardware': { it: 'Hardware', en: 'Hardware' },
'assets.software': { it: 'Software', en: 'Software' },
'assets.network': { it: 'Rete', en: 'Network' },
'assets.data': { it: 'Dati', en: 'Data' },
// ── Audit ──────────────────────────────────────
'audit.title': { it: 'Audit & Report', en: 'Audit & Reports' },
'audit.monitoring_tab': { it: 'Monitoraggio Continuo', en: 'Continuous Monitoring' },
'audit.controls': { it: 'Controlli di Compliance', en: 'Compliance Controls' },
'audit.evidence': { it: 'Evidenze', en: 'Evidence' },
'audit.logs': { it: 'Log di Audit', en: 'Audit Logs' },
'audit.export': { it: 'Esporta Report', en: 'Export Report' },
// ── Impostazioni ───────────────────────────────
'settings.title': { it: 'Impostazioni', en: 'Settings' },
'settings.organization': { it: 'Organizzazione', en: 'Organization' },
'settings.profile': { it: 'Profilo Utente', en: 'User Profile' },
'settings.members': { it: 'Membri Team', en: 'Team Members' },
'settings.security': { it: 'Sicurezza', en: 'Security' },
// ── Classificazione NIS2 ───────────────────────
'classification.essential': { it: 'Soggetto Essenziale', en: 'Essential Entity' },
'classification.important': { it: 'Soggetto Importante', en: 'Important Entity' },
'classification.not_applicable': { it: 'Non Applicabile', en: 'Not Applicable' },
'classification.voluntary': { it: 'Adesione Volontaria', en: 'Voluntary Compliance' },
// ── Azioni comuni ──────────────────────────────
'action.save': { it: 'Salva', en: 'Save' },
'action.cancel': { it: 'Annulla', en: 'Cancel' },
'action.delete': { it: 'Elimina', en: 'Delete' },
'action.edit': { it: 'Modifica', en: 'Edit' },
'action.create': { it: 'Crea', en: 'Create' },
'action.close': { it: 'Chiudi', en: 'Close' },
'action.back': { it: 'Indietro', en: 'Back' },
'action.next': { it: 'Avanti', en: 'Next' },
'action.confirm': { it: 'Conferma', en: 'Confirm' },
'action.search': { it: 'Cerca', en: 'Search' },
'action.filter': { it: 'Filtra', en: 'Filter' },
'action.export': { it: 'Esporta', en: 'Export' },
'action.loading': { it: 'Caricamento...', en: 'Loading...' },
// ── Stato ──────────────────────────────────────
'status.draft': { it: 'Bozza', en: 'Draft' },
'status.in_progress': { it: 'In Corso', en: 'In Progress' },
'status.completed': { it: 'Completato', en: 'Completed' },
'status.open': { it: 'Aperto', en: 'Open' },
'status.closed': { it: 'Chiuso', en: 'Closed' },
'status.active': { it: 'Attivo', en: 'Active' },
'status.archived': { it: 'Archiviato', en: 'Archived' },
// ── Messaggi ───────────────────────────────────
'msg.save_success': { it: 'Salvato con successo!', en: 'Saved successfully!' },
'msg.delete_confirm': { it: 'Sei sicuro di voler eliminare?', en: 'Are you sure you want to delete?' },
'msg.error_generic': { it: 'Si e\' verificato un errore.', en: 'An error occurred.' },
'msg.error_connection': { it: 'Errore di connessione al server.', en: 'Server connection error.' },
'msg.no_data': { it: 'Nessun dato disponibile.', en: 'No data available.' },
// ── Sessione / Idle timeout ─────────────────────
'session.expiring_title': { it: 'Sessione in scadenza', en: 'Session expiring' },
'session.expiring_msg': { it: 'Per motivi di sicurezza, verrai disconnesso tra', en: 'For security reasons, you will be logged out in' },
'session.idle_reason': { it: 'a causa di inattivita\'.', en: 'due to inactivity.' },
'session.stay': { it: 'Rimani connesso', en: 'Stay connected' },
'session.logout_now': { it: 'Disconnetti', en: 'Log out' },
// ── Multi-device Sessions (Fase 2) ──────────────
'sessions.title': { it: 'Sessioni Attive', en: 'Active Sessions' },
'sessions.desc': { it: 'Dispositivi attualmente loggati al tuo account. Puoi disconnetterli singolarmente o tutti tranne questo.', en: 'Devices currently logged into your account. You can disconnect them individually or all but this one.' },
'sessions.current_device': { it: 'Questo dispositivo', en: 'This device' },
'sessions.revoke': { it: 'Disconnetti', en: 'Disconnect' },
'sessions.revoke_all': { it: 'Disconnetti gli altri', en: 'Disconnect others' },
'sessions.ip': { it: 'IP', en: 'IP' },
'sessions.last_activity': { it: 'Ultimo accesso', en: 'Last activity' },
'sessions.login': { it: 'Login', en: 'Login' },
'sessions.empty': { it: 'Nessuna sessione attiva.', en: 'No active sessions.' },
'sessions.confirm_revoke': { it: 'Disconnettere questo dispositivo?', en: 'Disconnect this device?' },
'sessions.confirm_revoke_all': { it: 'Disconnettere tutti gli altri dispositivi?', en: 'Disconnect all other devices?' },
// ── Password Reset (Fase 3) ─────────────────────
'pwreset.forgot_link': { it: 'Password dimenticata?', en: 'Forgot password?' },
'pwreset.forgot_title': { it: 'Reimposta la tua password', en: 'Reset your password' },
'pwreset.forgot_helper': { it: 'Inserisci l\'indirizzo email associato al tuo account. Ti invieremo un link valido 30 minuti.', en: 'Enter the email associated with your account. We will send you a link valid for 30 minutes.' },
'pwreset.send_link': { it: 'Invia link', en: 'Send link' },
'pwreset.set_new': { it: 'Imposta una nuova password', en: 'Set a new password' },
'pwreset.new_password': { it: 'Nuova password', en: 'New password' },
'pwreset.confirm': { it: 'Conferma password', en: 'Confirm password' },
'pwreset.submit': { it: 'Imposta password', en: 'Set password' },
'pwreset.back_login': { it: 'Torna al login', en: 'Back to login' },
'pwreset.strength_weak': { it: 'Debole', en: 'Weak' },
'pwreset.strength_medium': { it: 'Media', en: 'Medium' },
'pwreset.strength_good': { it: 'Buona', en: 'Good' },
'pwreset.strength_excellent':{ it: 'Ottima', en: 'Excellent' },
// ── Tenant switcher (Fase 3) ────────────────────
'tenant.select': { it: 'Seleziona azienda', en: 'Select company' },
'tenant.all': { it: '← Tutte le aziende', en: '← All companies' },
// ── Preferenze (Fase 4) ─────────────────────────
'preferences.title': { it: 'Preferenze utente', en: 'User preferences' },
'preferences.desc': { it: 'Personalizzazione interfaccia e notifiche.', en: 'Interface and notifications personalization.' },
'preferences.language': { it: 'Lingua', en: 'Language' },
'preferences.theme': { it: 'Tema', en: 'Theme' },
'preferences.theme_auto': { it: 'Auto (segue sistema)', en: 'Auto (follows system)' },
'preferences.theme_light': { it: 'Chiaro', en: 'Light' },
'preferences.theme_dark': { it: 'Scuro', en: 'Dark' },
'preferences.timezone': { it: 'Timezone', en: 'Timezone' },
'preferences.notif_email': { it: 'Ricevi notifiche via email (incidenti, deadline, training)', en: 'Receive email notifications (incidents, deadlines, training)' },
'preferences.notif_inapp': { it: 'Mostra notifiche in-app (badge sidebar)', en: 'Show in-app notifications (sidebar badges)' },
'preferences.save': { it: 'Salva preferenze', en: 'Save preferences' },
'preferences.saved': { it: '✓ Salvato', en: '✓ Saved' },
// ── Impersonate (Fase 4) ────────────────────────
'impersonate.btn': { it: 'Impersonate', en: 'Impersonate' },
'impersonate.tooltip': { it: 'Entra come questo utente per 1 ora', en: 'Enter as this user for 1 hour' },
'impersonate.confirm': { it: 'Entrare come {email} per 1 ora?', en: 'Enter as {email} for 1 hour?' },
'impersonate.banner': { it: 'Modalità Impersonate — stai usando l\'account di', en: 'Impersonate mode — you are using the account of' },
'impersonate.exit': { it: 'Esci impersonate', en: 'Exit impersonate' },
// ── Branding white-label (Fase 5) ───────────────
'branding.title': { it: 'Branding white-label', en: 'White-label branding' },
'branding.desc': { it: 'Personalizza colori, logo e nome prodotto per il tuo studio. Visibile a tutti i tuoi clienti loggati.', en: 'Customize colors, logo and product name for your firm. Visible to all your logged-in clients.' },
'branding.brand_name': { it: 'Nome prodotto custom (opzionale)', en: 'Custom product name (optional)' },
'branding.logo_url': { it: 'URL Logo (opzionale)', en: 'Logo URL (optional)' },
'branding.primary': { it: 'Colore primario', en: 'Primary color' },
'branding.secondary': { it: 'Colore secondario', en: 'Secondary color' },
'branding.save': { it: 'Salva branding', en: 'Save branding' },
'branding.saved': { it: '✓ Salvato — ricarica per vedere', en: '✓ Saved — reload to see' },
// ── Settings tabs (Fase 4-5) ────────────────────
'settings.preferences': { it: 'Preferenze', en: 'Preferences' },
'settings.branding': { it: 'Branding', en: 'Branding' },
// ── Tempo relativo ─────────────────────────────
'time.now': { it: 'Adesso', en: 'Just now' },
'time.min_ago': { it: 'min fa', en: 'min ago' },
'time.hours_ago': { it: 'ore fa', en: 'hours ago' },
'time.yesterday': { it: 'Ieri', en: 'Yesterday' },
'time.days_ago': { it: 'giorni fa', en: 'days ago' },
// ── Compliance Journey / Workflow ──────────────
'workflow.title': { it: 'Compliance Journey', en: 'Compliance Journey' },
'workflow.progress': { it: 'Progresso Art.21 NIS2', en: 'NIS2 Art.21 Progress' },
'workflow.implementation': { it: 'implementazione misure', en: 'measures implementation' },
'workflow.next_action': { it: 'Prossima azione consigliata', en: 'Recommended next action' },
'workflow.phase': { it: 'Fase', en: 'Phase' },
'workflow.go_to_module': { it: 'Vai al modulo', en: 'Go to module' },
'workflow.ph1': { it: 'Preparazione', en: 'Preparation' },
'workflow.ph2': { it: 'Valutazione', en: 'Assessment' },
'workflow.ph3': { it: 'Rischi', en: 'Risks' },
'workflow.ph4': { it: 'Implementazione', en: 'Implementation' },
'workflow.ph5': { it: 'Monitoraggio', en: 'Monitoring' },
'workflow.ph6': { it: 'Reportistica', en: 'Reporting' },
'workflow.status_done': { it: 'Completato', en: 'Completed' },
'workflow.status_partial': { it: 'In corso', en: 'In progress' },
'workflow.status_active': { it: 'Attivo', en: 'Active' },
'workflow.status_todo': { it: 'Da iniziare', en: 'To do' },
'nav.workflow': { it: 'Compliance Journey', en: 'Compliance Journey' },
};
// ── API Pubblica ──────────────────────────────────────────────────
/**
* Inizializza i18n: carica lingua da localStorage o parametro.
*/
function init(defaultLang) {
_lang = localStorage.getItem('nis2_lang') || defaultLang || 'it';
applyTranslations();
}
/**
* Ritorna la lingua corrente.
*/
function getLang() {
return _lang;
}
/**
* Cambia lingua e ri-applica traduzioni.
*/
function setLang(lang) {
if (lang !== 'it' && lang !== 'en') return;
_lang = lang;
localStorage.setItem('nis2_lang', lang);
applyTranslations();
}
/**
* Traduce una chiave.
* @param {string} key - Chiave di traduzione (es. 'nav.dashboard')
* @param {object} [params] - Parametri per interpolazione {name: 'valore'}
* @returns {string}
*/
function t(key, params) {
const entry = _dict[key];
if (!entry) return key;
let text = entry[_lang] || entry['it'] || key;
if (params) {
Object.keys(params).forEach(k => {
text = text.replace(new RegExp('\\{' + k + '\\}', 'g'), params[k]);
});
}
return text;
}
/**
* Applica traduzioni a tutti gli elementi con data-i18n.
* Attributo data-i18n="chiave" → traduce textContent
* Attributo data-i18n-placeholder="chiave" → traduce placeholder
* Attributo data-i18n-title="chiave" → traduce title
*/
function applyTranslations() {
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
el.textContent = t(key);
});
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
el.placeholder = t(el.getAttribute('data-i18n-placeholder'));
});
document.querySelectorAll('[data-i18n-title]').forEach(el => {
el.title = t(el.getAttribute('data-i18n-title'));
});
document.querySelectorAll('[data-i18n-html]').forEach(el => {
el.innerHTML = t(el.getAttribute('data-i18n-html'));
});
}
/**
* Registra nuove traduzioni (merge).
*/
function addTranslations(entries) {
Object.assign(_dict, entries);
}
// ── Feedback & Segnalazioni ─────────────────────────────────
addTranslations({
'feedback.fab_title': { it: 'Segnala un problema o un miglioramento', en: 'Report an issue or improvement' },
'feedback.tab_new': { it: 'Nuova segnalazione', en: 'New report' },
'feedback.tab_mine': { it: 'Le mie segnalazioni', en: 'My reports' },
'feedback.tipo_label': { it: 'Tipo', en: 'Type' },
'feedback.tipo_bug': { it: 'Bug / Errore', en: 'Bug / Error' },
'feedback.tipo_ux': { it: 'Miglioramento interfaccia', en: 'UX improvement' },
'feedback.tipo_funzionalita':{ it: 'Nuova funzionalità', en: 'New feature' },
'feedback.tipo_domanda': { it: 'Domanda / Supporto', en: 'Question / Support' },
'feedback.tipo_altro': { it: 'Altro', en: 'Other' },
'feedback.priorita_label': { it: 'Priorità percepita', en: 'Perceived priority' },
'feedback.priorita_alta': { it: 'Alta', en: 'High' },
'feedback.priorita_media': { it: 'Media', en: 'Medium' },
'feedback.priorita_bassa': { it: 'Bassa', en: 'Low' },
'feedback.desc_label': { it: 'Descrizione', en: 'Description' },
'feedback.desc_hint': { it: 'Sii specifico: cosa hai fatto, cosa ti aspettavi, cosa è successo', en: 'Be specific: what you did, what you expected, what happened' },
'feedback.attach_label': { it: 'Screenshot (opzionale)', en: 'Screenshot (optional)' },
'feedback.submit_btn': { it: 'Invia segnalazione', en: 'Submit report' },
'feedback.submitting': { it: 'Analisi AI in corso…', en: 'AI analysis in progress…' },
'feedback.ai_badge': { it: 'Analisi AI completata', en: 'AI analysis complete' },
'feedback.resolve_question': { it: 'Il problema è stato risolto grazie a questo suggerimento?', en: 'Was the problem resolved by this suggestion?' },
'feedback.resolve_yes': { it: 'Sì, problema risolto', en: 'Yes, problem resolved' },
'feedback.resolve_no': { it: 'No, il problema persiste', en: 'No, problem persists' },
'feedback.resolve_password': { it: 'Conferma con password di verifica', en: 'Confirm with verification password' },
'feedback.resolve_confirm': { it: 'Conferma', en: 'Confirm' },
'feedback.resolved_ok': { it: 'Segnalazione marcata come risolta. Grazie per il feedback.', en: 'Report marked as resolved. Thank you for your feedback.' },
'feedback.status_aperto': { it: 'Aperto', en: 'Open' },
'feedback.status_lavorazione': { it: 'In lavorazione', en: 'In progress' },
'feedback.status_risolto': { it: 'Risolto', en: 'Resolved' },
'feedback.status_chiuso': { it: 'Chiuso', en: 'Closed' },
'feedback.empty': { it: 'Nessuna segnalazione ancora. Usala per segnalare problemi!', en: 'No reports yet. Use it to report issues!' },
'feedback.error_short': { it: 'Descrizione troppo breve (minimo 10 caratteri).', en: 'Description too short (minimum 10 characters).' },
'feedback.error_password': { it: 'Password non corretta.', en: 'Incorrect password.' },
'feedback.notif_open': { it: 'Segnalazione registrata. Il team la prenderà in carico.', en: 'Report registered. The team will handle it.' },
});
// ── Supply Chain / Fornitori (modulo questionari Fase 1) ────────────
addTranslations({
'sp.import_btn': { it: 'Importa', en: 'Import' },
'sp.categories_btn': { it: 'Categorie', en: 'Categories' },
'sp.templates_btn': { it: 'Template', en: 'Templates' },
'sp.new_supplier': { it: '+ Nuovo Fornitore', en: '+ New Supplier' },
'sp.category_field': { it: 'Categoria fornitore', en: 'Supplier category' },
'sp.category_none': { it: '— Nessuna —', en: '— None —' },
'sp.category_hint': { it: 'Classifica il fornitore (es. Cloud Provider). Gestisci le categorie dal pulsante "Categorie".', en: 'Classify the supplier (e.g. Cloud Provider). Manage categories from the "Categories" button.' },
// Categorie
'sp.cat_title': { it: 'Categorie fornitore', en: 'Supplier categories' },
'sp.cat_new': { it: '+ Nuova categoria', en: '+ New category' },
'sp.cat_preset': { it: 'Preset', en: 'Preset' },
'sp.cat_custom': { it: 'Personalizzata', en: 'Custom' },
'sp.cat_not_editable': { it: 'non modificabile', en: 'not editable' },
'sp.cat_name': { it: 'Nome categoria', en: 'Category name' },
'sp.cat_desc': { it: 'Descrizione', en: 'Description' },
'sp.cat_preset_note': { it: 'Le categorie preset sono fornite dal sistema e non sono modificabili. Puoi creare categorie personalizzate per la tua organizzazione.', en: 'Preset categories are provided by the system and cannot be modified. You can create custom categories for your organization.' },
'sp.cat_name_required': { it: 'Il nome della categoria è obbligatorio.', en: 'Category name is required.' },
'sp.cat_created': { it: 'Categoria creata.', en: 'Category created.' },
'sp.cat_updated': { it: 'Categoria aggiornata.', en: 'Category updated.' },
'sp.cat_deleted': { it: 'Categoria eliminata.', en: 'Category deleted.' },
'sp.cat_delete_confirm': { it: 'Eliminare la categoria?', en: 'Delete this category?' },
'sp.cat_in_use': { it: 'Impossibile eliminare (categoria in uso?).', en: 'Cannot delete (category in use?).' },
// Template
'sp.tpl_title': { it: 'Template questionari', en: 'Questionnaire templates' },
'sp.tpl_empty': { it: 'Nessun template questionario per questa organizzazione.', en: 'No questionnaire template for this organization.' },
'sp.tpl_active': { it: 'Attivo', en: 'Active' },
'sp.tpl_default': { it: 'Default', en: 'Default' },
'sp.tpl_view_questions': { it: 'Vedi domande', en: 'View questions' },
'sp.tpl_back': { it: '← Torna ai template', en: '← Back to templates' },
'sp.tpl_readonly': { it: "Sola lettura. L'editor delle domande sarà disponibile in una prossima versione.", en: 'Read-only. The question editor will be available in a future version.' },
'sp.tpl_required': { it: 'Obbligatoria', en: 'Required' },
'sp.tpl_high_crit': { it: 'Solo alta criticità', en: 'High criticality only' },
'sp.tpl_weight': { it: 'Peso', en: 'Weight' },
'sp.tpl_questions': { it: 'domande', en: 'questions' },
// Import
'sp.import_title': { it: 'Importa fornitori', en: 'Import suppliers' },
'sp.import_run': { it: 'Importa', en: 'Import' },
'sp.import_no_rows': { it: 'Nessuna riga valida (serve intestazione + almeno un fornitore con "name").', en: 'No valid rows (header + at least one supplier with "name" required).' },
'sp.import_too_many': { it: 'Troppi record (max 1000).', en: 'Too many records (max 1000).' },
'sp.import_done': { it: 'Completato: creati {created}, aggiornati {updated}, saltati {skipped}.', en: 'Done: {created} created, {updated} updated, {skipped} skipped.' },
'sp.conn_error': { it: 'Errore di connessione.', en: 'Connection error.' },
});
return { init, getLang, setLang, t, applyTranslations, addTranslations };
})();
// Shortcut globale
function t(key, params) { return I18n.t(key, params); }