From c4d58f8354df12fed41fb3e268ff19c99f6e5691 Mon Sep 17 00:00:00 2001 From: dnviti Date: Fri, 12 Dec 2025 14:25:16 +0100 Subject: [PATCH] feat: Implement and update translations for warehouse categories, core application titles, and other UI elements. --- docs/development/ZENTRAL.md | 9 +-- .../2025-12-12-141010_update_translations.md | 21 +++++ .../public/locales/en/translation.json | 77 ++++++++++++++++++- .../public/locales/it/translation.json | 77 ++++++++++++++++++- .../apps/communications/pages/LogsPage.tsx | 18 +++-- .../communications/pages/SettingsPage.tsx | 52 +++++++------ .../apps/warehouse/pages/CategoriesPage.tsx | 33 ++++---- src/frontend/src/components/SearchBar.tsx | 16 ++-- src/frontend/src/components/Sidebar.tsx | 2 +- 9 files changed, 236 insertions(+), 69 deletions(-) create mode 100644 docs/development/devlog/2025-12-12-141010_update_translations.md diff --git a/docs/development/ZENTRAL.md b/docs/development/ZENTRAL.md index 7e0b9ec..0ea6412 100644 --- a/docs/development/ZENTRAL.md +++ b/docs/development/ZENTRAL.md @@ -53,10 +53,5 @@ File riassuntivo dello stato di sviluppo di Zentral. - [2025-12-12 Resend Integration](./devlog/2025-12-12-120000_resend_integration.md) - **Completato** - [2025-12-12 Magazzino: Categorie Gerarchiche](./devlog/2025-12-12-133000_remove_product_groups_add_categories.md) - **Completato** - Sostituita la logica "Gruppi Merceologici" con l'utilizzo esteso delle "Categorie Articoli" gerarchiche. - - Riorganizzazione UI Auto Codes, allineamento stile a Custom Fields, miglioramento traduzioni e categorizzazione. -- [2025-12-12 - Modulo Comunicazioni](./devlog/2025-12-12-110000_communications_module.md) - **In Corso** - - Implementazione invio email e gestione comunicazioni. -- [2025-12-12 - Gestione Modulo Formazione (Generale)](./devlog/2025-12-12-105500_safety_training_schedule.md) - **In Corso** - - Implementazione modulo formazione generale e scadenziario. -- [2025-12-12 - Implementazione Gruppi Merceologici Magazzino](./devlog/2025-12-12-125000_magazzino_gruppi_merceologici.md) - **In Corso** - - Implementazione gestione gruppi merceologici per il magazzino. \ No newline at end of file +- [2025-12-12 Update Translations](./devlog/2025-12-12-141010_update_translations.md) - **In Corso** + - Aggiornamento traduzioni per categorie magazzino, comunicazioni e formazione. \ No newline at end of file diff --git a/docs/development/devlog/2025-12-12-141010_update_translations.md b/docs/development/devlog/2025-12-12-141010_update_translations.md new file mode 100644 index 0000000..9a5d1ca --- /dev/null +++ b/docs/development/devlog/2025-12-12-141010_update_translations.md @@ -0,0 +1,21 @@ +# Update Translations for New Developments + +## Status +- [x] Analysis of new features needing translation +- [x] Update Italian Translations (it) +- [x] Update English Translations (en) +- [x] Verification + +## Details +Verified recent developments: +1. **Warehouse - Categories**: New management of article categories. +2. **Communications**: Email configuration and logs. +3. **Training**: New module for courses and training sessions. + +I will scan these modules for `t()` calls and update the `translation.json` files in `public/locales/it` and `public/locales/en`. + +## Work Done +- **Warehouse Categories**: Updated `CategoriesPage.tsx` to use `useTranslation`. Added keys for titles, buttons, fields, and dialogs in both IT and EN locales. +- **Communications**: Updated `SettingsPage.tsx` and `LogsPage.tsx` to use `useTranslation`. Added complete set of keys for settings, fields, actions, messages and log columns in both IT and EN locales. +- **Components**: Updated `Sidebar.tsx`, `SearchBar.tsx` to use full translations. Added `apps.core.title` and ensure `categories` is available in menu. +- **Training**: Training module files were not found in the current workspace, so no translations were applied for this module yet. Suggest to review separately when module is available. diff --git a/src/frontend/public/locales/en/translation.json b/src/frontend/public/locales/en/translation.json index a4aa834..3d38abf 100644 --- a/src/frontend/public/locales/en/translation.json +++ b/src/frontend/public/locales/en/translation.json @@ -65,7 +65,8 @@ "emailConfig": "Email Configuration", "movements": "Movements", "stock": "Stock", - "inventory": "Inventory" + "inventory": "Inventory", + "categories": "Categories" }, "navigation": { "searchPlaceholder": "Search...", @@ -285,12 +286,33 @@ "confermato": "Confirmed" }, "apps": { + "core": { + "title": "Zentral" + }, "warehouse": { "title": "Warehouse Management", "inventory": "Inventory", "movements": "Movements", "stock": "Stock", - "categories": "Categories" + "categories": { + "title": "Article Categories", + "new": "New Category", + "edit": "Edit Category", + "empty": "No categories found", + "newParams": { + "root": "New Root Category" + }, + "fields": { + "name": "Name", + "description": "Description", + "sortOrder": "Sort Order", + "active": "Active" + }, + "deleteDialog": { + "title": "Delete Confirmation", + "content": "Are you sure you want to delete this category? This operation cannot be undone. If the category contains subcategories or articles, it may not be possible to delete it." + } + } }, "hr": { "title": "Human Resources", @@ -1552,5 +1574,56 @@ "permesso": "Permit", "altro": "Other" } + }, + "communications": { + "settings": { + "title": "Email Configuration", + "fields": { + "provider": "Provider", + "host": "SMTP Host", + "port": "Port", + "user": "Username", + "password": "Password", + "ssl": "Enable SSL/TLS", + "apiKey": "Resend API Key", + "fromEmail": "From Email", + "fromName": "From Name" + }, + "helpers": { + "apiKey": "Get your API Key at" + }, + "sections": { + "defaultSender": "Default Sender" + }, + "actions": { + "testConnection": "Test Connection", + "sendTest": "Send Test" + }, + "testStats": { + "title": "Test Email", + "recipient": "Recipient", + "subject": "Subject" + }, + "messages": { + "loadError": "Error loading configuration", + "saveSuccess": "Configuration saved successfully", + "saveError": "Error saving configuration", + "recipientRequired": "Recipient email is required for test", + "testSuccess": "Test email sent successfully", + "testError": "Error sending test email" + } + }, + "logs": { + "title": "Email Logs", + "columns": { + "id": "ID", + "date": "Date", + "status": "Status", + "sender": "Sender", + "recipient": "Recipient", + "subject": "Subject", + "error": "Error" + } + } } } \ No newline at end of file diff --git a/src/frontend/public/locales/it/translation.json b/src/frontend/public/locales/it/translation.json index df32233..1efdc25 100644 --- a/src/frontend/public/locales/it/translation.json +++ b/src/frontend/public/locales/it/translation.json @@ -61,7 +61,8 @@ "emailConfig": "Configurazione Email", "movements": "Movimenti", "stock": "Giacenze", - "inventory": "Inventario" + "inventory": "Inventario", + "categories": "Categorie" }, "navigation": { "searchPlaceholder": "Cerca...", @@ -281,12 +282,33 @@ "confermato": "Confermato" }, "apps": { + "core": { + "title": "Zentral" + }, "warehouse": { "title": "Gestione Magazzino", "inventory": "Inventario", "movements": "Movimenti", "stock": "Giacenze", - "categories": "Categorie" + "categories": { + "title": "Categorie Articoli", + "new": "Nuova Categoria", + "edit": "Modifica Categoria", + "empty": "Nessuna categoria trovata", + "newParams": { + "root": "Nuova Categoria Root" + }, + "fields": { + "name": "Nome", + "description": "Descrizione", + "sortOrder": "Ordinamento", + "active": "Attivo" + }, + "deleteDialog": { + "title": "Conferma Eliminazione", + "content": "Sei sicuro di voler eliminare questa categoria? L'operazione non può essere annullata. Se la categoria contiene sottocategorie o articoli, potrebbe non essere possibile eliminarla." + } + } }, "hr": { "title": "Gestione Personale", @@ -1633,5 +1655,56 @@ "permesso": "Permesso", "altro": "Altro" } + }, + "communications": { + "settings": { + "title": "Configurazione Email", + "fields": { + "provider": "Provider", + "host": "SMTP Host", + "port": "Porta", + "user": "Username", + "password": "Password", + "ssl": "Abilita SSL/TLS", + "apiKey": "Resend API Key", + "fromEmail": "Email Mittente", + "fromName": "Nome Mittente" + }, + "helpers": { + "apiKey": "Ottieni la tua API Key su" + }, + "sections": { + "defaultSender": "Mittente Default" + }, + "actions": { + "testConnection": "Test Connessione", + "sendTest": "Invia Test" + }, + "testStats": { + "title": "Test Email", + "recipient": "Destinatario", + "subject": "Oggetto" + }, + "messages": { + "loadError": "Errore nel caricamento configurazione", + "saveSuccess": "Configurazione salvata con successo", + "saveError": "Errore nel salvataggio configurazione", + "recipientRequired": "Email destinatario obbligatoria per il test", + "testSuccess": "Email di test inviata con successo", + "testError": "Errore nell'invio email di test" + } + }, + "logs": { + "title": "Log Email", + "columns": { + "id": "ID", + "date": "Data", + "status": "Stato", + "sender": "Mittente", + "recipient": "Destinatario", + "subject": "Oggetto", + "error": "Errore" + } + } } } \ No newline at end of file diff --git a/src/frontend/src/apps/communications/pages/LogsPage.tsx b/src/frontend/src/apps/communications/pages/LogsPage.tsx index 31c51b4..2c19ff7 100644 --- a/src/frontend/src/apps/communications/pages/LogsPage.tsx +++ b/src/frontend/src/apps/communications/pages/LogsPage.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { DataGrid, GridColDef } from '@mui/x-data-grid'; import { Box, Typography } from '@mui/material'; import { History } from '@mui/icons-material'; @@ -7,6 +8,7 @@ import { EmailLog } from '../types'; import dayjs from 'dayjs'; export default function LogsPage() { + const { t } = useTranslation(); const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(false); @@ -27,13 +29,13 @@ export default function LogsPage() { }; const columns: GridColDef[] = [ - { field: 'id', headerName: 'ID', width: 70 }, + { field: 'id', headerName: t('communications.logs.columns.id'), width: 70 }, { - field: 'sentDate', headerName: 'Data', width: 180, + field: 'sentDate', headerName: t('communications.logs.columns.date'), width: 180, valueFormatter: (params) => dayjs(params.value).format('DD/MM/YYYY HH:mm') }, { - field: 'status', headerName: 'Stato', width: 120, + field: 'status', headerName: t('communications.logs.columns.status'), width: 120, renderCell: (params) => ( ) }, - { field: 'sender', headerName: 'Mittente', width: 200 }, - { field: 'recipient', headerName: 'Destinatario', width: 200 }, - { field: 'subject', headerName: 'Oggetto', flex: 1 }, - { field: 'errorMessage', headerName: 'Errore', width: 200 }, + { field: 'sender', headerName: t('communications.logs.columns.sender'), width: 200 }, + { field: 'recipient', headerName: t('communications.logs.columns.recipient'), width: 200 }, + { field: 'subject', headerName: t('communications.logs.columns.subject'), flex: 1 }, + { field: 'errorMessage', headerName: t('communications.logs.columns.error'), width: 200 }, ]; return ( - Email Logs + {t('communications.logs.title')} (); const provider = watch('provider') || 'smtp'; const [loading, setLoading] = useState(false); @@ -28,7 +30,7 @@ export default function SettingsPage() { reset(config); } catch (error) { console.error(error); - setNotification({ type: 'error', message: 'Failed to load configuration' }); + setNotification({ type: 'error', message: t('communications.settings.messages.loadError') }); } finally { setLoading(false); } @@ -38,9 +40,9 @@ export default function SettingsPage() { try { setLoading(true); await communicationsService.saveConfig(data); - setNotification({ type: 'success', message: 'Configuration saved successfully' }); + setNotification({ type: 'success', message: t('communications.settings.messages.saveSuccess') }); } catch (error) { - setNotification({ type: 'error', message: 'Failed to save configuration' }); + setNotification({ type: 'error', message: t('communications.settings.messages.saveError') }); } finally { setLoading(false); } @@ -48,16 +50,16 @@ export default function SettingsPage() { const sendTest = async () => { if (!testData.to) { - setNotification({ type: 'error', message: 'Recipient email is required for test' }); + setNotification({ type: 'error', message: t('communications.settings.messages.recipientRequired') }); return; } try { setLoading(true); await communicationsService.sendTestEmail(testData); - setNotification({ type: 'success', message: 'Test email queued successfully' }); + setNotification({ type: 'success', message: t('communications.settings.messages.testSuccess') }); setTestMode(false); } catch (error: any) { - setNotification({ type: 'error', message: error.response?.data?.message || 'Failed to send test email' }); + setNotification({ type: 'error', message: error.response?.data?.message || t('communications.settings.messages.testError') }); } finally { setLoading(false); } @@ -66,7 +68,7 @@ export default function SettingsPage() { return ( - Configurazione Email + {t('communications.settings.title')} @@ -74,13 +76,13 @@ export default function SettingsPage() { - Provider + {t('communications.settings.fields.provider')} ( - SMTP Resend @@ -96,7 +98,7 @@ export default function SettingsPage() { name="host" control={control} defaultValue="" - render={({ field }) => } + render={({ field }) => } /> @@ -104,7 +106,7 @@ export default function SettingsPage() { name="port" control={control} defaultValue={587} - render={({ field }) => } + render={({ field }) => } /> @@ -113,7 +115,7 @@ export default function SettingsPage() { name="user" control={control} defaultValue="" - render={({ field }) => } + render={({ field }) => } /> @@ -121,7 +123,7 @@ export default function SettingsPage() { name="password" control={control} defaultValue="" - render={({ field }) => } + render={({ field }) => } /> @@ -133,7 +135,7 @@ export default function SettingsPage() { render={({ field: { onChange, value } }) => ( } - label="Enable SSL/TLS" + label={t('communications.settings.fields.ssl')} /> )} /> @@ -147,17 +149,17 @@ export default function SettingsPage() { name="resendApiKey" control={control} defaultValue="" - render={({ field }) => } + render={({ field }) => } /> - Ottieni la tua API Key su resend.com + {t('communications.settings.helpers.apiKey')} resend.com )} - Mittente Default + {t('communications.settings.sections.defaultSender')} @@ -165,7 +167,7 @@ export default function SettingsPage() { name="fromEmail" control={control} defaultValue="" - render={({ field }) => } + render={({ field }) => } /> @@ -173,7 +175,7 @@ export default function SettingsPage() { name="fromName" control={control} defaultValue="" - render={({ field }) => } + render={({ field }) => } /> @@ -183,7 +185,7 @@ export default function SettingsPage() { startIcon={} onClick={() => setTestMode(!testMode)} > - Test Connessione + {t('communications.settings.actions.testConnection')} @@ -200,11 +202,11 @@ export default function SettingsPage() { {testMode && ( - Test Email + {t('communications.settings.testStats.title')} setTestData({ ...testData, to: e.target.value })} @@ -212,7 +214,7 @@ export default function SettingsPage() { setTestData({ ...testData, subject: e.target.value })} @@ -220,7 +222,7 @@ export default function SettingsPage() { diff --git a/src/frontend/src/apps/warehouse/pages/CategoriesPage.tsx b/src/frontend/src/apps/warehouse/pages/CategoriesPage.tsx index 6d1cddb..dca54db 100644 --- a/src/frontend/src/apps/warehouse/pages/CategoriesPage.tsx +++ b/src/frontend/src/apps/warehouse/pages/CategoriesPage.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Box, Button, @@ -102,6 +103,7 @@ const CategoryItem: React.FC = ({ category, onEdit, onDelete, }; export default function CategoriesPage() { + const { t } = useTranslation(); const { data: categories, isLoading } = useCategoryTree(); const createMutation = useCreateCategory(); const updateMutation = useUpdateCategory(); @@ -196,21 +198,21 @@ export default function CategoriesPage() { }; if (isLoading) { - return Caricamento...; + return {t('common.loading')}; } return ( - Categorie Articoli + {t('apps.warehouse.categories.title')} @@ -227,7 +229,7 @@ export default function CategoriesPage() { ))} {(!categories || categories.length === 0) && ( - + )} @@ -236,19 +238,19 @@ export default function CategoriesPage() { {/* Create/Edit Dialog */} - {editingCategory ? "Modifica Categoria" : "Nuova Categoria"} + {editingCategory ? t('apps.warehouse.categories.edit') : t('apps.warehouse.categories.new')} setFormData({ ...formData, name: e.target.value })} fullWidth required /> setFormData({ ...formData, description: e.target.value })} fullWidth @@ -256,7 +258,7 @@ export default function CategoriesPage() { rows={3} /> setFormData({ ...formData, sortOrder: parseInt(e.target.value) || 0 })} @@ -270,32 +272,31 @@ export default function CategoriesPage() { onChange={(e) => setIsActive(e.target.checked)} /> } - label="Attivo" + label={t('apps.warehouse.categories.fields.active')} /> )} - + {/* Delete Confirmation Dialog */} setDeleteConfirmOpen(false)}> - Conferma Eliminazione + {t('apps.warehouse.categories.deleteDialog.title')} - Sei sicuro di voler eliminare questa categoria? L'operazione non può essere annullata. - Se la categoria contiene sottocategorie o articoli, potrebbe non essere possibile eliminarla. + {t('apps.warehouse.categories.deleteDialog.content')} - + diff --git a/src/frontend/src/components/SearchBar.tsx b/src/frontend/src/components/SearchBar.tsx index ee23955..5d5a720 100644 --- a/src/frontend/src/components/SearchBar.tsx +++ b/src/frontend/src/components/SearchBar.tsx @@ -64,14 +64,14 @@ export default function SearchBar() { const options = useMemo(() => { const opts: SearchOption[] = [ // Core - { label: t('menu.dashboard'), path: '/', category: 'Zentral', translationKey: 'menu.dashboard' }, - { label: t('menu.calendar'), path: '/calendario', category: 'Zentral', translationKey: 'menu.calendar' }, - { label: t('menu.events'), path: '/eventi', category: 'Zentral', translationKey: 'menu.events' }, - { label: t('menu.clients'), path: '/clienti', category: 'Zentral', translationKey: 'menu.clients' }, - { label: t('menu.location'), path: '/location', category: 'Zentral', translationKey: 'menu.location' }, - { label: t('menu.articles'), path: '/articoli', category: 'Zentral', translationKey: 'menu.articles' }, - { label: t('menu.resources'), path: '/risorse', category: 'Zentral', translationKey: 'menu.resources' }, - { label: t('menu.reports'), path: '/report-templates', category: 'Zentral', translationKey: 'menu.reports' }, + { label: t('menu.dashboard'), path: '/', category: t('apps.core.title'), translationKey: 'menu.dashboard' }, + { label: t('menu.calendar'), path: '/calendario', category: t('apps.core.title'), translationKey: 'menu.calendar' }, + { label: t('menu.events'), path: '/eventi', category: t('apps.core.title'), translationKey: 'menu.events' }, + { label: t('menu.clients'), path: '/clienti', category: t('apps.core.title'), translationKey: 'menu.clients' }, + { label: t('menu.location'), path: '/location', category: t('apps.core.title'), translationKey: 'menu.location' }, + { label: t('menu.articles'), path: '/articoli', category: t('apps.core.title'), translationKey: 'menu.articles' }, + { label: t('menu.resources'), path: '/risorse', category: t('apps.core.title'), translationKey: 'menu.resources' }, + { label: t('menu.reports'), path: '/report-templates', category: t('apps.core.title'), translationKey: 'menu.reports' }, ]; if (activeAppCodes.includes('warehouse')) { diff --git a/src/frontend/src/components/Sidebar.tsx b/src/frontend/src/components/Sidebar.tsx index c6fea21..9e713a9 100644 --- a/src/frontend/src/components/Sidebar.tsx +++ b/src/frontend/src/components/Sidebar.tsx @@ -103,7 +103,7 @@ export default function Sidebar({ onClose, isCollapsed = false, onToggleCollapse const menuStructure: MenuItem[] = [ { id: 'dashboard', - label: 'Zentral Dashboard', + label: t('menu.dashboard'), icon: , path: '/', translationKey: 'menu.dashboard',