From e4504d05907b970fcfc88625e002d75f1ba01243 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Fri, 15 May 2026 15:39:58 +0200 Subject: [PATCH 01/13] added translation for the page Category (no for the alla compoenentes insede category) --- src/components/Category/Category.tsx | 12 +++-- src/components/Task/GlobalTaskSearch.tsx | 62 +++++++++++------------- src/components/UI/SearchTasksButton.tsx | 5 +- src/locales/en.json | 58 ++++++++++++++++++++++ src/locales/it.json | 58 ++++++++++++++++++++++ 5 files changed, 154 insertions(+), 41 deletions(-) diff --git a/src/components/Category/Category.tsx b/src/components/Category/Category.tsx index 6604ad3..668ccd7 100644 --- a/src/components/Category/Category.tsx +++ b/src/components/Category/Category.tsx @@ -10,6 +10,7 @@ import ShareCategoryDialog from './ShareCategoryDialog'; import ManageCategoryShares from './ManageCategoryShares'; import eventEmitter, { emitCategoryDeleted, emitCategoryUpdated, emitTaskAdded , EVENTS } from '../../utils/eventEmitter'; import GoogleCalendarService from '../../services/googleCalendarService'; +import { useTranslation } from "react-i18next"; export interface CategoryProps { @@ -41,6 +42,7 @@ const Category: React.FC = ({ onEdit, onPressCategory }) => { + const { t } = useTranslation(); const [showMenu, setShowMenu] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); @@ -145,8 +147,8 @@ const Category: React.FC = ({ // Only owners can delete if (!isOwned) { Alert.alert( - "Permesso negato", - "Solo il proprietario può eliminare questa categoria." + t("categories.permissions.denied"), + t("categories.permissions.ownerOnlyDelete") ); closeMenu(); return; @@ -183,7 +185,7 @@ const Category: React.FC = ({ } catch (error) { console.error("Errore durante l'eliminazione della categoria:", error); setShowDeleteModal(false); - Alert.alert("Errore", "Impossibile eliminare la categoria. Riprova più tardi."); + Alert.alert(t("categories.delete.error"), t("categories.delete.failed")); } finally { setIsDeleting(false); } @@ -199,8 +201,8 @@ const Category: React.FC = ({ // Check permissions - owners and READ_WRITE users can edit if (!isOwned && permissionLevel === "READ_ONLY") { Alert.alert( - "Permesso negato", - "Hai solo permessi di lettura per questa categoria. Non puoi modificarla." + t("categories.permissions.denied"), + t("categories.permissions.readOnly") ); closeMenu(); return; diff --git a/src/components/Task/GlobalTaskSearch.tsx b/src/components/Task/GlobalTaskSearch.tsx index c50411f..32e9ae4 100644 --- a/src/components/Task/GlobalTaskSearch.tsx +++ b/src/components/Task/GlobalTaskSearch.tsx @@ -14,6 +14,7 @@ import SearchBar from "../UI/SearchBar"; import { getAllTasks, Task as TaskType } from "../../services/taskService"; import Task from "./Task"; import eventEmitter, { EVENTS } from '../../utils/eventEmitter'; +import { useTranslation } from "react-i18next"; export interface GlobalTaskSearchProps { visible: boolean; @@ -24,6 +25,7 @@ const GlobalTaskSearch: React.FC = ({ visible, onClose, }) => { + const { t } = useTranslation(); const [searchQuery, setSearchQuery] = useState(""); const [allTasks, setAllTasks] = useState([]); const [loading, setLoading] = useState(false); @@ -102,7 +104,7 @@ const GlobalTaskSearch: React.FC = ({ setHasLoaded(true); } catch (error) { console.error("Errore nel caricamento dei task:", error); - Alert.alert("Errore", "Impossibile caricare i task per la ricerca"); + Alert.alert(t("globalTaskSearch.error"), t("globalTaskSearch.loadFailed")); } finally { setLoading(false); } @@ -163,7 +165,7 @@ const GlobalTaskSearch: React.FC = ({ return ( - Caricamento task... + {t("globalTaskSearch.loadingTasks")} ); } @@ -172,7 +174,7 @@ const GlobalTaskSearch: React.FC = ({ return ( - Caricamento in corso... + {t("globalTaskSearch.loadingInProgress")} ); } @@ -182,7 +184,7 @@ const GlobalTaskSearch: React.FC = ({ - Nessun task trovato per "{searchQuery}" + {t("globalTaskSearch.noTasksFound", { query: searchQuery })} ); @@ -192,7 +194,7 @@ const GlobalTaskSearch: React.FC = ({ return ( - Nessun task disponibile + {t("globalTaskSearch.noTasksAvailable")} ); } @@ -201,7 +203,7 @@ const GlobalTaskSearch: React.FC = ({ - Inizia a digitare per cercare tra i tuoi task + {t("globalTaskSearch.startTyping")} ); @@ -214,37 +216,27 @@ const GlobalTaskSearch: React.FC = ({ presentationStyle="pageSheet" onRequestClose={onClose} > - - {/* Header */} - - - Ricerca Task - - - - - - - + + {/* Header */} + + + + {t("globalTaskSearch.title")} + + + + + + + + + + - - - - {hasLoaded && ( - - - {searchQuery.trim() - ? `${filteredTasks.length} di ${allTasks.length} task` - : `${allTasks.length} task totali` - } - - - )} - {/* Content */} diff --git a/src/components/UI/SearchTasksButton.tsx b/src/components/UI/SearchTasksButton.tsx index e69b84c..59cfe57 100644 --- a/src/components/UI/SearchTasksButton.tsx +++ b/src/components/UI/SearchTasksButton.tsx @@ -1,6 +1,7 @@ import React from "react"; import { TouchableOpacity, Text, StyleSheet, View } from "react-native"; import { MaterialIcons } from "@expo/vector-icons"; +import { useTranslation } from "react-i18next"; export interface SearchTasksButtonProps { onPress: () => void; @@ -11,6 +12,8 @@ const SearchTasksButton: React.FC = ({ onPress, style, }) => { + const { t } = useTranslation(); + return ( = ({ color="#666666" style={styles.icon} /> - Cerca tra tutti i task + {t("globalTaskSearch.searchAllTasks")} diff --git a/src/locales/en.json b/src/locales/en.json index e2bfd99..7456bb7 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -507,6 +507,50 @@ "deleted": "Category deleted successfully", "error": "Error", "emptyTitle": "Category name cannot be empty" + }, + "addModal": { + "title": "Add Category", + "name": "Category Name", + "description": "Description", + "namePlaceholder": "Enter category name", + "descriptionPlaceholder": "Enter description", + "save": "Save", + "saving": "Saving...", + "cancel": "Cancel", + "limitReached": "Category limit reached", + "limitReachedMessage": "You have reached the maximum number of categories for your plan. Upgrade to add more.", + "addError": "Could not add category" + }, + "permissions": { + "denied": "Permission denied", + "ownerOnlyDelete": "Only the owner can delete this category.", + "readOnly": "You only have read permissions for this category. You cannot modify it.", + "readOnlyAdd": "You only have read permissions for this category. You cannot add tasks." + }, + "delete": { + "error": "Error", + "failed": "Could not delete category. Please try again later." + }, + "update": { + "error": "Error", + "failed": "Could not update category. Please try again later.", + "emptyTitle": "Category title cannot be empty" + }, + "shared": { + "title": "Shared category", + "info": "This category is shared by another user. Your permission: {{permission}}", + "permissions": { + "READ_ONLY": "Read only", + "READ_WRITE": "Read/Write" + }, + "idError": "Error", + "noId": "Category ID not available", + "success": "Success" + }, + "task": { + "error": "Error", + "addFailed": "Could not add task. Please try again later.", + "status": "Pending" } }, "tasks": { @@ -1027,5 +1071,19 @@ "offlineWarning": "Offline. Prices unavailable.", "offlineError": "Service offline or plan unavailable.", "getPlan": "Get {{plan}}" + }, + "globalTaskSearch": { + "title": "Task Search", + "searchAllTasks": "Search All Tasks", + "searchPlaceholder": "Search by title, description, category...", + "loadingTasks": "Loading tasks...", + "loadingInProgress": "Loading in progress...", + "noTasksFound": "No tasks found for \"{{query}}\"", + "noTasksAvailable": "No tasks available", + "startTyping": "Start typing to search your tasks", + "tasksCount": "{{filtered}} of {{total}} tasks", + "totalTasks": "{{total}} total tasks", + "error": "Error", + "loadFailed": "Unable to load tasks for search" } } diff --git a/src/locales/it.json b/src/locales/it.json index de25566..547cc3d 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -508,6 +508,50 @@ "deleted": "Categoria eliminata con successo", "error": "Errore", "emptyTitle": "Il nome della categoria non può essere vuoto" + }, + "addModal": { + "title": "Aggiungi Categoria", + "name": "Nome Categoria", + "description": "Descrizione", + "namePlaceholder": "Inserisci il nome della categoria", + "descriptionPlaceholder": "Inserisci la descrizione", + "save": "Salva", + "saving": "Salvataggio...", + "cancel": "Annulla", + "limitReached": "Limite categorie raggiunto", + "limitReachedMessage": "Hai raggiunto il numero massimo di categorie per il tuo piano. Fai l'upgrade per aggiungerne altre.", + "addError": "Non è stato possibile aggiungere la categoria" + }, + "permissions": { + "denied": "Permesso negato", + "ownerOnlyDelete": "Solo il proprietario può eliminare questa categoria.", + "readOnly": "Hai solo permessi di lettura per questa categoria. Non puoi modificarla.", + "readOnlyAdd": "Hai solo permessi di lettura per questa categoria. Non puoi aggiungere task." + }, + "delete": { + "error": "Errore", + "failed": "Impossibile eliminare la categoria. Riprova più tardi." + }, + "update": { + "error": "Errore", + "failed": "Impossibile aggiornare la categoria. Riprova più tardi.", + "emptyTitle": "Il titolo della categoria non può essere vuoto" + }, + "shared": { + "title": "Categoria condivisa", + "info": "Questa categoria è condivisa da un altro utente. Il tuo permesso: {{permission}}", + "permissions": { + "READ_ONLY": "Sola lettura", + "READ_WRITE": "Lettura/Scrittura" + }, + "idError": "Errore", + "noId": "ID categoria non disponibile", + "success": "Successo" + }, + "task": { + "error": "Errore", + "addFailed": "Impossibile aggiungere il task. Riprova più tardi.", + "status": "In sospeso" } }, "tasks": { @@ -1027,5 +1071,19 @@ "offlineWarning": "Offline. Prezzi non disponibili.", "offlineError": "Servizio offline o piano non disponibile.", "getPlan": "Ottieni {{plan}}" + }, + "globalTaskSearch": { + "title": "Ricerca Task", + "searchAllTasks": "Cerca tra tutti i task", + "searchPlaceholder": "Cerca per titolo, descrizione, categoria...", + "loadingTasks": "Caricamento task...", + "loadingInProgress": "Caricamento in corso...", + "noTasksFound": "Nessun task trovato per \"{{query}}\"", + "noTasksAvailable": "Nessun task disponibile", + "startTyping": "Inizia a digitare per cercare tra i tuoi task", + "tasksCount": "{{filtered}} di {{total}} task", + "totalTasks": "{{total}} task totali", + "error": "Errore", + "loadFailed": "Impossibile caricare i task per la ricerca" } } From 5b7f2409ddf2a6256ad5080dba9c14e130078f86 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Fri, 15 May 2026 16:14:00 +0200 Subject: [PATCH 02/13] added translation for all components in Categories.tsx execpt for the add task modal --- src/components/Category/AddCategoryButton.tsx | 22 ++--- src/components/Category/Category.tsx | 30 +++---- src/components/Category/CategoryMenu.tsx | 14 ++-- .../Category/DeleteCategoryModal.tsx | 22 ++--- src/components/Category/EditCategoryModal.tsx | 30 +++---- .../Category/ManageCategoryShares.tsx | 79 +++++++++-------- .../Category/ShareCategoryDialog.tsx | 36 ++++---- src/locales/en.json | 84 +++++++++++++++++++ src/locales/it.json | 84 +++++++++++++++++++ 9 files changed, 295 insertions(+), 106 deletions(-) diff --git a/src/components/Category/AddCategoryButton.tsx b/src/components/Category/AddCategoryButton.tsx index 3e482ee..526d83b 100644 --- a/src/components/Category/AddCategoryButton.tsx +++ b/src/components/Category/AddCategoryButton.tsx @@ -17,6 +17,7 @@ import Animated, { } from "react-native-reanimated"; import { addCategory, CategoryLimitError } from "../../services/taskService"; import { emitCategoryAdded } from "../../utils/eventEmitter"; +import { useTranslation } from "react-i18next"; // Definiamo un'interfaccia chiara per i dati della categoria export interface CategoryData { @@ -34,6 +35,7 @@ export interface AddCategoryButtonProps { const AddCategoryButton: React.FC = ({ onCategoryAdded, }) => { + const { t } = useTranslation(); const [formVisible, setFormVisible] = useState(false); const animationValue = useSharedValue(0); const [name, setName] = useState(""); @@ -56,7 +58,7 @@ const AddCategoryButton: React.FC = ({ const handleSave = async () => { if (name.trim() === "") { - Alert.alert("Errore", "Il nome della categoria non può essere vuoto"); + Alert.alert(t("categories.messages.error"), t("categories.messages.emptyTitle")); return; } @@ -108,8 +110,8 @@ const AddCategoryButton: React.FC = ({ } catch (error) { if (error instanceof CategoryLimitError) { Alert.alert( - "Limite categorie raggiunto", - "Hai raggiunto il numero massimo di categorie per il tuo piano. Fai l'upgrade per aggiungerne altre." + t("categories.addModal.limitReached"), + t("categories.addModal.limitReachedMessage") ); return; } @@ -127,7 +129,7 @@ const AddCategoryButton: React.FC = ({ handleCancel(); } catch (error) { console.error("Errore nell'aggiunta della categoria:", error); - Alert.alert("Errore", "Non è stato possibile aggiungere la categoria"); + Alert.alert(t("categories.messages.error"), t("categories.addModal.addError")); } finally { setIsSubmitting(false); } @@ -151,17 +153,17 @@ const AddCategoryButton: React.FC = ({ - Nome Categoria + {t("categories.addModal.name")} - Descrizione + {t("categories.addModal.description")} = ({ disabled={isSubmitting} > - {isSubmitting ? "Salvataggio..." : "Salva"} + {isSubmitting ? t("categories.addModal.saving") : t("categories.addModal.save")} = ({ onPress={handleCancel} disabled={isSubmitting} > - Annulla + {t("categories.addModal.cancel")} diff --git a/src/components/Category/Category.tsx b/src/components/Category/Category.tsx index 668ccd7..48615e0 100644 --- a/src/components/Category/Category.tsx +++ b/src/components/Category/Category.tsx @@ -216,7 +216,7 @@ const Category: React.FC = ({ const handleSaveEdit = async () => { if (!editName.trim()) { - Alert.alert("Errore", "Il titolo della categoria non può essere vuoto"); + Alert.alert(t("categories.update.error"), t("categories.update.emptyTitle")); return; } @@ -226,21 +226,21 @@ const Category: React.FC = ({ name: editName.trim(), description: editDescription.trim() }); - + // Emetti un evento per notificare la modifica della categoria emitCategoryUpdated({ name: editName.trim(), description: editDescription.trim(), oldName: title }); - + setShowEditModal(false); if (onEdit) { onEdit(); } } catch (error) { console.error("Errore durante l'aggiornamento della categoria:", error); - Alert.alert("Errore", "Impossibile aggiornare la categoria. Riprova più tardi."); + Alert.alert(t("categories.update.error"), t("categories.update.failed")); } finally { setIsEditing(false); } @@ -259,8 +259,10 @@ const Category: React.FC = ({ setShowShareDialog(true); } else { Alert.alert( - "Categoria condivisa", - `Questa categoria è condivisa da un altro utente. Il tuo permesso: ${permissionLevel === "READ_ONLY" ? "Sola lettura" : "Lettura/Scrittura"}` + t("categories.shared.title"), + t("categories.shared.info", { + permission: t(`categories.shared.permissions.${permissionLevel}`) + }) ); } }; @@ -268,22 +270,22 @@ const Category: React.FC = ({ const handleManageShares = () => { closeMenu(); if (!categoryId) { - Alert.alert("Errore", "ID categoria non disponibile"); + Alert.alert(t("categories.shared.idError"), t("categories.shared.noId")); return; } setShowManageShares(true); }; const handleShareSuccess = (message: string) => { - Alert.alert("Successo", message); + Alert.alert(t("categories.shared.success"), message); }; const handleAddTask = () => { // Check permissions before allowing task creation if (!isOwned && permissionLevel === "READ_ONLY") { Alert.alert( - "Permesso negato", - "Hai solo permessi di lettura per questa categoria. Non puoi aggiungere task." + t("categories.permissions.denied"), + t("categories.permissions.readOnlyAdd") ); return; } @@ -298,19 +300,19 @@ const Category: React.FC = ({ start_time: new Date().toISOString(), priority: priority, category_name: title, - status: "In sospeso", + status: t("categories.task.status"), user: "" // Campo richiesto dal server }; console.log("Nuovo task:", d); addTask(d).then((addedTask) => { // Emetti un evento per notificare l'aggiunta di un nuovo task emitTaskAdded(addedTask || d); - + fetchTaskCount(); - setShowAddTask(false); + setShowAddTask(false); }).catch(error => { console.error("Errore durante l'aggiunta del task:", error); - Alert.alert("Errore", "Impossibile aggiungere il task. Riprova più tardi."); + Alert.alert(t("categories.task.error"), t("categories.task.addFailed")); }); }; diff --git a/src/components/Category/CategoryMenu.tsx b/src/components/Category/CategoryMenu.tsx index 788edd8..12f3d47 100644 --- a/src/components/Category/CategoryMenu.tsx +++ b/src/components/Category/CategoryMenu.tsx @@ -1,6 +1,7 @@ import React from "react"; import { View, Text, StyleSheet, TouchableOpacity, Modal } from "react-native"; import { MaterialIcons } from '@expo/vector-icons'; +import { useTranslation } from "react-i18next"; export interface CategoryMenuProps { visible: boolean; @@ -25,6 +26,7 @@ const CategoryMenu: React.FC = ({ isOwned = true, permissionLevel = "READ_WRITE" }) => { + const { t } = useTranslation(); // Determine if user can edit/delete based on permissions const canEdit = isOwned || permissionLevel === "READ_WRITE"; const canDelete = isOwned; @@ -50,7 +52,7 @@ const CategoryMenu: React.FC = ({ onPress={onEdit} > - Modifica + {t("categories.menu.edit")} )} @@ -61,7 +63,7 @@ const CategoryMenu: React.FC = ({ > - {isOwned ? "Condividi" : "Info Condivisione"} + {isOwned ? t("categories.menu.share") : t("categories.menu.shareInfo")} @@ -72,7 +74,7 @@ const CategoryMenu: React.FC = ({ onPress={onManageShares} > - Gestisci Accesso + {t("categories.menu.manageAccess")} )} @@ -85,7 +87,7 @@ const CategoryMenu: React.FC = ({ > - {isDeleting ? "Eliminazione..." : "Elimina"} + {isDeleting ? t("categories.menu.deleting") : t("categories.menu.delete")} )} @@ -97,7 +99,7 @@ const CategoryMenu: React.FC = ({ onPress={onClose} > - Esci + {t("categories.menu.leave")} )} @@ -105,7 +107,7 @@ const CategoryMenu: React.FC = ({ {!isOwned && permissionLevel === "READ_ONLY" && ( - Solo lettura + {t("categories.menu.readOnly")} )} diff --git a/src/components/Category/DeleteCategoryModal.tsx b/src/components/Category/DeleteCategoryModal.tsx index 34b49f9..f6b92da 100644 --- a/src/components/Category/DeleteCategoryModal.tsx +++ b/src/components/Category/DeleteCategoryModal.tsx @@ -8,6 +8,7 @@ import { ActivityIndicator, } from "react-native"; import { Ionicons } from "@expo/vector-icons"; +import { useTranslation } from "react-i18next"; export interface DeleteCategoryModalProps { visible: boolean; @@ -26,6 +27,7 @@ const DeleteCategoryModal: React.FC = ({ onCancel, onConfirm, }) => { + const { t } = useTranslation(); return ( = ({ - Elimina categoria + {t("categories.deleteModal.title")} - Sei sicuro di voler eliminare{" "} + {t("categories.deleteModal.confirmMessage")}{" "} "{categoryName}"? - Tutti i task al suo interno verranno eliminati. + {t("categories.deleteModal.warningMessage")} {/* Sezione Google Calendar — visibile solo se connesso */} @@ -51,10 +53,10 @@ const DeleteCategoryModal: React.FC = ({ - Google Calendar + {t("categories.deleteModal.calendar")} - Vuoi eliminare i task di questa categoria anche da Google Calendar? + {t("categories.deleteModal.calendarQuestion")} @@ -68,7 +70,7 @@ const DeleteCategoryModal: React.FC = ({ ) : ( - Sì, elimina ovunque + {t("categories.deleteModal.yesDeleteEverywhere")} )} @@ -80,7 +82,7 @@ const DeleteCategoryModal: React.FC = ({ activeOpacity={0.7} > - No, solo dall'app + {t("categories.deleteModal.noDeleteOnlyApp")} @@ -97,7 +99,7 @@ const DeleteCategoryModal: React.FC = ({ activeOpacity={0.7} > - Annulla + {t("categories.deleteModal.cancel")} @@ -111,7 +113,7 @@ const DeleteCategoryModal: React.FC = ({ ) : ( - Elimina + {t("categories.deleteModal.delete")} )} @@ -126,7 +128,7 @@ const DeleteCategoryModal: React.FC = ({ disabled={isDeleting} activeOpacity={0.7} > - Annulla + {t("categories.deleteModal.cancel")} )} diff --git a/src/components/Category/EditCategoryModal.tsx b/src/components/Category/EditCategoryModal.tsx index ab8aeef..dc67439 100644 --- a/src/components/Category/EditCategoryModal.tsx +++ b/src/components/Category/EditCategoryModal.tsx @@ -1,5 +1,6 @@ import React from "react"; import { View, Text, StyleSheet, TouchableOpacity, Modal, TextInput } from "react-native"; +import { useTranslation } from "react-i18next"; export interface EditCategoryModalProps { visible: boolean; @@ -22,6 +23,7 @@ const EditCategoryModal: React.FC = ({ onDescriptionChange, isEditing }) => { + const { t } = useTranslation(); return ( = ({ > - Modifica Categoria - + {t("categories.editModal.title")} + - Titolo + {t("categories.editModal.titleLabel")} - + - Descrizione + {t("categories.editModal.descriptionLabel")} - - Annulla + {t("categories.editModal.cancel")} - - - {isEditing ? "Salvataggio..." : "Salva"} + {isEditing ? t("categories.editModal.saving") : t("categories.editModal.save")} diff --git a/src/components/Category/ManageCategoryShares.tsx b/src/components/Category/ManageCategoryShares.tsx index 9d1007b..7acb21e 100644 --- a/src/components/Category/ManageCategoryShares.tsx +++ b/src/components/Category/ManageCategoryShares.tsx @@ -13,6 +13,7 @@ import { import categoryShareService, { UserShareInfo, } from "../../services/categoryShareService"; +import { useTranslation } from "react-i18next"; export interface ManageCategorySharesProps { visible: boolean; @@ -31,6 +32,7 @@ const ManageCategoryShares: React.FC = ({ onClose, onAddPerson, }) => { + const { t } = useTranslation(); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); @@ -48,7 +50,7 @@ const ManageCategoryShares: React.FC = ({ setUsers(usersData); } catch (error: any) { console.error("Error loading users:", error); - Alert.alert("Errore", "Impossibile caricare gli utenti con accesso"); + Alert.alert(t("categories.messages.error"), t("categories.manageAccess.errors.loadUsers")); } finally { setLoading(false); setRefreshing(false); @@ -62,19 +64,22 @@ const ManageCategoryShares: React.FC = ({ const handleChangePermission = (user: UserShareInfo) => { if (!isOwner) { - Alert.alert("Errore", "Solo il proprietario può modificare i permessi"); + Alert.alert(t("categories.messages.error"), t("categories.manageAccess.errors.onlyOwnerCanModify")); return; } const newPermission = user.permission_level === "READ_ONLY" ? "READ_WRITE" : "READ_ONLY"; Alert.alert( - "Cambia Permesso", - `Vuoi cambiare il permesso di ${user.name} a ${newPermission}?`, + t("categories.manageAccess.changePermissionTitle"), + t("categories.manageAccess.changePermissionMessage", { + name: user.name, + permission: newPermission + }), [ - { text: "Annulla", style: "cancel" }, + { text: t("categories.manageAccess.cancel"), style: "cancel" }, { - text: "Conferma", + text: t("categories.manageAccess.confirm"), onPress: async () => { try { // Find the share_id from the shares list @@ -82,7 +87,7 @@ const ManageCategoryShares: React.FC = ({ const share = shares.find((s) => s.shared_with_user_id === user.user_id); if (!share) { - Alert.alert("Errore", "Condivisione non trovata"); + Alert.alert(t("categories.messages.error"), t("categories.manageAccess.errors.shareNotFound")); return; } @@ -92,11 +97,11 @@ const ManageCategoryShares: React.FC = ({ newPermission ); - Alert.alert("Successo", `Permesso aggiornato a ${newPermission}`); + Alert.alert(t("categories.shared.success"), t("categories.manageAccess.success.permissionUpdated", { permission: newPermission })); loadUsers(); } catch (error: any) { console.error("Error updating permission:", error); - Alert.alert("Errore", error.message || "Impossibile aggiornare il permesso"); + Alert.alert(t("categories.messages.error"), error.message || t("categories.manageAccess.errors.updateFailed")); } }, }, @@ -106,26 +111,26 @@ const ManageCategoryShares: React.FC = ({ const handleRemoveUser = (user: UserShareInfo) => { if (!isOwner) { - Alert.alert("Errore", "Solo il proprietario può rimuovere utenti"); + Alert.alert(t("categories.messages.error"), t("categories.manageAccess.errors.onlyOwnerCanRemove")); return; } Alert.alert( - "Rimuovi Accesso", - `Vuoi rimuovere l'accesso di ${user.name}?`, + t("categories.manageAccess.removeAccessTitle"), + t("categories.manageAccess.removeAccessMessage", { name: user.name }), [ - { text: "Annulla", style: "cancel" }, + { text: t("categories.manageAccess.cancel"), style: "cancel" }, { - text: "Rimuovi", + text: t("categories.manageAccess.remove"), style: "destructive", onPress: async () => { try { await categoryShareService.removeShare(categoryId, user.user_id); - Alert.alert("Successo", `Accesso rimosso per ${user.name}`); + Alert.alert(t("categories.shared.success"), t("categories.manageAccess.success.accessRemoved", { name: user.name })); loadUsers(); } catch (error: any) { console.error("Error removing user:", error); - Alert.alert("Errore", error.message || "Impossibile rimuovere l'utente"); + Alert.alert(t("categories.messages.error"), error.message || t("categories.manageAccess.errors.removeFailed")); } }, }, @@ -144,15 +149,15 @@ const ManageCategoryShares: React.FC = ({ - {item.name} {isCurrentUserOwner && "(Tu)"} + {item.name} {isCurrentUserOwner && t("categories.manageAccess.you")} {item.email} {isCurrentUserOwner - ? "Proprietario" + ? t("categories.manageAccess.owner") : item.permission_level === "READ_ONLY" - ? "Sola lettura" - : "Lettura e scrittura"} + ? t("categories.manageAccess.readOnly") + : t("categories.manageAccess.readWrite")} @@ -161,18 +166,22 @@ const ManageCategoryShares: React.FC = ({ { - Alert.alert("Azioni", `Cosa vuoi fare con ${item.name}?`, [ - { text: "Annulla", style: "cancel" }, - { - text: "Cambia Permesso", - onPress: () => handleChangePermission(item), - }, - { - text: "Rimuovi", - style: "destructive", - onPress: () => handleRemoveUser(item), - }, - ]); + Alert.alert( + t("categories.manageAccess.actions"), + t("categories.manageAccess.whatToDo", { name: item.name }), + [ + { text: t("categories.manageAccess.cancel"), style: "cancel" }, + { + text: t("categories.manageAccess.changePermission"), + onPress: () => handleChangePermission(item), + }, + { + text: t("categories.manageAccess.remove"), + style: "destructive", + onPress: () => handleRemoveUser(item), + }, + ] + ); }} > @@ -192,7 +201,7 @@ const ManageCategoryShares: React.FC = ({ - Utenti con accesso + {t("categories.manageAccess.title")} @@ -215,14 +224,14 @@ const ManageCategoryShares: React.FC = ({ onRefresh={handleRefresh} ListEmptyComponent={ - Nessun utente trovato + {t("categories.manageAccess.noUsers")} } /> {isOwner && ( - + Aggiungi persona + {t("categories.manageAccess.addPerson")} )} diff --git a/src/components/Category/ShareCategoryDialog.tsx b/src/components/Category/ShareCategoryDialog.tsx index 2f33281..9b9c43d 100644 --- a/src/components/Category/ShareCategoryDialog.tsx +++ b/src/components/Category/ShareCategoryDialog.tsx @@ -14,6 +14,7 @@ import { import categoryShareService from "../../services/categoryShareService"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { STORAGE_KEYS } from "../../constants/authConstants"; +import { useTranslation } from "react-i18next"; export interface ShareCategoryDialogProps { visible: boolean; @@ -30,6 +31,7 @@ const ShareCategoryDialog: React.FC = ({ onClose, onSuccess, }) => { + const { t } = useTranslation(); const [email, setEmail] = useState(""); const [permission, setPermission] = useState<"READ_ONLY" | "READ_WRITE">("READ_ONLY"); const [loading, setLoading] = useState(false); @@ -46,7 +48,7 @@ const ShareCategoryDialog: React.FC = ({ // Validate the request const validation = categoryShareService.validateShareRequest(email, currentUserEmail); if (!validation.valid) { - setError(validation.error || "Errore di validazione"); + setError(validation.error || t("categories.share.errors.validation")); setLoading(false); return; } @@ -54,26 +56,26 @@ const ShareCategoryDialog: React.FC = ({ // Share the category const result = await categoryShareService.shareCategory(categoryId, email, permission); - onSuccess(`Categoria condivisa con ${result.shared_with}`); + onSuccess(t("categories.share.success", { user: result.shared_with })); handleClose(); } catch (err: any) { // Handle specific error messages - let errorMessage = "Errore sconosciuto. Riprova più tardi."; + let errorMessage = t("categories.share.errors.unknown"); if (err.status === 400) { if (err.message.includes("already shared")) { - errorMessage = "Categoria già condivisa con questo utente"; + errorMessage = t("categories.share.errors.alreadyShared"); } else if (err.message.includes("not found")) { - errorMessage = "Utente non trovato. Verifica l'email."; + errorMessage = t("categories.share.errors.userNotFound"); } else if (err.message.includes("yourself")) { - errorMessage = "Non puoi condividere con te stesso!"; + errorMessage = t("categories.share.errors.shareWithYourself"); } } else if (err.status === 403) { - errorMessage = "Non hai i permessi per condividere questa categoria"; + errorMessage = t("categories.share.errors.noPermission"); } else if (err.status === 404) { - errorMessage = "Categoria non trovata"; + errorMessage = t("categories.share.errors.categoryNotFound"); } else if (err.status === 401) { - errorMessage = "Sessione scaduta. Effettua nuovamente il login."; + errorMessage = t("categories.share.errors.sessionExpired"); } else if (err.message) { errorMessage = err.message; } @@ -103,15 +105,15 @@ const ShareCategoryDialog: React.FC = ({ style={styles.overlay} > - Condividi "{categoryName}" + {t("categories.share.title")} "{categoryName}" - Email utente: + {t("categories.share.userEmail")} = ({ - Permessi: + {t("categories.share.permissions")}: = ({ {permission === "READ_ONLY" && } - Sola lettura (READ_ONLY) + {t("categories.share.readOnly")} = ({ {permission === "READ_WRITE" && } - Lettura e scrittura (READ_WRITE) + {t("categories.share.readWrite")} @@ -159,7 +161,7 @@ const ShareCategoryDialog: React.FC = ({ onPress={handleClose} disabled={loading} > - Annulla + {t("categories.share.cancel")} = ({ {loading ? ( ) : ( - Condividi + {t("categories.share.shareButton")} )} diff --git a/src/locales/en.json b/src/locales/en.json index 7456bb7..0b9fc36 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -521,6 +521,27 @@ "limitReachedMessage": "You have reached the maximum number of categories for your plan. Upgrade to add more.", "addError": "Could not add category" }, + "editModal": { + "title": "Edit Category", + "titleLabel": "Title", + "titlePlaceholder": "Enter category title", + "descriptionLabel": "Description", + "descriptionPlaceholder": "Enter a description (optional)", + "save": "Save", + "saving": "Saving...", + "cancel": "Cancel" + }, + "deleteModal": { + "title": "Delete category", + "confirmMessage": "Are you sure you want to delete", + "warningMessage": "All tasks inside will be deleted.", + "calendar": "Google Calendar", + "calendarQuestion": "Do you want to delete this category's tasks from Google Calendar as well?", + "yesDeleteEverywhere": "Yes, delete everywhere", + "noDeleteOnlyApp": "No, only from app", + "cancel": "Cancel", + "delete": "Delete" + }, "permissions": { "denied": "Permission denied", "ownerOnlyDelete": "Only the owner can delete this category.", @@ -551,6 +572,69 @@ "error": "Error", "addFailed": "Could not add task. Please try again later.", "status": "Pending" + }, + "share": { + "title": "Share", + "userEmail": "User email:", + "emailPlaceholder": "user@example.com", + "permissions": "Permissions:", + "readOnly": "Read only (READ_ONLY)", + "readWrite": "Read and write (READ_WRITE)", + "shareButton": "Share", + "cancel": "Cancel", + "errors": { + "validation": "Validation error", + "alreadyShared": "Category already shared with this user", + "userNotFound": "User not found. Please verify the email.", + "shareWithYourself": "You cannot share with yourself!", + "noPermission": "You don't have permission to share this category", + "categoryNotFound": "Category not found", + "sessionExpired": "Session expired. Please login again.", + "unknown": "Unknown error. Please try again later." + }, + "success": "Category shared with {{user}}" + }, + "manageAccess": { + "title": "Users with access", + "loading": "Loading...", + "noUsers": "No users found", + "addPerson": "+ Add person", + "owner": "Owner", + "readOnly": "Read only", + "readWrite": "Read and write", + "you": "(You)", + "actions": "Actions", + "whatToDo": "What do you want to do with {{name}}?", + "changePermission": "Change Permission", + "remove": "Remove", + "cancel": "Cancel", + "confirm": "Confirm", + "changePermissionTitle": "Change Permission", + "changePermissionMessage": "Do you want to change {{name}}'s permission to {{permission}}?", + "removeAccessTitle": "Remove Access", + "removeAccessMessage": "Do you want to remove {{name}}'s access?", + "errors": { + "loadUsers": "Could not load users with access", + "onlyOwnerCanModify": "Only the owner can modify permissions", + "onlyOwnerCanRemove": "Only the owner can remove users", + "shareNotFound": "Share not found", + "updateFailed": "Could not update permission", + "removeFailed": "Could not remove user" + }, + "success": { + "permissionUpdated": "Permission updated to {{permission}}", + "accessRemoved": "Access removed for {{name}}" + } + }, + "menu": { + "edit": "Edit", + "share": "Share", + "shareInfo": "Share Info", + "manageAccess": "Manage Access", + "delete": "Delete", + "deleting": "Deleting...", + "leave": "Leave", + "readOnly": "Read only" } }, "tasks": { diff --git a/src/locales/it.json b/src/locales/it.json index 547cc3d..80abbba 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -522,6 +522,27 @@ "limitReachedMessage": "Hai raggiunto il numero massimo di categorie per il tuo piano. Fai l'upgrade per aggiungerne altre.", "addError": "Non è stato possibile aggiungere la categoria" }, + "editModal": { + "title": "Modifica Categoria", + "titleLabel": "Titolo", + "titlePlaceholder": "Inserisci il titolo della categoria", + "descriptionLabel": "Descrizione", + "descriptionPlaceholder": "Inserisci una descrizione (opzionale)", + "save": "Salva", + "saving": "Salvataggio...", + "cancel": "Annulla" + }, + "deleteModal": { + "title": "Elimina categoria", + "confirmMessage": "Sei sicuro di voler eliminare", + "warningMessage": "Tutti i task al suo interno verranno eliminati.", + "calendar": "Google Calendar", + "calendarQuestion": "Vuoi eliminare i task di questa categoria anche da Google Calendar?", + "yesDeleteEverywhere": "Sì, elimina ovunque", + "noDeleteOnlyApp": "No, solo dall'app", + "cancel": "Annulla", + "delete": "Elimina" + }, "permissions": { "denied": "Permesso negato", "ownerOnlyDelete": "Solo il proprietario può eliminare questa categoria.", @@ -552,6 +573,69 @@ "error": "Errore", "addFailed": "Impossibile aggiungere il task. Riprova più tardi.", "status": "In sospeso" + }, + "share": { + "title": "Condividi", + "userEmail": "Email utente:", + "emailPlaceholder": "user@example.com", + "permissions": "Permessi:", + "readOnly": "Sola lettura (READ_ONLY)", + "readWrite": "Lettura e scrittura (READ_WRITE)", + "shareButton": "Condividi", + "cancel": "Annulla", + "errors": { + "validation": "Errore di validazione", + "alreadyShared": "Categoria già condivisa con questo utente", + "userNotFound": "Utente non trovato. Verifica l'email.", + "shareWithYourself": "Non puoi condividere con te stesso!", + "noPermission": "Non hai i permessi per condividere questa categoria", + "categoryNotFound": "Categoria non trovata", + "sessionExpired": "Sessione scaduta. Effettua nuovamente il login.", + "unknown": "Errore sconosciuto. Riprova più tardi." + }, + "success": "Categoria condivisa con {{user}}" + }, + "manageAccess": { + "title": "Utenti con accesso", + "loading": "Caricamento...", + "noUsers": "Nessun utente trovato", + "addPerson": "+ Aggiungi persona", + "owner": "Proprietario", + "readOnly": "Sola lettura", + "readWrite": "Lettura e scrittura", + "you": "(Tu)", + "actions": "Azioni", + "whatToDo": "Cosa vuoi fare con {{name}}?", + "changePermission": "Cambia Permesso", + "remove": "Rimuovi", + "cancel": "Annulla", + "confirm": "Conferma", + "changePermissionTitle": "Cambia Permesso", + "changePermissionMessage": "Vuoi cambiare il permesso di {{name}} a {{permission}}?", + "removeAccessTitle": "Rimuovi Accesso", + "removeAccessMessage": "Vuoi rimuovere l'accesso di {{name}}?", + "errors": { + "loadUsers": "Impossibile caricare gli utenti con accesso", + "onlyOwnerCanModify": "Solo il proprietario può modificare i permessi", + "onlyOwnerCanRemove": "Solo il proprietario può rimuovere utenti", + "shareNotFound": "Condivisione non trovata", + "updateFailed": "Impossibile aggiornare il permesso", + "removeFailed": "Impossibile rimuovere l'utente" + }, + "success": { + "permissionUpdated": "Permesso aggiornato a {{permission}}", + "accessRemoved": "Accesso rimosso per {{name}}" + } + }, + "menu": { + "edit": "Modifica", + "share": "Condividi", + "shareInfo": "Info Condivisione", + "manageAccess": "Gestisci Accesso", + "delete": "Elimina", + "deleting": "Eliminazione...", + "leave": "Esci", + "readOnly": "Solo lettura" } }, "tasks": { From 4d2bdb0071f628d3ed525d25ac637fa017ce9930 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Fri, 15 May 2026 16:21:57 +0200 Subject: [PATCH 03/13] Add: add translation for the text 'cosa da fare' in Categoy.tsx --- src/components/Category/CategoryHeader.tsx | 9 +++++++-- src/locales/en.json | 1 + src/locales/it.json | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/Category/CategoryHeader.tsx b/src/components/Category/CategoryHeader.tsx index a8c43fd..8a1ae4f 100644 --- a/src/components/Category/CategoryHeader.tsx +++ b/src/components/Category/CategoryHeader.tsx @@ -1,6 +1,7 @@ import React from "react"; import { View, Text, StyleSheet, Image } from "react-native"; import { MaterialIcons } from '@expo/vector-icons'; +import { useTranslation } from "react-i18next"; import CategoryBadge from './CategoryBadge'; export interface CategoryHeaderProps { @@ -20,6 +21,8 @@ const CategoryHeader: React.FC = ({ screenWidth, badgeType }) => { + const { t } = useTranslation(); + return ( = ({ fontSize: screenWidth < 350 ? 12 : 14, marginLeft: screenWidth < 350 ? 4 : 6, }]}> - {isLoading ? "Caricamento..." : `${taskCount} cose da fare`} + {isLoading + ? t("common.messages.loading") + : t("categories.taskCount", { count: taskCount })} @@ -123,4 +128,4 @@ const styles = StyleSheet.create({ }, }); -export default CategoryHeader; \ No newline at end of file +export default CategoryHeader; diff --git a/src/locales/en.json b/src/locales/en.json index 0b9fc36..fa8cfe9 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -573,6 +573,7 @@ "addFailed": "Could not add task. Please try again later.", "status": "Pending" }, + "taskCount": "{{count}} things to do", "share": { "title": "Share", "userEmail": "User email:", diff --git a/src/locales/it.json b/src/locales/it.json index 80abbba..b3aaea0 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -574,6 +574,7 @@ "addFailed": "Impossibile aggiungere il task. Riprova più tardi.", "status": "In sospeso" }, + "taskCount": "{{count}} cose da fare", "share": { "title": "Condividi", "userEmail": "Email utente:", From 94ad169c7699d017790a3acfe884e701e3b596bb Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Fri, 15 May 2026 17:08:32 +0200 Subject: [PATCH 04/13] add: complete translation for the note screen and updated the style of the modal for change the color --- src/components/Notes/StickyNote.tsx | 67 +++++++++++++++++------------ src/locales/en.json | 4 ++ src/locales/it.json | 4 ++ 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/components/Notes/StickyNote.tsx b/src/components/Notes/StickyNote.tsx index 5ce7d48..024eacc 100644 --- a/src/components/Notes/StickyNote.tsx +++ b/src/components/Notes/StickyNote.tsx @@ -17,6 +17,7 @@ import Animated, { } from 'react-native-reanimated'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import { X, Palette } from 'lucide-react-native'; +import { useTranslation } from 'react-i18next'; import { Note } from '../../services/noteService'; import { useNotesActions } from '../../context/NotesContext'; @@ -48,6 +49,7 @@ export interface StickyNoteRef { } export const StickyNote: React.FC = React.memo(({ note, canvasScale }) => { + const { t } = useTranslation(); const { updateNote, deleteNote, updateNotePosition } = useNotesActions(); const [isEditing, setIsEditing] = useState(false); const [editText, setEditText] = useState(note.text); @@ -273,7 +275,7 @@ export const StickyNote: React.FC = React.memo(({ note, canvasS onPress={handleCloseColorPicker} > - Scegli un colore + {t('notes.colorPicker.title')} {COLORS.map((color) => ( = React.memo(({ note, canvasS style={styles.closeColorPicker} onPress={handleCloseColorPicker} > - Chiudi + {t('notes.colorPicker.close')} @@ -373,57 +375,66 @@ const styles = StyleSheet.create({ }, colorPickerOverlay: { flex: 1, - backgroundColor: 'rgba(0, 0, 0, 0.4)', + backgroundColor: 'rgba(0, 0, 0, 0.6)', justifyContent: 'center', alignItems: 'center', + paddingHorizontal: 24, }, colorPickerModal: { - backgroundColor: 'white', + backgroundColor: '#ffffff', borderRadius: 16, - padding: 20, - width: 260, + padding: 24, + width: '85%', + maxWidth: 320, alignItems: 'center', - elevation: 10, + borderWidth: 1.5, + borderColor: '#e1e5e9', + elevation: 3, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.3, - shadowRadius: 8, + shadowOpacity: 0.08, + shadowRadius: 12, }, colorPickerTitle: { - fontSize: 16, - fontWeight: '600', - color: '#333', - marginBottom: 16, + fontSize: 18, + fontWeight: '500', + color: '#000000', + marginBottom: 18, + fontFamily: 'System', }, colorGrid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', - gap: 8, + gap: 12, + marginBottom: 8, }, colorOption: { - width: 38, - height: 38, - borderRadius: 19, + width: 36, + height: 36, + borderRadius: 18, borderWidth: 2, - borderColor: '#ddd', + borderColor: '#e1e5e9', }, selectedColor: { - borderColor: '#007AFF', - borderWidth: 3, - transform: [{ scale: 1.1 }], + borderColor: '#000000', + borderWidth: 2, + transform: [{ scale: 1.05 }], }, closeColorPicker: { - marginTop: 16, - paddingVertical: 10, - paddingHorizontal: 24, + marginTop: 18, + paddingVertical: 12, + paddingHorizontal: 28, backgroundColor: '#f0f0f0', - borderRadius: 8, + borderRadius: 12, + borderWidth: 1.5, + borderColor: '#e1e5e9', alignItems: 'center', }, closeText: { - fontSize: 14, - color: '#666', + fontSize: 15, + color: '#000000', fontWeight: '500', + fontFamily: 'System', }, -}); \ No newline at end of file +}); diff --git a/src/locales/en.json b/src/locales/en.json index fa8cfe9..b0e5ffe 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -669,6 +669,10 @@ "newNote": "New Note", "create": "Create", "cancel": "Cancel", + "colorPicker": { + "title": "Choose a color", + "close": "Close" + }, "messages": { "created": "Note created successfully", "deleted": "Note deleted successfully" diff --git a/src/locales/it.json b/src/locales/it.json index b3aaea0..5272e01 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -670,6 +670,10 @@ "newNote": "Nuova Nota", "create": "Crea", "cancel": "Annulla", + "colorPicker": { + "title": "Scegli un colore", + "close": "Chiudi" + }, "messages": { "created": "Nota creata con successo", "deleted": "Nota eliminata con successo" From 16ff32b0964c31340a40aff0da03fc7b8571fe0b Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Fri, 15 May 2026 21:03:55 +0200 Subject: [PATCH 05/13] feat: add translations for AddTask, AddTaskButton, ReadOnlyModal and translation files --- src/components/Task/AddTask.tsx | 60 +++++++++---------- src/components/Task/AddTaskButton.tsx | 8 ++- src/components/Task/FormComponents.tsx | 14 +++-- src/components/Task/ReadOnlyModal.tsx | 10 ++-- src/components/Task/TaskEditModal.tsx | 53 ++++++++-------- src/locales/en.json | 51 ++++++++++++++++ src/locales/it.json | 51 ++++++++++++++++ .../screens/NotificationSettings.tsx | 16 +++-- 8 files changed, 190 insertions(+), 73 deletions(-) diff --git a/src/components/Task/AddTask.tsx b/src/components/Task/AddTask.tsx index 97cc32b..a38d33f 100644 --- a/src/components/Task/AddTask.tsx +++ b/src/components/Task/AddTask.tsx @@ -120,11 +120,11 @@ const AddTask: React.FC = ({ const [customDuration, setCustomDuration] = useState(""); const DURATION_PRESETS = [ - { label: "15 min", value: 15 }, - { label: "30 min", value: 30 }, - { label: "1 ora", value: 60 }, - { label: "2 ore", value: 120 }, - { label: "4 ore", value: 240 }, + { label: t("tasks.form.durationPresets.min15"), value: 15 }, + { label: t("tasks.form.durationPresets.min30"), value: 30 }, + { label: t("tasks.form.durationPresets.hour1"), value: 60 }, + { label: t("tasks.form.durationPresets.hour2"), value: 120 }, + { label: t("tasks.form.durationPresets.hour4"), value: 240 }, ]; // Se initialDate è fornito, imposta la data iniziale all'apertura @@ -190,12 +190,12 @@ const AddTask: React.FC = ({ let hasError = false; if (!title.trim()) { - setTitleError("Il titolo è obbligatorio"); + setTitleError(t("tasks.form.errors.titleRequired")); hasError = true; } if (allowCategorySelection && !localCategory.trim()) { - setCategoryError("La categoria è obbligatoria"); + setCategoryError(t("tasks.form.errors.categoryRequired")); hasError = true; } @@ -269,7 +269,7 @@ const AddTask: React.FC = ({ } } catch (error) { console.error("Error saving task:", error); - Alert.alert("Error", "Failed to save task"); + Alert.alert(t("tasks.form.errorTitle"), t("tasks.form.errors.saveFailed")); } finally { resetForm(); handleCancel(); @@ -326,7 +326,7 @@ const AddTask: React.FC = ({ - Aggiungi Task + {t("tasks.addTask")} @@ -335,7 +335,7 @@ const AddTask: React.FC = ({ {allowCategorySelection && ( <> - Categoria * + {t("tasks.form.categoryLabel")} * = ({ onPress={() => { const options = [ ...categoriesOptions.map(cat => cat.label), - 'Annulla' + t("common.buttons.cancel") ]; const cancelButtonIndex = options.length - 1; @@ -352,8 +352,8 @@ const AddTask: React.FC = ({ { options, cancelButtonIndex, - title: 'Seleziona categoria', - message: 'Scegli una categoria per il task', + title: t("tasks.form.categoryActionTitle"), + message: t("tasks.form.categoryActionMessage"), }, (buttonIndex) => { if (buttonIndex !== cancelButtonIndex) { @@ -370,7 +370,7 @@ const AddTask: React.FC = ({ !localCategory && styles.categoryPlaceholder ]} > - {localCategory || "Seleziona categoria"} + {localCategory || t("tasks.form.categoryPlaceholder")} @@ -380,10 +380,10 @@ const AddTask: React.FC = ({ )} - Titolo * + {t("tasks.form.titleLabel")} * { setTitle(text); @@ -394,10 +394,10 @@ const AddTask: React.FC = ({ {titleError} ) : null} - Descrizione + {t("tasks.form.descriptionLabel")} = ({ /> {/* Segmented control: Scadenza semplice / Ripetitività */} - Scadenza + {t("tasks.form.deadlineLabel")} = ({ styles.deadlineTypeText, deadlineType === 'simple' && styles.deadlineTypeTextActive, ]}> - Scadenza semplice + {t("tasks.form.deadlineSimple")} @@ -458,7 +458,7 @@ const AddTask: React.FC = ({ styles.deadlineTypeText, deadlineType === 'recurring' && styles.deadlineTypeTextActive, ]}> - Ripetitività + {t("tasks.form.deadlineRecurring")} @@ -474,7 +474,7 @@ const AddTask: React.FC = ({ {selectedDateTime ? selectedDateTime.toLocaleDateString("it-IT") - : "Seleziona data"} + : t("tasks.form.datePlaceholder")} @@ -495,7 +495,7 @@ const AddTask: React.FC = ({ hour: "2-digit", minute: "2-digit", }) - : "Ora"} + : t("tasks.form.timePlaceholder")} = ({ )} - Priorità + {t("tasks.form.priorityLabel")} = ({ priority === 1 && styles.priorityButtonTextActive, ]} > - Bassa + {t("tasks.priority.low")} = ({ priority === 2 && styles.priorityButtonTextActive, ]} > - Media + {t("tasks.priority.medium")} = ({ priority === 3 && styles.priorityButtonTextActive, ]} > - Alta + {t("tasks.priority.high")} {/* Duration selector */} - Durata stimata (opzionale) + {t("tasks.form.durationLabel")} {DURATION_PRESETS.map((preset) => ( = ({ { @@ -653,7 +653,7 @@ const AddTask: React.FC = ({ - Salva + {t("common.buttons.save")} void; @@ -10,12 +11,15 @@ export interface AddTaskButtonProps { } const AddTaskButton: React.FC = ({ onPress, screenWidth, categoryTitle, isInline = false }) => { + const { t } = useTranslation(); return ( [ styles.controlsContainer, isInline ? styles.inlineButton : null, diff --git a/src/components/Task/FormComponents.tsx b/src/components/Task/FormComponents.tsx index 12d2116..e7b80b1 100644 --- a/src/components/Task/FormComponents.tsx +++ b/src/components/Task/FormComponents.tsx @@ -7,6 +7,7 @@ import { RecurrencePattern } from "../../types/recurringTask"; // Componente per selezionare la priorità export const PrioritySelector = ({ value, onChange }) => { + const { t } = useTranslation(); return ( { Bassa + ]}>{t("tasks.priority.low")} { Media + ]}>{t("tasks.priority.medium")} { Alta + ]}>{t("tasks.priority.high")} ); @@ -56,6 +57,7 @@ export const PrioritySelector = ({ value, onChange }) => { // Componente per selezionare lo stato export const StatusSelector = ({ value, onChange }) => { + const { t } = useTranslation(); return ( { In sospeso + ]}>{t("tasks.status.pending")} { In corso + ]}>{t("tasks.status.inProgress")} { Completato + ]}>{t("tasks.status.completed")} ); diff --git a/src/components/Task/ReadOnlyModal.tsx b/src/components/Task/ReadOnlyModal.tsx index 1193e77..195f30b 100644 --- a/src/components/Task/ReadOnlyModal.tsx +++ b/src/components/Task/ReadOnlyModal.tsx @@ -2,6 +2,7 @@ import React from "react"; import { View, Text, TouchableOpacity, Modal } from "react-native"; import { MaterialIcons } from "@expo/vector-icons"; import { styles } from "./TaskStyles"; +import { useTranslation } from "react-i18next"; export interface ReadOnlyModalProps { visible: boolean; @@ -10,6 +11,7 @@ export interface ReadOnlyModalProps { } const ReadOnlyModal = ({ visible, onClose, taskTitle }: ReadOnlyModalProps) => { + const { t } = useTranslation(); return ( { - Categoria in sola lettura + {t("tasks.readOnly.title")} @@ -37,7 +39,7 @@ const ReadOnlyModal = ({ visible, onClose, taskTitle }: ReadOnlyModalProps) => { lineHeight: 24, paddingHorizontal: 10 }}> - Non puoi modificare questo task perché la categoria è condivisa con te in sola lettura. + {t("tasks.readOnly.message")} { marginTop: 10, paddingHorizontal: 10 }}> - Puoi solo completarlo o visualizzarne i dettagli. + {t("tasks.readOnly.subMessage")} @@ -56,7 +58,7 @@ const ReadOnlyModal = ({ visible, onClose, taskTitle }: ReadOnlyModalProps) => { style={styles.saveButton} onPress={onClose} > - Ho capito + {t("tasks.readOnly.confirmButton")} diff --git a/src/components/Task/TaskEditModal.tsx b/src/components/Task/TaskEditModal.tsx index 0d633ec..b9db2d3 100644 --- a/src/components/Task/TaskEditModal.tsx +++ b/src/components/Task/TaskEditModal.tsx @@ -6,14 +6,7 @@ import { styles } from "./TaskStyles"; import { PrioritySelector, StatusSelector, DatePickerButton, TimePickerButton } from "./FormComponents"; import { RecurrenceConfig } from "./RecurrenceConfig"; import { RecurrenceConfig as RecurrenceConfigType } from "../../types/recurringTask"; - -const DURATION_PRESETS = [ - { label: "15 min", value: 15 }, - { label: "30 min", value: 30 }, - { label: "1 ora", value: 60 }, - { label: "2 ore", value: 120 }, - { label: "4 ore", value: 240 }, -]; +import { useTranslation } from "react-i18next"; // Componente per il modal di modifica const TaskEditModal = ({ @@ -22,6 +15,14 @@ const TaskEditModal = ({ onClose, onSave }) => { + const { t } = useTranslation(); + const DURATION_PRESETS = [ + { label: t("tasks.form.durationPresets.min15"), value: 15 }, + { label: t("tasks.form.durationPresets.min30"), value: 30 }, + { label: t("tasks.form.durationPresets.hour1"), value: 60 }, + { label: t("tasks.form.durationPresets.hour2"), value: 120 }, + { label: t("tasks.form.durationPresets.hour4"), value: 240 }, + ]; const [editedTask, setEditedTask] = useState({ title: "", description: "", @@ -120,7 +121,7 @@ const TaskEditModal = ({ const handleSave = () => { if (!editedTask.title.trim()) { - Alert.alert("Errore", "Il titolo è obbligatorio"); + Alert.alert(t("tasks.form.errorTitle"), t("tasks.form.errors.titleRequired")); return; } @@ -160,34 +161,34 @@ const TaskEditModal = ({ - Modifica Task + {t("tasks.editTask")} - Titolo * + {t("tasks.form.titleLabel")} * setEditedTask({...editedTask, title: text})} - placeholder="Titolo del task" + placeholder={t("tasks.form.titleEditPlaceholder")} /> - Descrizione + {t("tasks.form.descriptionLabel")} setEditedTask({...editedTask, description: text})} - placeholder="Descrizione del task" + placeholder={t("tasks.form.descriptionEditPlaceholder")} multiline numberOfLines={4} textAlignVertical="top" /> {/* Segmented control: Scadenza semplice / Ripetitività */} - Scadenza + {t("tasks.form.deadlineLabel")} - Scadenza semplice + {t("tasks.form.deadlineSimple")} @@ -237,7 +238,7 @@ const TaskEditModal = ({ editStyles.deadlineTypeText, deadlineType === 'recurring' && editStyles.deadlineTypeTextActive, ]}> - Ripetitività + {t("tasks.form.deadlineRecurring")} @@ -250,14 +251,14 @@ const TaskEditModal = ({ openDatePicker('end')} - placeholder="Seleziona data" + placeholder={t("tasks.form.datePlaceholder")} /> editedTask.end_time ? openTimePicker('end') : null} - placeholder="Ora" + placeholder={t("tasks.form.timePlaceholder")} /> {editedTask.end_time && ( @@ -302,19 +303,19 @@ const TaskEditModal = ({ /> )} - Priorità + {t("tasks.form.priorityLabel")} setEditedTask({...editedTask, priority})} /> - - Stato + + {t("tasks.form.statusLabel")} setEditedTask({...editedTask, status})} /> - - Durata stimata (opzionale) + + {t("tasks.form.durationLabel")} {DURATION_PRESETS.map((preset) => ( { @@ -380,7 +381,7 @@ const TaskEditModal = ({ style={styles.saveButton} onPress={handleSave} > - Salva Modifiche + {t("tasks.form.saveChanges")} diff --git a/src/locales/en.json b/src/locales/en.json index b0e5ffe..d12dc12 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -656,6 +656,57 @@ "medium": "Medium", "high": "High" }, + "status": { + "pending": "Pending", + "inProgress": "In Progress", + "completed": "Completed" + }, + "form": { + "durationPresets": { + "min15": "15 min", + "min30": "30 min", + "hour1": "1 hour", + "hour2": "2 hours", + "hour4": "4 hours" + }, + "errors": { + "titleRequired": "Title is required", + "categoryRequired": "Category is required", + "saveFailed": "Failed to save task" + }, + "errorTitle": "Error", + "categoryLabel": "Category", + "categoryActionTitle": "Select category", + "categoryActionMessage": "Choose a category for the task", + "categoryPlaceholder": "Select category", + "titleLabel": "Title", + "titlePlaceholder": "Enter title", + "titleEditPlaceholder": "Task title", + "descriptionLabel": "Description", + "descriptionPlaceholder": "Enter description", + "descriptionEditPlaceholder": "Task description", + "deadlineLabel": "Deadline", + "deadlineSimple": "Simple deadline", + "deadlineRecurring": "Recurring", + "datePlaceholder": "Select date", + "timePlaceholder": "Time", + "priorityLabel": "Priority", + "statusLabel": "Status", + "durationLabel": "Estimated duration (optional)", + "customDurationPlaceholder": "Custom (minutes)", + "saveChanges": "Save Changes" + }, + "readOnly": { + "title": "Read-only category", + "message": "You cannot edit this task because the category is shared with you in read-only mode.", + "subMessage": "You can only complete it or view its details.", + "confirmButton": "Got it" + }, + "accessibility": { + "addTaskLabel": "Add new activity", + "addTaskHint": "Add a new activity", + "addTaskHintCategory": "Add a new activity to the {{category}} category" + }, "messages": { "created": "Task created successfully", "updated": "Task updated successfully", diff --git a/src/locales/it.json b/src/locales/it.json index 5272e01..87db085 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -657,6 +657,57 @@ "medium": "Media", "high": "Alta" }, + "status": { + "pending": "In sospeso", + "inProgress": "In corso", + "completed": "Completato" + }, + "form": { + "durationPresets": { + "min15": "15 min", + "min30": "30 min", + "hour1": "1 ora", + "hour2": "2 ore", + "hour4": "4 ore" + }, + "errors": { + "titleRequired": "Il titolo è obbligatorio", + "categoryRequired": "La categoria è obbligatoria", + "saveFailed": "Impossibile salvare il task" + }, + "errorTitle": "Errore", + "categoryLabel": "Categoria", + "categoryActionTitle": "Seleziona categoria", + "categoryActionMessage": "Scegli una categoria per il task", + "categoryPlaceholder": "Seleziona categoria", + "titleLabel": "Titolo", + "titlePlaceholder": "Inserisci il titolo", + "titleEditPlaceholder": "Titolo del task", + "descriptionLabel": "Descrizione", + "descriptionPlaceholder": "Inserisci la descrizione", + "descriptionEditPlaceholder": "Descrizione del task", + "deadlineLabel": "Scadenza", + "deadlineSimple": "Scadenza semplice", + "deadlineRecurring": "Ripetitività", + "datePlaceholder": "Seleziona data", + "timePlaceholder": "Ora", + "priorityLabel": "Priorità", + "statusLabel": "Stato", + "durationLabel": "Durata stimata (opzionale)", + "customDurationPlaceholder": "Personalizzata (minuti)", + "saveChanges": "Salva Modifiche" + }, + "readOnly": { + "title": "Categoria in sola lettura", + "message": "Non puoi modificare questo task perché la categoria è condivisa con te in sola lettura.", + "subMessage": "Puoi solo completarlo o visualizzarne i dettagli.", + "confirmButton": "Ho capito" + }, + "accessibility": { + "addTaskLabel": "Aggiungi nuova attività", + "addTaskHint": "Aggiungi una nuova attività", + "addTaskHintCategory": "Aggiungi una nuova attività alla categoria {{category}}" + }, "messages": { "created": "Task creato con successo", "updated": "Task aggiornato con successo", diff --git a/src/navigation/screens/NotificationSettings.tsx b/src/navigation/screens/NotificationSettings.tsx index 01a1ea1..0571f1e 100644 --- a/src/navigation/screens/NotificationSettings.tsx +++ b/src/navigation/screens/NotificationSettings.tsx @@ -39,7 +39,9 @@ const HOURS = Array.from({ length: 24 }, (_, i) => ({ })); // Costanti per giorni della settimana (valori 0-6) -const WEEK_DAYS = [0, 1, 2, 3, 4, 5, 6]; +const WEEK_DAYS = [1, 2, 3, 4, 5, 6, 0]; +const toUiWeekday = (apiDay: number) => (apiDay + 1) % 7; +const toApiWeekday = (uiDay: number) => (uiDay + 6) % 7; export default function NotificationSettingsScreen() { const { t } = useTranslation(); @@ -204,14 +206,17 @@ export default function NotificationSettingsScreen() { }; const handleWeeklySummaryDay = async (day: number) => { - if (!weeklySummarySettings || weeklySummarySettings.day === day) return; + if (!weeklySummarySettings) return; + + const apiDay = toApiWeekday(day); + if (weeklySummarySettings.day === apiDay) return; const previous = weeklySummarySettings.day; - setWeeklySummarySettings((prev) => prev ? { ...prev, day } : prev); + setWeeklySummarySettings((prev) => prev ? { ...prev, day: apiDay } : prev); setSavingField('weekly_day'); try { - const updated = await updateWeeklySummarySettings({ day }); + const updated = await updateWeeklySummarySettings({ day: apiDay }); setWeeklySummarySettings(updated); } catch (error: any) { setWeeklySummarySettings((prev) => prev ? { ...prev, day: previous } : prev); @@ -256,6 +261,7 @@ export default function NotificationSettingsScreen() { if (!settings) return null; const timezoneIsSynced = settings.timezone === deviceTimezone; + const selectedUiWeeklyDay = weeklySummarySettings ? toUiWeekday(weeklySummarySettings.day) : null; return ( @@ -469,7 +475,7 @@ export default function NotificationSettingsScreen() { {WEEK_DAYS.map((day) => { - const isSelected = weeklySummarySettings.day === day; + const isSelected = selectedUiWeeklyDay === day; const isSaving = savingField === 'weekly_day'; return ( Date: Fri, 15 May 2026 21:04:14 +0200 Subject: [PATCH 06/13] feat: add translations for FormComponents and TaskEditModal --- src/components/Task/FormComponents.tsx | 2 +- src/components/Task/TaskEditModal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Task/FormComponents.tsx b/src/components/Task/FormComponents.tsx index e7b80b1..4c7a212 100644 --- a/src/components/Task/FormComponents.tsx +++ b/src/components/Task/FormComponents.tsx @@ -303,4 +303,4 @@ export const NumberInput = ({ ); -}; \ No newline at end of file +}; diff --git a/src/components/Task/TaskEditModal.tsx b/src/components/Task/TaskEditModal.tsx index b9db2d3..3b0ba10 100644 --- a/src/components/Task/TaskEditModal.tsx +++ b/src/components/Task/TaskEditModal.tsx @@ -445,4 +445,4 @@ const editStyles = StyleSheet.create({ }, }); -export default TaskEditModal; \ No newline at end of file +export default TaskEditModal; From 5d7071ca836cb556b6b4ad6454d3a436e7250fc3 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Fri, 15 May 2026 21:41:39 +0200 Subject: [PATCH 07/13] feat: add translations for TaskCard, TaskActionMenu, ItemDetailModal, CompletedTasksModal and CreateRecurringTaskModal --- .../BotChat/widgets/ItemDetailModal.tsx | 36 ++++++++--- .../Task/CreateRecurringTaskModal.tsx | 7 ++- src/components/Task/TaskActionMenu.tsx | 47 ++++++++------- src/components/Task/TaskCard.tsx | 59 ++++++++++++------ src/locales/en.json | 60 +++++++++++++++++++ src/locales/it.json | 60 +++++++++++++++++++ 6 files changed, 218 insertions(+), 51 deletions(-) diff --git a/src/components/BotChat/widgets/ItemDetailModal.tsx b/src/components/BotChat/widgets/ItemDetailModal.tsx index dc5968d..b266a41 100644 --- a/src/components/BotChat/widgets/ItemDetailModal.tsx +++ b/src/components/BotChat/widgets/ItemDetailModal.tsx @@ -11,6 +11,7 @@ import { ActivityIndicator, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; +import { useTranslation } from 'react-i18next'; import { ItemDetailModalProps } from '../types'; import * as taskService from '../../../services/taskService'; @@ -23,6 +24,7 @@ const ItemDetailModal: React.FC = ({ itemType, onClose, }) => { + const { t } = useTranslation(); const [isLoading, setIsLoading] = useState(false); // Handler per completare un task @@ -35,10 +37,16 @@ const ItemDetailModal: React.FC = ({ ...item, status: item.completed ? 'pending' : 'completed', }); - Alert.alert('Successo', `Task ${item.completed ? 'riaperto' : 'completato'}`); + Alert.alert( + t('itemDetailModal.success'), + item.completed ? t('itemDetailModal.reopenTask') : t('itemDetailModal.completeTask') + ); onClose(); } catch (error: any) { - Alert.alert('Errore', error.message || 'Impossibile aggiornare il task'); + Alert.alert( + t('itemDetailModal.error'), + error.message || t('itemDetailModal.updateTaskError') + ); } finally { setIsLoading(false); } @@ -46,13 +54,22 @@ const ItemDetailModal: React.FC = ({ // Handler per eliminare un item const handleDelete = () => { + let confirmMessage: string; + if (itemType === 'task') { + confirmMessage = t('itemDetailModal.deleteTaskConfirm'); + } else if (itemType === 'category') { + confirmMessage = t('itemDetailModal.deleteCategoryConfirm'); + } else { + confirmMessage = t('itemDetailModal.deleteNoteConfirm'); + } + Alert.alert( - 'Conferma eliminazione', - `Sei sicuro di voler eliminare ${itemType === 'task' ? 'questo task' : itemType === 'category' ? 'questa categoria' : 'questa nota'}?`, + t('itemDetailModal.deleteConfirm'), + confirmMessage, [ - { text: 'Annulla', style: 'cancel' }, + { text: t('common.buttons.cancel'), style: 'cancel' }, { - text: 'Elimina', + text: t('common.buttons.delete'), style: 'destructive', onPress: async () => { setIsLoading(true); @@ -61,10 +78,13 @@ const ItemDetailModal: React.FC = ({ await taskService.deleteTask(item.task_id); } // TODO: Implementa delete per categorie e note se necessario - Alert.alert('Successo', 'Elemento eliminato'); + Alert.alert(t('itemDetailModal.success'), t('itemDetailModal.itemDeleted')); onClose(); } catch (error: any) { - Alert.alert('Errore', error.message || 'Impossibile eliminare'); + Alert.alert( + t('itemDetailModal.error'), + error.message || t('itemDetailModal.deleteError') + ); } finally { setIsLoading(false); } diff --git a/src/components/Task/CreateRecurringTaskModal.tsx b/src/components/Task/CreateRecurringTaskModal.tsx index 8578392..20bf5d3 100644 --- a/src/components/Task/CreateRecurringTaskModal.tsx +++ b/src/components/Task/CreateRecurringTaskModal.tsx @@ -13,6 +13,7 @@ import { Platform, } from 'react-native'; import DateTimePickerModal from 'react-native-modal-datetime-picker'; +import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; import { getCategories } from '../../services/taskService'; import { @@ -63,6 +64,8 @@ const nextFullHour = (): Date => { // ── Component ────────────────────────────────────────────────────────────── const CreateRecurringTaskModal: React.FC = ({ visible, taskId, onClose, onSaved }) => { + const { t } = useTranslation(); + // Form state const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); @@ -241,10 +244,10 @@ const CreateRecurringTaskModal: React.FC = ({ visible, taskId, onClose, o {/* Header */} - {isEditMode ? 'Modifica task ricorrente' : 'Nuovo task ricorrente'} + {isEditMode ? t('tasks.editTask') + ' ' + t('recurring.title') : 'Nuovo task ricorrente'} - Annulla + {t('common.buttons.cancel')} diff --git a/src/components/Task/TaskActionMenu.tsx b/src/components/Task/TaskActionMenu.tsx index b5d10d1..f87f9c8 100644 --- a/src/components/Task/TaskActionMenu.tsx +++ b/src/components/Task/TaskActionMenu.tsx @@ -1,19 +1,22 @@ import React from "react"; import { View, Text, TouchableOpacity, Modal } from "react-native"; import { MaterialIcons } from "@expo/vector-icons"; +import { useTranslation } from "react-i18next"; import { styles } from "./TaskStyles"; // Componente per il menu delle azioni -const TaskActionMenu = ({ - visible, - onClose, - onEdit, - onDelete, +const TaskActionMenu = ({ + visible, + onClose, + onEdit, + onDelete, onShare, isDeleting, isCompleted, // Nuova prop per identificare se il task è completato onReopen // Nuova prop per gestire la riapertura del task }) => { + const { t } = useTranslation(); + return ( - {isCompleted ? ( // Menu semplificato per i task completati - { onReopen && onReopen(); onClose(); }} > - Riapri impegno + {t('taskActionMenu.reopen')} ) : ( // Menu completo per i task non completati <> - - Modifica + {t('taskActionMenu.edit')} - - - {isDeleting ? "Eliminazione..." : "Elimina"} + {isDeleting ? t('taskActionMenu.deleting') : t('taskActionMenu.delete')} - - - Condividi + {t('taskActionMenu.share')} )} diff --git a/src/components/Task/TaskCard.tsx b/src/components/Task/TaskCard.tsx index 2e75599..ca4ebeb 100644 --- a/src/components/Task/TaskCard.tsx +++ b/src/components/Task/TaskCard.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { View, TouchableOpacity, StyleSheet } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import dayjs from 'dayjs'; +import { useTranslation } from 'react-i18next'; import { Task } from '../../services/taskService'; import { CardSurface, AppText, StatusChip } from '../UI/foundation'; import { colors, spacing } from '../../theme/tokens'; @@ -12,6 +13,8 @@ export interface TaskCardProps { } const TaskCard: React.FC = ({ task, onPress }) => { + const { t } = useTranslation(); + const sanitizeString = (value: any): string => { if (typeof value === 'string') { return value.trim(); @@ -26,7 +29,7 @@ const TaskCard: React.FC = ({ task, onPress }) => { const dateToShow = nextOccurrence || endTime || startTime; if (!dateToShow) { - return 'Nessuna scadenza'; + return t('taskCard.noDeadline'); } const now = dayjs(); @@ -34,9 +37,9 @@ const TaskCard: React.FC = ({ task, onPress }) => { let datePrefix = ''; if (taskDate.isSame(now, 'day')) { - datePrefix = 'Oggi '; + datePrefix = t('taskCard.today') + ' '; } else if (taskDate.isSame(now.add(1, 'day'), 'day')) { - datePrefix = 'Domani '; + datePrefix = t('taskCard.tomorrow') + ' '; } else { datePrefix = taskDate.format('DD/MM '); } @@ -89,43 +92,59 @@ const TaskCard: React.FC = ({ task, onPress }) => { const interval = task.recurrence_interval || 1; if (task.recurrence_pattern === 'daily') { - return interval === 1 ? 'Ogni giorno' : `Ogni ${interval} giorni`; + return interval === 1 + ? t('taskCard.recurring.daily') + : t('taskCard.recurring.daily_plural', { count: interval }); } if (task.recurrence_pattern === 'weekly') { - const dayNames = ['Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab', 'Dom']; + const dayNames = [ + t('taskCard.daysOfWeek.monday'), + t('taskCard.daysOfWeek.tuesday'), + t('taskCard.daysOfWeek.wednesday'), + t('taskCard.daysOfWeek.thursday'), + t('taskCard.daysOfWeek.friday'), + t('taskCard.daysOfWeek.saturday'), + t('taskCard.daysOfWeek.sunday') + ]; if (task.recurrence_days_of_week && task.recurrence_days_of_week.length > 0) { const days = task.recurrence_days_of_week.map(d => dayNames[d - 1]).join(', '); - return interval === 1 ? `Ogni ${days}` : `Ogni ${interval} settimane: ${days}`; + return interval === 1 + ? t('taskCard.recurring.weekly_days', { days }) + : t('taskCard.recurring.weekly_days_plural', { count: interval, days }); } - return interval === 1 ? 'Ogni settimana' : `Ogni ${interval} settimane`; + return interval === 1 + ? t('taskCard.recurring.weekly') + : t('taskCard.recurring.weekly_plural', { count: interval }); } if (task.recurrence_pattern === 'monthly') { const day = task.recurrence_day_of_month || 1; return interval === 1 - ? `Ogni ${day}° giorno del mese` - : `Ogni ${interval} mesi il ${day}°`; + ? t('taskCard.recurring.monthly', { day }) + : t('taskCard.recurring.monthly_plural', { count: interval, day }); } - return 'Ricorrente'; + return t('taskCard.recurring.label'); }; const formatDuration = (minutes?: number | null): string | null => { if (!minutes) return null; - if (minutes < 60) return `${minutes} min`; + if (minutes < 60) return t('taskCard.duration.minutes', { count: minutes }); const hours = Math.floor(minutes / 60); const remainingMinutes = minutes % 60; if (remainingMinutes === 0) { - return hours === 1 ? '1 ora' : `${hours} ore`; + return hours === 1 + ? t('taskCard.duration.hour') + : t('taskCard.duration.hours', { count: hours }); } - return `${hours}h ${remainingMinutes}min`; + return t('taskCard.duration.hourMinutes', { hours, minutes: remainingMinutes }); }; const priorityColors: Record = { - 'Alta': '#000000', - 'Media': '#333333', - 'Bassa': '#666666', + [t('taskCard.priority.high')]: '#000000', + [t('taskCard.priority.medium')]: '#333333', + [t('taskCard.priority.low')]: '#666666', 'default': '#999999' }; @@ -173,7 +192,9 @@ const TaskCard: React.FC = ({ task, onPress }) => { {task.is_recurring && task.recurrence_current_count !== undefined && task.recurrence_current_count > 0 && ( - Completato {task.recurrence_current_count} {task.recurrence_current_count === 1 ? 'volta' : 'volte'} + {task.recurrence_current_count === 1 + ? t('taskCard.completion.single', { count: task.recurrence_current_count }) + : t('taskCard.completion.plural', { count: task.recurrence_current_count })} )} @@ -205,7 +226,7 @@ const TaskCard: React.FC = ({ task, onPress }) => { const dateString = task.next_occurrence || task.end_time || computeNextOccurrence(); const label = dateString ? formatTaskTime(undefined, dateString, undefined) - : 'Ricorrente'; + : t('taskCard.recurring.label'); return ( @@ -216,7 +237,7 @@ const TaskCard: React.FC = ({ task, onPress }) => { const dateString = task.end_time; const label = dateString ? formatTaskTime(task.start_time, task.end_time) - : 'Nessuna scadenza'; + : t('taskCard.noDeadline'); return ( diff --git a/src/locales/en.json b/src/locales/en.json index d12dc12..3711fb6 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1225,5 +1225,65 @@ "totalTasks": "{{total}} total tasks", "error": "Error", "loadFailed": "Unable to load tasks for search" + }, + "taskCard": { + "noDeadline": "No deadline", + "today": "Today", + "tomorrow": "Tomorrow", + "recurring": { + "daily": "Every day", + "daily_plural": "Every {{count}} days", + "weekly": "Every week", + "weekly_plural": "Every {{count}} weeks", + "weekly_days": "Every {{days}}", + "weekly_days_plural": "Every {{count}} weeks: {{days}}", + "monthly": "Every {{day}}st day of the month", + "monthly_plural": "Every {{count}} months on the {{day}}", + "label": "Recurring" + }, + "daysOfWeek": { + "monday": "Mon", + "tuesday": "Tue", + "wednesday": "Wed", + "thursday": "Thu", + "friday": "Fri", + "saturday": "Sat", + "sunday": "Sun" + }, + "duration": { + "minutes": "{{count}} min", + "hour": "1 hour", + "hours": "{{count}} hours", + "hourMinutes": "{{hours}}h {{minutes}}min" + }, + "completion": { + "single": "Completed {{count}} time", + "plural": "Completed {{count}} times" + }, + "priority": { + "high": "High", + "medium": "Medium", + "low": "Low" + } + }, + "taskActionMenu": { + "reopen": "Reopen task", + "edit": "Edit", + "share": "Share", + "delete": "Delete", + "deleting": "Deleting..." + }, + "itemDetailModal": { + "success": "Success", + "error": "Error", + "completeTask": "Task completed", + "reopenTask": "Task reopened", + "updateTaskError": "Unable to update task", + "deleteConfirm": "Confirm deletion", + "deleteTaskConfirm": "Are you sure you want to delete this task?", + "deleteCategoryConfirm": "Are you sure you want to delete this category?", + "deleteNoteConfirm": "Are you sure you want to delete this note?", + "deleteError": "Unable to delete", + "itemDeleted": "Item deleted" } } diff --git a/src/locales/it.json b/src/locales/it.json index 87db085..dca6a05 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -1225,5 +1225,65 @@ "totalTasks": "{{total}} task totali", "error": "Errore", "loadFailed": "Impossibile caricare i task per la ricerca" + }, + "taskCard": { + "noDeadline": "Nessuna scadenza", + "today": "Oggi", + "tomorrow": "Domani", + "recurring": { + "daily": "Ogni giorno", + "daily_plural": "Ogni {{count}} giorni", + "weekly": "Ogni settimana", + "weekly_plural": "Ogni {{count}} settimane", + "weekly_days": "Ogni {{days}}", + "weekly_days_plural": "Ogni {{count}} settimane: {{days}}", + "monthly": "Ogni {{day}}° giorno del mese", + "monthly_plural": "Ogni {{count}} mesi il {{day}}°", + "label": "Ricorrente" + }, + "daysOfWeek": { + "monday": "Lun", + "tuesday": "Mar", + "wednesday": "Mer", + "thursday": "Gio", + "friday": "Ven", + "saturday": "Sab", + "sunday": "Dom" + }, + "duration": { + "minutes": "{{count}} min", + "hour": "1 ora", + "hours": "{{count}} ore", + "hourMinutes": "{{hours}}h {{minutes}}min" + }, + "completion": { + "single": "Completato {{count}} volta", + "plural": "Completato {{count}} volte" + }, + "priority": { + "high": "Alta", + "medium": "Media", + "low": "Bassa" + } + }, + "taskActionMenu": { + "reopen": "Riapri impegno", + "edit": "Modifica", + "share": "Condividi", + "delete": "Elimina", + "deleting": "Eliminazione..." + }, + "itemDetailModal": { + "success": "Successo", + "error": "Errore", + "completeTask": "Task completato", + "reopenTask": "Task riaperto", + "updateTaskError": "Impossibile aggiornare il task", + "deleteConfirm": "Conferma eliminazione", + "deleteTaskConfirm": "Sei sicuro di voler eliminare questo task?", + "deleteCategoryConfirm": "Sei sicuro di voler eliminare questa categoria?", + "deleteNoteConfirm": "Sei sicuro di voler eliminare questa nota?", + "deleteError": "Impossibile eliminare", + "itemDeleted": "Elemento eliminato" } } From b38325aad28728e691c9c9b8d542a82c67d67586 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Fri, 15 May 2026 21:49:16 +0200 Subject: [PATCH 08/13] feat: add overdue label to TaskCard and translate task sharing and deletion messages --- src/components/Calendar/CalendarView.tsx | 4 +++- src/components/Task/Task.tsx | 7 ++++++- src/components/Task/TaskCard.tsx | 16 ++++++++++++++++ src/locales/en.json | 10 ++++++++++ src/locales/it.json | 10 ++++++++++ 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/components/Calendar/CalendarView.tsx b/src/components/Calendar/CalendarView.tsx index a0a2a75..51120d3 100644 --- a/src/components/Calendar/CalendarView.tsx +++ b/src/components/Calendar/CalendarView.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; import { View, ScrollView, StyleSheet, Alert, ActivityIndicator, Dimensions } from 'react-native'; +import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; import { Task as TaskType, getAllTasks, addTask, deleteTask, updateTask, completeTask, disCompleteTask } from '../../services/taskService'; import { TaskCacheService } from '../../services/TaskCacheService'; @@ -16,6 +17,7 @@ import { addTaskToList } from '../TaskList/types'; import { LoadingState, EmptyState, StatusChip, AppText } from '../UI/foundation'; const CalendarView: React.FC = () => { + const { t } = useTranslation(); const [selectedDate, setSelectedDate] = useState(dayjs().format('YYYY-MM-DD')); const [tasks, setTasks] = useState([]); const [showAddTask, setShowAddTask] = useState(false); @@ -339,7 +341,7 @@ const CalendarView: React.FC = () => { )); } catch (error) { console.error("Errore nell'eliminazione del task:", error); - Alert.alert("Errore", "Impossibile eliminare il task. Riprova."); + Alert.alert(t('itemDetailModal.error'), t('taskDelete.error')); } }; diff --git a/src/components/Task/Task.tsx b/src/components/Task/Task.tsx index f832099..10f8bb2 100644 --- a/src/components/Task/Task.tsx +++ b/src/components/Task/Task.tsx @@ -1,6 +1,7 @@ import React, { useState, useRef, useEffect } from "react"; import { TouchableOpacity, Animated, Alert, LayoutAnimation, Platform, UIManager, Easing } from "react-native"; import { MaterialIcons } from "@expo/vector-icons"; +import { useTranslation } from "react-i18next"; // Importa i sottocomponenti import TaskHeader from "./TaskHeader"; @@ -31,6 +32,8 @@ const Task = ({ permissionLevel = "READ_WRITE", hideCheckbox = false, }) => { + console.log('[TASK] Rendering Task:', { id: task.id, title: task.title, status: task.status, isCompleted: task.status === "Completato" }); + // Stati const [expanded, setExpanded] = useState(false); const [showModal, setShowModal] = useState(false); @@ -59,6 +62,7 @@ const Task = ({ const containerHeightAnim = useRef(new Animated.Value(0)).current; // Determina se il task è completato + const { t } = useTranslation(); const isCompleted = task.status === "Completato"; // Ottieni il colore di priorità per lo sfondo e il bordo const priorityBackgroundColor = getPriorityColors(task.priority); @@ -141,6 +145,7 @@ const Task = ({ const onComponentLayout = (event) => { if (componentHeight === 0) { const height = event.nativeEvent.layout.height; + console.log('[TASK] onComponentLayout:', { taskId: task.id, height, title: task.title }); setComponentHeight(height); heightAnim.setValue(height); containerHeightAnim.setValue(height); @@ -328,7 +333,7 @@ const Task = ({ const handleShare = () => { setShowModal(false); - Alert.alert("Condivisione", "Funzionalità di condivisione non ancora implementata"); + Alert.alert(t('taskSharing.notImplemented'), t('taskSharing.notImplemented')); }; const handleReopen = async () => { diff --git a/src/components/Task/TaskCard.tsx b/src/components/Task/TaskCard.tsx index ca4ebeb..5542c61 100644 --- a/src/components/Task/TaskCard.tsx +++ b/src/components/Task/TaskCard.tsx @@ -86,6 +86,15 @@ const TaskCard: React.FC = ({ task, onPress }) => { return null; }; + const isTaskOverdue = (): boolean => { + if (!task.end_time || task.status === 'Completato' || task.status === 'completed') { + return false; + } + const now = dayjs(); + const endTime = dayjs(task.end_time); + return endTime.isBefore(now); + }; + const getRecurrenceDescription = (): string | null => { if (!task.is_recurring || !task.recurrence_pattern) return null; @@ -206,6 +215,13 @@ const TaskCard: React.FC = ({ task, onPress }) => { ) : null; })()} + {isTaskOverdue() && ( + + )} + Date: Sat, 16 May 2026 10:37:43 +0200 Subject: [PATCH 09/13] feat: add translation support for task error messages, deletion confirmations, and date display --- src/components/Task/BasicComponents.tsx | 5 +++-- src/components/Task/Task.tsx | 12 ++++++------ src/components/Task/TaskUtils.tsx | 4 ++-- src/locales/en.json | 8 +++++++- src/locales/it.json | 8 +++++++- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/components/Task/BasicComponents.tsx b/src/components/Task/BasicComponents.tsx index 362925d..4d1d525 100644 --- a/src/components/Task/BasicComponents.tsx +++ b/src/components/Task/BasicComponents.tsx @@ -85,9 +85,10 @@ export const DaysRemaining = ({ isRecurring?: boolean; nextOccurrence?: string; }) => { + const { t } = useTranslation(); if (isRecurring) { const dateToUse = nextOccurrence || endDate; - const text = dateToUse ? getDaysRemainingText(dateToUse) : "Ricorrente"; + const text = dateToUse ? getDaysRemainingText(dateToUse, t) : "Ricorrente"; const color = dateToUse ? getDaysRemainingColor(dateToUse) : "#007AFF"; return ( @@ -97,7 +98,7 @@ export const DaysRemaining = ({ ); } - const daysRemainingText = getDaysRemainingText(endDate); + const daysRemainingText = getDaysRemainingText(endDate, t); const daysRemainingColor = getDaysRemainingColor(endDate); return ( diff --git a/src/components/Task/Task.tsx b/src/components/Task/Task.tsx index 10f8bb2..50ff2ab 100644 --- a/src/components/Task/Task.tsx +++ b/src/components/Task/Task.tsx @@ -171,7 +171,7 @@ const Task = ({ } } catch (error) { console.error("Errore durante la modifica dello stato del task:", error); - Alert.alert("Errore", "Impossibile modificare lo stato del task. Riprova."); + Alert.alert(t("tasks.errors.title"), t("tasks.errors.updateStatusFailed")); } }; @@ -307,11 +307,11 @@ const Task = ({ setIsDeleting(true); Alert.alert( - "Elimina Task", - `Sei sicuro di voler eliminare "${task.title}"?`, + t("taskDelete.confirmTitle"), + t("taskDelete.confirmMessage", { title: task.title }), [ { - text: "Annulla", + text: t("common.buttons.cancel"), style: "cancel", onPress: () => { setIsDeleting(false); @@ -319,7 +319,7 @@ const Task = ({ } }, { - text: "Elimina", + text: t("common.buttons.delete"), style: "destructive", onPress: () => { setShowModal(false); @@ -346,7 +346,7 @@ const Task = ({ } } catch (error) { console.error("Errore durante la riapertura del task:", error); - Alert.alert("Errore", "Impossibile riaprire il task. Riprova."); + Alert.alert(t("tasks.errors.title"), t("tasks.errors.reopenFailed")); } }; diff --git a/src/components/Task/TaskUtils.tsx b/src/components/Task/TaskUtils.tsx index 7b8b395..6d94c27 100644 --- a/src/components/Task/TaskUtils.tsx +++ b/src/components/Task/TaskUtils.tsx @@ -3,7 +3,7 @@ import { Dimensions } from "react-native"; export const { width } = Dimensions.get("window"); // Funzioni di utility per date e priorità -export const getDaysRemainingText = (endDate) => { +export const getDaysRemainingText = (endDate, t) => { if (!endDate) return "Nessuna scadenza"; const today = new Date(); @@ -13,7 +13,7 @@ export const getDaysRemainingText = (endDate) => { const diffTime = dueDate.getTime() - today.getTime(); const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24)); - if (diffDays < 0) return "Scaduto"; + if (diffDays < 0) return t ? t("taskCard.overdue") : "Scaduto"; if (diffDays === 0) { const due = new Date(endDate); const hours = due.getHours().toString().padStart(2, "0"); diff --git a/src/locales/en.json b/src/locales/en.json index 1c8a80c..ad284ff 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -713,6 +713,11 @@ "deleted": "Task deleted successfully", "completed": "Task completed", "reopened": "Task reopened" + }, + "errors": { + "title": "Error", + "updateStatusFailed": "Unable to update task status. Please try again.", + "reopenFailed": "Unable to reopen the task. Please try again." } }, "notes": { @@ -1292,7 +1297,8 @@ "notEnabled": "Task sharing is not enabled" }, "taskDelete": { - "confirm": "Are you sure you want to delete the task", + "confirmTitle": "Delete task", + "confirmMessage": "Are you sure you want to delete \"{{title}}\"?", "error": "Unable to delete task. Please try again.", "deleteAllCompletedError": "Unable to delete all completed tasks. Please try again." } diff --git a/src/locales/it.json b/src/locales/it.json index a70dc49..6000466 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -714,6 +714,11 @@ "deleted": "Task eliminato con successo", "completed": "Task completato", "reopened": "Task riaperto" + }, + "errors": { + "title": "Errore", + "updateStatusFailed": "Impossibile aggiornare lo stato del task. Riprova.", + "reopenFailed": "Impossibile riaprire il task. Riprova." } }, "notes": { @@ -1292,7 +1297,8 @@ "notEnabled": "La condivisione dei task non è abilitata" }, "taskDelete": { - "confirm": "Sei sicuro di voler eliminare il task", + "confirmTitle": "Elimina task", + "confirmMessage": "Sei sicuro di voler eliminare \"{{title}}\"?", "error": "Impossibile eliminare il task. Riprova.", "deleteAllCompletedError": "Impossibile eliminare tutti i task completati. Riprova." } From 9350d031c1a5b7d8fdf9fd802a46531977487ef9 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Sat, 16 May 2026 10:37:51 +0200 Subject: [PATCH 10/13] feat: complete translation integration for TaskUtils and BasicComponents --- src/components/Task/BasicComponents.tsx | 2 +- src/components/Task/TaskUtils.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Task/BasicComponents.tsx b/src/components/Task/BasicComponents.tsx index 4d1d525..5640f37 100644 --- a/src/components/Task/BasicComponents.tsx +++ b/src/components/Task/BasicComponents.tsx @@ -124,4 +124,4 @@ export const TaskTitle = ({ title, completed, numberOfLines, priority }) => { {title} ); -}; \ No newline at end of file +}; diff --git a/src/components/Task/TaskUtils.tsx b/src/components/Task/TaskUtils.tsx index 6d94c27..bc0fc43 100644 --- a/src/components/Task/TaskUtils.tsx +++ b/src/components/Task/TaskUtils.tsx @@ -76,4 +76,4 @@ export const getPriorityBorderColor = (priority) => { default: return "#666666"; // Grigio medio per bassa priorità } -}; \ No newline at end of file +}; From 6bc98ce38e6591f96e421f119c2034aafb92ca16 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Sat, 16 May 2026 11:18:51 +0200 Subject: [PATCH 11/13] fix: return null for missing endDate in BasicComponents --- src/components/Task/BasicComponents.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Task/BasicComponents.tsx b/src/components/Task/BasicComponents.tsx index 5640f37..733e098 100644 --- a/src/components/Task/BasicComponents.tsx +++ b/src/components/Task/BasicComponents.tsx @@ -98,6 +98,8 @@ export const DaysRemaining = ({ ); } + if (!endDate) return null; + const daysRemainingText = getDaysRemainingText(endDate, t); const daysRemainingColor = getDaysRemainingColor(endDate); From 8df64b98b33d7ea3118167e0c80ad3d879383602 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Sat, 16 May 2026 11:18:59 +0200 Subject: [PATCH 12/13] fix: return empty string for missing endDate in TaskUtils --- src/components/Task/TaskUtils.tsx | 2 +- src/locales/en.json | 23 +++++++++++++++++++++++ src/locales/it.json | 23 +++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/components/Task/TaskUtils.tsx b/src/components/Task/TaskUtils.tsx index bc0fc43..ce46b5a 100644 --- a/src/components/Task/TaskUtils.tsx +++ b/src/components/Task/TaskUtils.tsx @@ -4,7 +4,7 @@ export const { width } = Dimensions.get("window"); // Funzioni di utility per date e priorità export const getDaysRemainingText = (endDate, t) => { - if (!endDate) return "Nessuna scadenza"; + if (!endDate) return ""; const today = new Date(); today.setHours(0, 0, 0, 0); diff --git a/src/locales/en.json b/src/locales/en.json index ad284ff..cb35349 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1301,5 +1301,28 @@ "confirmMessage": "Are you sure you want to delete \"{{title}}\"?", "error": "Unable to delete task. Please try again.", "deleteAllCompletedError": "Unable to delete all completed tasks. Please try again." + }, + "completedTasks": { + "buttonLabel_one": "completed", + "buttonLabel_other": "completed", + "count_one": "{{count}} completed task", + "count_other": "{{count}} completed tasks", + "chartTitle": "Last 7 days", + "days": { + "sunday": "Sun", + "monday": "Mon", + "tuesday": "Tue", + "wednesday": "Wed", + "thursday": "Thu", + "friday": "Fri", + "saturday": "Sat", + "today": "Today" + }, + "deleteAll": { + "label": "Delete all", + "title": "Delete all completed tasks", + "message": "Are you sure? All completed tasks will be permanently deleted." + }, + "comingSoon": "Work in progress" } } diff --git a/src/locales/it.json b/src/locales/it.json index 6000466..bb33899 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -1301,5 +1301,28 @@ "confirmMessage": "Sei sicuro di voler eliminare \"{{title}}\"?", "error": "Impossibile eliminare il task. Riprova.", "deleteAllCompletedError": "Impossibile eliminare tutti i task completati. Riprova." + }, + "completedTasks": { + "buttonLabel_one": "completato", + "buttonLabel_other": "completati", + "count_one": "{{count}} task completato", + "count_other": "{{count}} task completati", + "chartTitle": "Ultimi 7 giorni", + "days": { + "sunday": "Dom", + "monday": "Lun", + "tuesday": "Mar", + "wednesday": "Mer", + "thursday": "Gio", + "friday": "Ven", + "saturday": "Sab", + "today": "Oggi" + }, + "deleteAll": { + "label": "Elimina tutti", + "title": "Elimina tutti i task completati", + "message": "Sei sicuro? Tutti i task completati verranno eliminati definitivamente." + }, + "comingSoon": "Prossimamente" } } From 4ad3a90102537d3f12a8ed94d6a5030f16899ba3 Mon Sep 17 00:00:00 2001 From: Gabry848 Date: Sat, 16 May 2026 11:55:17 +0200 Subject: [PATCH 13/13] feat: add calendar copy for calendar views