feat(localization): add and update translations for multiple languages
- Introduced new localization files for Albanian, German, Spanish, Portuguese, and Chinese, enhancing the application's multilingual support. - Added new keys and updated existing translations in project-view, task-list-table, and settings files to improve user experience across different languages. - Enhanced error handling and empty state messages in task management components to provide clearer feedback to users. - Updated tooltip texts and button labels for better clarity and consistency in the user interface.
This commit is contained in:
14
worklenz-frontend/public/locales/alb/project-view.json
Normal file
14
worklenz-frontend/public/locales/alb/project-view.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"taskList": "Lista e Detyrave",
|
||||
"board": "Tabela Kanban",
|
||||
"insights": "Analiza",
|
||||
"files": "Skedarë",
|
||||
"members": "Anëtarë",
|
||||
"updates": "Përditësime",
|
||||
"projectView": "Pamja e Projektit",
|
||||
"loading": "Duke ngarkuar projektin...",
|
||||
"error": "Gabim në ngarkimin e projektit",
|
||||
"pinnedTab": "E fiksuar si tab i parazgjedhur",
|
||||
"pinTab": "Fikso si tab i parazgjedhur",
|
||||
"unpinTab": "Hiqe fiksimin e tab-it të parazgjedhur"
|
||||
}
|
||||
@@ -4,14 +4,26 @@
|
||||
"createTask": "Krijo detyrë",
|
||||
"settings": "Cilësimet",
|
||||
"subscribe": "Abonohu",
|
||||
"unsubscribe": "Ç'abonohu",
|
||||
"unsubscribe": "Çabonohu",
|
||||
"deleteProject": "Fshi projektin",
|
||||
"startDate": "Data e fillimit",
|
||||
"endDate": "Data e përfundimit",
|
||||
"endDate": "Data e mbarimit",
|
||||
"projectSettings": "Cilësimet e projektit",
|
||||
"projectSummary": "Përmbledhja e projektit",
|
||||
"receiveProjectSummary": "Merrni një përmbledhje të projektit çdo mbrëmje.",
|
||||
"refreshProject": "Rifresko projektin",
|
||||
"saveAsTemplate": "Ruaje si shabllon",
|
||||
"invite": "Fto"
|
||||
"saveAsTemplate": "Ruaj si model",
|
||||
"invite": "Fto",
|
||||
"subscribeTooltip": "Abonohu tek njoftimet e projektit",
|
||||
"unsubscribeTooltip": "Çabonohu nga njoftimet e projektit",
|
||||
"refreshTooltip": "Rifresko të dhënat e projektit",
|
||||
"settingsTooltip": "Hap cilësimet e projektit",
|
||||
"saveAsTemplateTooltip": "Ruaj këtë projekt si model",
|
||||
"inviteTooltip": "Fto anëtarë të ekipit në këtë projekt",
|
||||
"createTaskTooltip": "Krijo një detyrë të re",
|
||||
"importTaskTooltip": "Importo detyrë nga modeli",
|
||||
"navigateBackTooltip": "Kthehu tek lista e projekteve",
|
||||
"projectStatusTooltip": "Statusi i projektit",
|
||||
"projectDatesInfo": "Informacion për kohëzgjatjen e projektit",
|
||||
"projectCategoryTooltip": "Kategoria e projektit"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"saveChanges": "Ruaj Ndryshimet",
|
||||
"profileJoinedText": "U bashkua një muaj më parë",
|
||||
"profileLastUpdatedText": "Përditësuar një muaj më parë",
|
||||
"avatarTooltip": "Klikoni për të ngarkuar një avatar"
|
||||
"avatarTooltip": "Klikoni për të ngarkuar një avatar",
|
||||
"title": "Cilësimet e Profilit"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"title": "Anëtarët e Ekipit",
|
||||
"nameColumn": "Emri",
|
||||
"projectsColumn": "Projektet",
|
||||
"emailColumn": "Email",
|
||||
@@ -40,5 +41,7 @@
|
||||
"ownerText": "Pronar i Ekipit",
|
||||
"addedText": "Shtuar",
|
||||
"updatedText": "Përditësuar",
|
||||
"noResultFound": "Shkruani një adresë email dhe shtypni Enter..."
|
||||
"noResultFound": "Shkruani një adresë email dhe shtypni Enter...",
|
||||
"jobTitlesFetchError": "Dështoi marrja e titujve të punës",
|
||||
"invitationResent": "Ftesa u dërgua sërish me sukses!"
|
||||
}
|
||||
|
||||
16
worklenz-frontend/public/locales/alb/settings/teams.json
Normal file
16
worklenz-frontend/public/locales/alb/settings/teams.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"title": "Ekipet",
|
||||
"team": "Ekip",
|
||||
"teams": "Ekipet",
|
||||
"name": "Emri",
|
||||
"created": "Krijuar",
|
||||
"ownsBy": "I përket",
|
||||
"edit": "Ndrysho",
|
||||
"editTeam": "Ndrysho Ekipin",
|
||||
"pinTooltip": "Kliko për ta fiksuar në menunë kryesore",
|
||||
"editTeamName": "Ndrysho Emrin e Ekipit",
|
||||
"updateName": "Përditëso Emrin",
|
||||
"namePlaceholder": "Emri",
|
||||
"nameRequired": "Ju lutem shkruani një Emër",
|
||||
"updateFailed": "Ndryshimi i emrit të ekipit dështoi!"
|
||||
}
|
||||
@@ -68,6 +68,13 @@
|
||||
"dueDatePlaceholder": "Data e afatit",
|
||||
"startDatePlaceholder": "Data e fillimit",
|
||||
|
||||
"emptyStates": {
|
||||
"noTaskGroups": "Nuk u gjetën grupe detyrash",
|
||||
"noTaskGroupsDescription": "Detyrat do të shfaqen këtu kur krijohen ose kur aplikohen filtra.",
|
||||
"errorPrefix": "Gabim:",
|
||||
"dragTaskFallback": "Detyrë"
|
||||
},
|
||||
|
||||
"customColumns": {
|
||||
"addCustomColumn": "Shto një kolonë të personalizuar",
|
||||
"customColumnHeader": "Kolona e Personalizuar",
|
||||
|
||||
14
worklenz-frontend/public/locales/de/project-view.json
Normal file
14
worklenz-frontend/public/locales/de/project-view.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"taskList": "Aufgabenliste",
|
||||
"board": "Kanban-Board",
|
||||
"insights": "Insights",
|
||||
"files": "Dateien",
|
||||
"members": "Mitglieder",
|
||||
"updates": "Aktualisierungen",
|
||||
"projectView": "Projektansicht",
|
||||
"loading": "Projekt wird geladen...",
|
||||
"error": "Fehler beim Laden des Projekts",
|
||||
"pinnedTab": "Als Standard-Registerkarte festgesetzt",
|
||||
"pinTab": "Als Standard-Registerkarte festsetzen",
|
||||
"unpinTab": "Standard-Registerkarte lösen"
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"createTask": "Aufgabe erstellen",
|
||||
"settings": "Einstellungen",
|
||||
"subscribe": "Abonnieren",
|
||||
"unsubscribe": "Abbestellen",
|
||||
"unsubscribe": "Abonnement beenden",
|
||||
"deleteProject": "Projekt löschen",
|
||||
"startDate": "Startdatum",
|
||||
"endDate": "Enddatum",
|
||||
@@ -13,5 +13,17 @@
|
||||
"receiveProjectSummary": "Erhalten Sie jeden Abend eine Projektzusammenfassung.",
|
||||
"refreshProject": "Projekt aktualisieren",
|
||||
"saveAsTemplate": "Als Vorlage speichern",
|
||||
"invite": "Einladen"
|
||||
"invite": "Einladen",
|
||||
"subscribeTooltip": "Projektbenachrichtigungen abonnieren",
|
||||
"unsubscribeTooltip": "Projektbenachrichtigungen beenden",
|
||||
"refreshTooltip": "Projektdaten aktualisieren",
|
||||
"settingsTooltip": "Projekteinstellungen öffnen",
|
||||
"saveAsTemplateTooltip": "Dieses Projekt als Vorlage speichern",
|
||||
"inviteTooltip": "Teammitglieder zu diesem Projekt einladen",
|
||||
"createTaskTooltip": "Neue Aufgabe erstellen",
|
||||
"importTaskTooltip": "Aufgabe aus Vorlage importieren",
|
||||
"navigateBackTooltip": "Zurück zur Projektliste",
|
||||
"projectStatusTooltip": "Projektstatus",
|
||||
"projectDatesInfo": "Informationen zum Projektzeitraum",
|
||||
"projectCategoryTooltip": "Projektkategorie"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"saveChanges": "Änderungen speichern",
|
||||
"profileJoinedText": "Vor einem Monat beigetreten",
|
||||
"profileLastUpdatedText": "Vor einem Monat aktualisiert",
|
||||
"avatarTooltip": "Klicken Sie zum Hochladen eines Avatars"
|
||||
"avatarTooltip": "Klicken Sie zum Hochladen eines Avatars",
|
||||
"title": "Profil-Einstellungen"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"title": "Teammitglieder",
|
||||
"nameColumn": "Name",
|
||||
"projectsColumn": "Projekte",
|
||||
"emailColumn": "E-Mail",
|
||||
@@ -40,5 +41,7 @@
|
||||
"ownerText": "Team-Besitzer",
|
||||
"addedText": "Hinzugefügt",
|
||||
"updatedText": "Aktualisiert",
|
||||
"noResultFound": "Geben Sie eine E-Mail-Adresse ein und drücken Sie Enter..."
|
||||
"noResultFound": "Geben Sie eine E-Mail-Adresse ein und drücken Sie Enter...",
|
||||
"jobTitlesFetchError": "Fehler beim Abrufen der Jobtitel",
|
||||
"invitationResent": "Einladung erfolgreich erneut gesendet!"
|
||||
}
|
||||
|
||||
16
worklenz-frontend/public/locales/de/settings/teams.json
Normal file
16
worklenz-frontend/public/locales/de/settings/teams.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"title": "Teams",
|
||||
"team": "Team",
|
||||
"teams": "Teams",
|
||||
"name": "Name",
|
||||
"created": "Erstellt",
|
||||
"ownsBy": "Gehört zu",
|
||||
"edit": "Bearbeiten",
|
||||
"editTeam": "Team bearbeiten",
|
||||
"pinTooltip": "Klicken Sie hier, um dies im Hauptmenü zu fixieren",
|
||||
"editTeamName": "Team-Name bearbeiten",
|
||||
"updateName": "Name aktualisieren",
|
||||
"namePlaceholder": "Name",
|
||||
"nameRequired": "Bitte geben Sie einen Namen ein",
|
||||
"updateFailed": "Änderung des Team-Namens fehlgeschlagen!"
|
||||
}
|
||||
@@ -68,6 +68,13 @@
|
||||
"dueDatePlaceholder": "Fälligkeitsdatum",
|
||||
"startDatePlaceholder": "Startdatum",
|
||||
|
||||
"emptyStates": {
|
||||
"noTaskGroups": "Keine Aufgabengruppen gefunden",
|
||||
"noTaskGroupsDescription": "Aufgaben werden hier angezeigt, wenn sie erstellt oder Filter angewendet werden.",
|
||||
"errorPrefix": "Fehler:",
|
||||
"dragTaskFallback": "Aufgabe"
|
||||
},
|
||||
|
||||
"customColumns": {
|
||||
"addCustomColumn": "Benutzerdefinierte Spalte hinzufügen",
|
||||
"customColumnHeader": "Benutzerdefinierte Spalte",
|
||||
|
||||
14
worklenz-frontend/public/locales/en/project-view.json
Normal file
14
worklenz-frontend/public/locales/en/project-view.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"taskList": "Task List",
|
||||
"board": "Kanban Board",
|
||||
"insights": "Insights",
|
||||
"files": "Files",
|
||||
"members": "Members",
|
||||
"updates": "Updates",
|
||||
"projectView": "Project View",
|
||||
"loading": "Loading project...",
|
||||
"error": "Error loading project",
|
||||
"pinnedTab": "Pinned as default tab",
|
||||
"pinTab": "Pin as default tab",
|
||||
"unpinTab": "Unpin default tab"
|
||||
}
|
||||
@@ -13,5 +13,17 @@
|
||||
"receiveProjectSummary": "Receive a project summary every evening.",
|
||||
"refreshProject": "Refresh project",
|
||||
"saveAsTemplate": "Save as template",
|
||||
"invite": "Invite"
|
||||
"invite": "Invite",
|
||||
"subscribeTooltip": "Subscribe to project notifications",
|
||||
"unsubscribeTooltip": "Unsubscribe from project notifications",
|
||||
"refreshTooltip": "Refresh project data",
|
||||
"settingsTooltip": "Open project settings",
|
||||
"saveAsTemplateTooltip": "Save this project as a template",
|
||||
"inviteTooltip": "Invite team members to this project",
|
||||
"createTaskTooltip": "Create a new task",
|
||||
"importTaskTooltip": "Import task from template",
|
||||
"navigateBackTooltip": "Go back to projects list",
|
||||
"projectStatusTooltip": "Project status",
|
||||
"projectDatesInfo": "Project timeline information",
|
||||
"projectCategoryTooltip": "Project category"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"saveChanges": "Save Changes",
|
||||
"profileJoinedText": "Joined a month ago",
|
||||
"profileLastUpdatedText": "Last updated a month ago",
|
||||
"avatarTooltip": "Click to upload an avatar"
|
||||
"avatarTooltip": "Click to upload an avatar",
|
||||
"title": "Profile Settings"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"title": "Team Members",
|
||||
"nameColumn": "Name",
|
||||
"projectsColumn": "Projects",
|
||||
"emailColumn": "Email",
|
||||
@@ -40,5 +41,7 @@
|
||||
"ownerText": "Team Owner",
|
||||
"addedText": "Added",
|
||||
"updatedText": "Updated",
|
||||
"noResultFound": "Type an email address and hit enter..."
|
||||
"noResultFound": "Type an email address and hit enter...",
|
||||
"jobTitlesFetchError": "Failed to fetch job titles",
|
||||
"invitationResent": "Invitation resent successfully!"
|
||||
}
|
||||
|
||||
16
worklenz-frontend/public/locales/en/settings/teams.json
Normal file
16
worklenz-frontend/public/locales/en/settings/teams.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"title": "Teams",
|
||||
"team": "Team",
|
||||
"teams": "Teams",
|
||||
"name": "Name",
|
||||
"created": "Created",
|
||||
"ownsBy": "Owns By",
|
||||
"edit": "Edit",
|
||||
"editTeam": "Edit Team",
|
||||
"pinTooltip": "Click to pin this into the main menu",
|
||||
"editTeamName": "Edit Team Name",
|
||||
"updateName": "Update Name",
|
||||
"namePlaceholder": "Name",
|
||||
"nameRequired": "Please enter a Name",
|
||||
"updateFailed": "Team name change failed!"
|
||||
}
|
||||
@@ -68,6 +68,13 @@
|
||||
"dueDatePlaceholder": "Due Date",
|
||||
"startDatePlaceholder": "Start Date",
|
||||
|
||||
"emptyStates": {
|
||||
"noTaskGroups": "No task groups found",
|
||||
"noTaskGroupsDescription": "Tasks will appear here when they are created or when filters are applied.",
|
||||
"errorPrefix": "Error:",
|
||||
"dragTaskFallback": "Task"
|
||||
},
|
||||
|
||||
"customColumns": {
|
||||
"addCustomColumn": "Add a custom column",
|
||||
"customColumnHeader": "Custom Column",
|
||||
|
||||
14
worklenz-frontend/public/locales/es/project-view.json
Normal file
14
worklenz-frontend/public/locales/es/project-view.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"taskList": "Lista de Tareas",
|
||||
"board": "Tablero Kanban",
|
||||
"insights": "Análisis",
|
||||
"files": "Archivos",
|
||||
"members": "Miembros",
|
||||
"updates": "Actualizaciones",
|
||||
"projectView": "Vista del Proyecto",
|
||||
"loading": "Cargando proyecto...",
|
||||
"error": "Error al cargar el proyecto",
|
||||
"pinnedTab": "Fijado como pestaña predeterminada",
|
||||
"pinTab": "Fijar como pestaña predeterminada",
|
||||
"unpinTab": "Desfijar pestaña predeterminada"
|
||||
}
|
||||
@@ -2,16 +2,28 @@
|
||||
"importTasks": "Importar tareas",
|
||||
"importTask": "Importar tarea",
|
||||
"createTask": "Crear tarea",
|
||||
"settings": "Ajustes",
|
||||
"settings": "Configuración",
|
||||
"subscribe": "Suscribirse",
|
||||
"unsubscribe": "Cancelar suscripción",
|
||||
"deleteProject": "Eliminar proyecto",
|
||||
"startDate": "Fecha de inicio",
|
||||
"endDate": "Fecha de finalización",
|
||||
"projectSettings": "Ajustes del proyecto",
|
||||
"projectSettings": "Configuración del proyecto",
|
||||
"projectSummary": "Resumen del proyecto",
|
||||
"receiveProjectSummary": "Recibir un resumen del proyecto todas las noches.",
|
||||
"receiveProjectSummary": "Recibe un resumen del proyecto cada noche.",
|
||||
"refreshProject": "Actualizar proyecto",
|
||||
"saveAsTemplate": "Guardar como plantilla",
|
||||
"invite": "Invitar"
|
||||
"invite": "Invitar",
|
||||
"subscribeTooltip": "Suscribirse a notificaciones del proyecto",
|
||||
"unsubscribeTooltip": "Cancelar suscripción a notificaciones del proyecto",
|
||||
"refreshTooltip": "Actualizar datos del proyecto",
|
||||
"settingsTooltip": "Abrir configuración del proyecto",
|
||||
"saveAsTemplateTooltip": "Guardar este proyecto como plantilla",
|
||||
"inviteTooltip": "Invitar miembros del equipo a este proyecto",
|
||||
"createTaskTooltip": "Crear una nueva tarea",
|
||||
"importTaskTooltip": "Importar tarea desde plantilla",
|
||||
"navigateBackTooltip": "Volver a la lista de proyectos",
|
||||
"projectStatusTooltip": "Estado del proyecto",
|
||||
"projectDatesInfo": "Información de cronograma del proyecto",
|
||||
"projectCategoryTooltip": "Categoría del proyecto"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"saveChanges": "Guardar cambios",
|
||||
"profileJoinedText": "Se unió hace un mes",
|
||||
"profileLastUpdatedText": "Última actualización hace un mes",
|
||||
"avatarTooltip": "Haz clic para subir un avatar"
|
||||
"avatarTooltip": "Haz clic para subir un avatar",
|
||||
"title": "Configuración del Perfil"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"title": "Miembros del Equipo",
|
||||
"nameColumn": "Nombre",
|
||||
"projectsColumn": "Proyectos",
|
||||
"emailColumn": "Correo electrónico",
|
||||
@@ -40,5 +41,7 @@
|
||||
"ownerText": "Propietario del equipo",
|
||||
"addedText": "Agregado",
|
||||
"updatedText": "Actualizado",
|
||||
"noResultFound": "Escriba una dirección de correo electrónico y presione enter..."
|
||||
"noResultFound": "Escriba una dirección de correo electrónico y presione enter...",
|
||||
"jobTitlesFetchError": "Error al obtener los cargos",
|
||||
"invitationResent": "¡Invitación reenviada exitosamente!"
|
||||
}
|
||||
|
||||
16
worklenz-frontend/public/locales/es/settings/teams.json
Normal file
16
worklenz-frontend/public/locales/es/settings/teams.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"title": "Equipos",
|
||||
"team": "Equipo",
|
||||
"teams": "Equipos",
|
||||
"name": "Nombre",
|
||||
"created": "Creado",
|
||||
"ownsBy": "Pertenece a",
|
||||
"edit": "Editar",
|
||||
"editTeam": "Editar Equipo",
|
||||
"pinTooltip": "Haz clic para fijar esto en el menú principal",
|
||||
"editTeamName": "Editar Nombre del Equipo",
|
||||
"updateName": "Actualizar Nombre",
|
||||
"namePlaceholder": "Nombre",
|
||||
"nameRequired": "Por favor ingresa un Nombre",
|
||||
"updateFailed": "¡Falló el cambio de nombre del equipo!"
|
||||
}
|
||||
@@ -68,6 +68,13 @@
|
||||
"dueDatePlaceholder": "Fecha de vencimiento",
|
||||
"startDatePlaceholder": "Fecha de inicio",
|
||||
|
||||
"emptyStates": {
|
||||
"noTaskGroups": "No se encontraron grupos de tareas",
|
||||
"noTaskGroupsDescription": "Las tareas aparecerán aquí cuando se creen o cuando se apliquen filtros.",
|
||||
"errorPrefix": "Error:",
|
||||
"dragTaskFallback": "Tarea"
|
||||
},
|
||||
|
||||
"customColumns": {
|
||||
"addCustomColumn": "Agregar una columna personalizada",
|
||||
"customColumnHeader": "Columna Personalizada",
|
||||
|
||||
14
worklenz-frontend/public/locales/pt/project-view.json
Normal file
14
worklenz-frontend/public/locales/pt/project-view.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"taskList": "Lista de Tarefas",
|
||||
"board": "Quadro Kanban",
|
||||
"insights": "Insights",
|
||||
"files": "Arquivos",
|
||||
"members": "Membros",
|
||||
"updates": "Atualizações",
|
||||
"projectView": "Visualização do Projeto",
|
||||
"loading": "Carregando projeto...",
|
||||
"error": "Erro ao carregar projeto",
|
||||
"pinnedTab": "Fixada como aba padrão",
|
||||
"pinTab": "Fixar como aba padrão",
|
||||
"unpinTab": "Desfixar aba padrão"
|
||||
}
|
||||
@@ -7,11 +7,23 @@
|
||||
"unsubscribe": "Cancelar inscrição",
|
||||
"deleteProject": "Excluir projeto",
|
||||
"startDate": "Data de início",
|
||||
"endDate": "Data de fim",
|
||||
"endDate": "Data de término",
|
||||
"projectSettings": "Configurações do projeto",
|
||||
"projectSummary": "Resumo do projeto",
|
||||
"receiveProjectSummary": "Receber um resumo do projeto todas as noites.",
|
||||
"receiveProjectSummary": "Receba um resumo do projeto todas as noites.",
|
||||
"refreshProject": "Atualizar projeto",
|
||||
"saveAsTemplate": "Salvar como modelo",
|
||||
"invite": "Convidar"
|
||||
"invite": "Convidar",
|
||||
"subscribeTooltip": "Inscrever-se nas notificações do projeto",
|
||||
"unsubscribeTooltip": "Cancelar inscrição nas notificações do projeto",
|
||||
"refreshTooltip": "Atualizar dados do projeto",
|
||||
"settingsTooltip": "Abrir configurações do projeto",
|
||||
"saveAsTemplateTooltip": "Salvar este projeto como modelo",
|
||||
"inviteTooltip": "Convidar membros da equipe para este projeto",
|
||||
"createTaskTooltip": "Criar uma nova tarefa",
|
||||
"importTaskTooltip": "Importar tarefa de modelo",
|
||||
"navigateBackTooltip": "Voltar para lista de projetos",
|
||||
"projectStatusTooltip": "Status do projeto",
|
||||
"projectDatesInfo": "Informações do cronograma do projeto",
|
||||
"projectCategoryTooltip": "Categoria do projeto"
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
"saveChanges": "Salvar Alterações",
|
||||
"profileJoinedText": "Entrou há um mês",
|
||||
"profileLastUpdatedText": "Última atualização há um mês",
|
||||
"avatarTooltip": "Clique para carregar um avatar"
|
||||
"avatarTooltip": "Clique para carregar um avatar",
|
||||
"title": "Configurações do Perfil"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"title": "Membros da Equipe",
|
||||
"nameColumn": "Nome",
|
||||
"projectsColumn": "Projetos",
|
||||
"emailColumn": "Email",
|
||||
@@ -40,5 +41,7 @@
|
||||
"ownerText": "Dono da Equipe",
|
||||
"addedText": "Adicionado",
|
||||
"updatedText": "Atualizado",
|
||||
"noResultFound": "Digite um endereço de email e pressione enter..."
|
||||
"noResultFound": "Digite um endereço de email e pressione enter...",
|
||||
"jobTitlesFetchError": "Falha ao buscar cargos",
|
||||
"invitationResent": "Convite reenviado com sucesso!"
|
||||
}
|
||||
|
||||
16
worklenz-frontend/public/locales/pt/settings/teams.json
Normal file
16
worklenz-frontend/public/locales/pt/settings/teams.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"title": "Equipes",
|
||||
"team": "Equipe",
|
||||
"teams": "Equipes",
|
||||
"name": "Nome",
|
||||
"created": "Criado",
|
||||
"ownsBy": "Pertence a",
|
||||
"edit": "Editar",
|
||||
"editTeam": "Editar Equipe",
|
||||
"pinTooltip": "Clique para fixar isso no menu principal",
|
||||
"editTeamName": "Editar Nome da Equipe",
|
||||
"updateName": "Atualizar Nome",
|
||||
"namePlaceholder": "Nome",
|
||||
"nameRequired": "Por favor digite um Nome",
|
||||
"updateFailed": "Falha na alteração do nome da equipe!"
|
||||
}
|
||||
@@ -68,6 +68,13 @@
|
||||
"dueDatePlaceholder": "Data de vencimento",
|
||||
"startDatePlaceholder": "Data de início",
|
||||
|
||||
"emptyStates": {
|
||||
"noTaskGroups": "Nenhum grupo de tarefas encontrado",
|
||||
"noTaskGroupsDescription": "As tarefas aparecerão aqui quando forem criadas ou quando filtros forem aplicados.",
|
||||
"errorPrefix": "Erro:",
|
||||
"dragTaskFallback": "Tarefa"
|
||||
},
|
||||
|
||||
"customColumns": {
|
||||
"addCustomColumn": "Adicionar uma coluna personalizada",
|
||||
"customColumnHeader": "Coluna Personalizada",
|
||||
|
||||
14
worklenz-frontend/public/locales/zh/project-view.json
Normal file
14
worklenz-frontend/public/locales/zh/project-view.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"taskList": "任务列表",
|
||||
"board": "看板",
|
||||
"insights": "数据洞察",
|
||||
"files": "文件",
|
||||
"members": "成员",
|
||||
"updates": "动态更新",
|
||||
"projectView": "项目视图",
|
||||
"loading": "正在加载项目...",
|
||||
"error": "加载项目时出错",
|
||||
"pinnedTab": "已固定为默认标签页",
|
||||
"pinTab": "固定为默认标签页",
|
||||
"unpinTab": "取消固定默认标签页"
|
||||
}
|
||||
@@ -1,13 +1,29 @@
|
||||
{
|
||||
"importTasks": "导入任务",
|
||||
"createTask": "创建任务",
|
||||
"settings": "设置",
|
||||
"subscribe": "订阅",
|
||||
"unsubscribe": "取消订阅",
|
||||
"deleteProject": "删除项目",
|
||||
"startDate": "开始日期",
|
||||
"endDate": "结束日期",
|
||||
"projectSettings": "项目设置",
|
||||
"projectSummary": "项目摘要",
|
||||
"receiveProjectSummary": "每晚接收项目摘要。"
|
||||
"importTasks": "导入任务",
|
||||
"importTask": "导入任务",
|
||||
"createTask": "创建任务",
|
||||
"settings": "设置",
|
||||
"subscribe": "订阅",
|
||||
"unsubscribe": "取消订阅",
|
||||
"deleteProject": "删除项目",
|
||||
"startDate": "开始日期",
|
||||
"endDate": "结束日期",
|
||||
"projectSettings": "项目设置",
|
||||
"projectSummary": "项目摘要",
|
||||
"receiveProjectSummary": "每晚接收项目摘要。",
|
||||
"refreshProject": "刷新项目",
|
||||
"saveAsTemplate": "保存为模板",
|
||||
"invite": "邀请",
|
||||
"subscribeTooltip": "订阅项目通知",
|
||||
"unsubscribeTooltip": "取消订阅项目通知",
|
||||
"refreshTooltip": "刷新项目数据",
|
||||
"settingsTooltip": "打开项目设置",
|
||||
"saveAsTemplateTooltip": "将此项目保存为模板",
|
||||
"inviteTooltip": "邀请团队成员加入此项目",
|
||||
"createTaskTooltip": "创建新任务",
|
||||
"importTaskTooltip": "从模板导入任务",
|
||||
"navigateBackTooltip": "返回项目列表",
|
||||
"projectStatusTooltip": "项目状态",
|
||||
"projectDatesInfo": "项目时间安排信息",
|
||||
"projectCategoryTooltip": "项目类别"
|
||||
}
|
||||
@@ -9,5 +9,6 @@
|
||||
"saveChanges": "保存更改",
|
||||
"profileJoinedText": "一个月前加入",
|
||||
"profileLastUpdatedText": "一个月前更新",
|
||||
"avatarTooltip": "点击上传头像"
|
||||
"avatarTooltip": "点击上传头像",
|
||||
"title": "个人资料设置"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"profile": "个人资料",
|
||||
"appearance": "外观",
|
||||
"notifications": "通知",
|
||||
"clients": "客户",
|
||||
"job-titles": "职位",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"title": "团队成员",
|
||||
"nameColumn": "名称",
|
||||
"projectsColumn": "项目",
|
||||
"emailColumn": "电子邮件",
|
||||
@@ -40,5 +41,7 @@
|
||||
"ownerText": "团队所有者",
|
||||
"addedText": "已添加",
|
||||
"updatedText": "已更新",
|
||||
"noResultFound": "输入电子邮件地址并按回车键..."
|
||||
"noResultFound": "输入电子邮件地址并按回车键...",
|
||||
"jobTitlesFetchError": "获取职位失败",
|
||||
"invitationResent": "邀请重新发送成功!"
|
||||
}
|
||||
16
worklenz-frontend/public/locales/zh/settings/teams.json
Normal file
16
worklenz-frontend/public/locales/zh/settings/teams.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"title": "团队",
|
||||
"team": "团队",
|
||||
"teams": "团队",
|
||||
"name": "名称",
|
||||
"created": "创建时间",
|
||||
"ownsBy": "所有者",
|
||||
"edit": "编辑",
|
||||
"editTeam": "编辑团队",
|
||||
"pinTooltip": "点击将此项固定到主菜单",
|
||||
"editTeamName": "编辑团队名称",
|
||||
"updateName": "更新名称",
|
||||
"namePlaceholder": "名称",
|
||||
"nameRequired": "请输入名称",
|
||||
"updateFailed": "团队名称更改失败!"
|
||||
}
|
||||
@@ -50,5 +50,17 @@
|
||||
"selectCategory": "选择类别",
|
||||
"pleaseEnterAName": "请输入名称",
|
||||
"pleaseSelectACategory": "请选择类别",
|
||||
"create": "创建"
|
||||
"create": "创建",
|
||||
"searchTasks": "搜索任务...",
|
||||
"searchPlaceholder": "搜索...",
|
||||
"fieldsText": "字段",
|
||||
"loadingFilters": "加载筛选器...",
|
||||
"noOptionsFound": "未找到选项",
|
||||
"filtersActive": "个筛选器已激活",
|
||||
"filterActive": "个筛选器已激活",
|
||||
"clearAll": "清除全部",
|
||||
"clearing": "清除中...",
|
||||
"cancel": "取消",
|
||||
"search": "搜索",
|
||||
"groupedBy": "分组依据"
|
||||
}
|
||||
@@ -36,6 +36,7 @@
|
||||
"addTaskText": "+ 添加任务",
|
||||
"addSubTaskText": "+ 添加子任务",
|
||||
"addTaskInputPlaceholder": "输入任务并按回车键",
|
||||
"noTasksInGroup": "此组中没有任务",
|
||||
"openButton": "打开",
|
||||
"okButton": "确定",
|
||||
"noLabelsFound": "未找到标签",
|
||||
@@ -52,5 +53,74 @@
|
||||
"convertToTask": "转换为任务",
|
||||
"delete": "删除",
|
||||
"searchByNameInputPlaceholder": "按名称搜索"
|
||||
},
|
||||
"setDueDate": "设置截止日期",
|
||||
"setStartDate": "设置开始日期",
|
||||
"clearDueDate": "清除截止日期",
|
||||
"clearStartDate": "清除开始日期",
|
||||
"dueDatePlaceholder": "截止日期",
|
||||
"startDatePlaceholder": "开始日期",
|
||||
|
||||
"emptyStates": {
|
||||
"noTaskGroups": "未找到任务组",
|
||||
"noTaskGroupsDescription": "创建任务或应用筛选器后,任务将显示在此处。",
|
||||
"errorPrefix": "错误:",
|
||||
"dragTaskFallback": "任务"
|
||||
},
|
||||
|
||||
"customColumns": {
|
||||
"addCustomColumn": "添加自定义列",
|
||||
"customColumnHeader": "自定义列",
|
||||
"customColumnSettings": "自定义列设置",
|
||||
"noCustomValue": "无值",
|
||||
"peopleField": "人员字段",
|
||||
"noDate": "无日期",
|
||||
"unsupportedField": "不支持的字段类型",
|
||||
|
||||
"modal": {
|
||||
"addFieldTitle": "添加字段",
|
||||
"editFieldTitle": "编辑字段",
|
||||
"fieldTitle": "字段标题",
|
||||
"fieldTitleRequired": "字段标题为必填项",
|
||||
"columnTitlePlaceholder": "列标题",
|
||||
"type": "类型",
|
||||
"deleteConfirmTitle": "确定要删除此自定义列吗?",
|
||||
"deleteConfirmDescription": "此操作无法撤销。与此列关联的所有数据将被永久删除。",
|
||||
"deleteButton": "删除",
|
||||
"cancelButton": "取消",
|
||||
"createButton": "创建",
|
||||
"updateButton": "更新",
|
||||
"createSuccessMessage": "自定义列创建成功",
|
||||
"updateSuccessMessage": "自定义列更新成功",
|
||||
"deleteSuccessMessage": "自定义列删除成功",
|
||||
"deleteErrorMessage": "删除自定义列失败",
|
||||
"createErrorMessage": "创建自定义列失败",
|
||||
"updateErrorMessage": "更新自定义列失败"
|
||||
},
|
||||
|
||||
"fieldTypes": {
|
||||
"people": "人员",
|
||||
"number": "数字",
|
||||
"date": "日期",
|
||||
"selection": "选择",
|
||||
"checkbox": "复选框",
|
||||
"labels": "标签",
|
||||
"key": "键",
|
||||
"formula": "公式"
|
||||
}
|
||||
},
|
||||
|
||||
"indicators": {
|
||||
"tooltips": {
|
||||
"subtasks": "{{count}} 个子任务",
|
||||
"subtasks_plural": "{{count}} 个子任务",
|
||||
"comments": "{{count}} 条评论",
|
||||
"comments_plural": "{{count}} 条评论",
|
||||
"attachments": "{{count}} 个附件",
|
||||
"attachments_plural": "{{count}} 个附件",
|
||||
"subscribers": "任务有订阅者",
|
||||
"dependencies": "任务有依赖项",
|
||||
"recurring": "重复任务"
|
||||
}
|
||||
}
|
||||
}
|
||||
35
worklenz-frontend/public/locales/zh/task-management.json
Normal file
35
worklenz-frontend/public/locales/zh/task-management.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"noTasksInGroup": "此组中没有任务",
|
||||
"noTasksInGroupDescription": "添加任务开始使用",
|
||||
"addFirstTask": "添加你的第一个任务",
|
||||
"openTask": "打开",
|
||||
"subtask": "子任务",
|
||||
"subtasks": "子任务",
|
||||
"comment": "评论",
|
||||
"comments": "评论",
|
||||
"attachment": "附件",
|
||||
"attachments": "附件",
|
||||
"enterSubtaskName": "输入子任务名称...",
|
||||
"add": "添加",
|
||||
"cancel": "取消",
|
||||
"renameGroup": "重命名组",
|
||||
"renameStatus": "重命名状态",
|
||||
"renamePhase": "重命名阶段",
|
||||
"changeCategory": "更改类别",
|
||||
"clickToEditGroupName": "点击编辑组名称",
|
||||
"enterGroupName": "输入组名称",
|
||||
|
||||
"indicators": {
|
||||
"tooltips": {
|
||||
"subtasks": "{{count}} 个子任务",
|
||||
"subtasks_plural": "{{count}} 个子任务",
|
||||
"comments": "{{count}} 条评论",
|
||||
"comments_plural": "{{count}} 条评论",
|
||||
"attachments": "{{count}} 个附件",
|
||||
"attachments_plural": "{{count}} 个附件",
|
||||
"subscribers": "任务有订阅者",
|
||||
"dependencies": "任务有依赖项",
|
||||
"recurring": "重复任务"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,15 @@ import { PushpinFilled, PushpinOutlined } from '@ant-design/icons';
|
||||
import { colors } from '../styles/colors';
|
||||
import { navRoutes, NavRoutesType } from '../features/navbar/navRoutes';
|
||||
|
||||
// Props type for the component
|
||||
type PinRouteToNavbarButtonProps = {
|
||||
name: string;
|
||||
path: string;
|
||||
adminOnly?: boolean;
|
||||
};
|
||||
|
||||
// this component pin the given path to navbar
|
||||
const PinRouteToNavbarButton = ({ name, path }: NavRoutesType) => {
|
||||
const PinRouteToNavbarButton = ({ name, path, adminOnly = false }: PinRouteToNavbarButtonProps) => {
|
||||
const navRoutesList: NavRoutesType[] = getJSONFromLocalStorage('navRoutes') || navRoutes;
|
||||
|
||||
const [isPinned, setIsPinned] = useState(
|
||||
@@ -18,7 +25,7 @@ const PinRouteToNavbarButton = ({ name, path }: NavRoutesType) => {
|
||||
const handlePinToNavbar = (name: string, path: string) => {
|
||||
let newNavRoutesList;
|
||||
|
||||
const route: NavRoutesType = { name, path };
|
||||
const route: NavRoutesType = { name, path, adminOnly };
|
||||
|
||||
if (isPinned) {
|
||||
newNavRoutesList = navRoutesList.filter(item => item.name !== route.name);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Divider, Form, Input, message, Modal, Typography } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||
import { editTeamName, fetchTeams } from '@/features/teams/teamSlice';
|
||||
import { ITeamGetResponse } from '@/types/teams/team.type';
|
||||
@@ -11,6 +12,7 @@ interface EditTeamNameModalProps {
|
||||
}
|
||||
|
||||
const EditTeamNameModal = ({ team, isModalOpen, onCancel }: EditTeamNameModalProps) => {
|
||||
const { t } = useTranslation('settings/teams');
|
||||
const dispatch = useAppDispatch();
|
||||
const [form] = Form.useForm();
|
||||
const [updating, setUpdating] = useState(false);
|
||||
@@ -33,7 +35,7 @@ const EditTeamNameModal = ({ team, isModalOpen, onCancel }: EditTeamNameModalPro
|
||||
}
|
||||
setUpdating(false);
|
||||
} catch (error) {
|
||||
message.error('Team name change failed!');
|
||||
message.error(t('updateFailed'));
|
||||
} finally {
|
||||
setUpdating(false);
|
||||
}
|
||||
@@ -49,13 +51,13 @@ const EditTeamNameModal = ({ team, isModalOpen, onCancel }: EditTeamNameModalPro
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
Edit Team Name
|
||||
{t('editTeamName')}
|
||||
<Divider />
|
||||
</Typography.Text>
|
||||
}
|
||||
open={isModalOpen}
|
||||
onOk={form.submit}
|
||||
okText="Update Name"
|
||||
okText={t('updateName')}
|
||||
onCancel={() => {
|
||||
onCancel();
|
||||
setUpdating(false);
|
||||
@@ -67,15 +69,15 @@ const EditTeamNameModal = ({ team, isModalOpen, onCancel }: EditTeamNameModalPro
|
||||
<Form form={form} layout="vertical" onFinish={handleFormSubmit}>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="Name"
|
||||
label={t('name')}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Please enter a Name',
|
||||
message: t('nameRequired'),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="Name" />
|
||||
<Input placeholder={t('namePlaceholder')} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -65,7 +65,7 @@ const UpdateMemberDrawer = ({ selectedMemberId, onRoleUpdate }: UpdateMemberDraw
|
||||
setJobTitles(res.body.data || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching job titles:', error);
|
||||
logger.error('Error fetching job titles:', error);
|
||||
message.error(t('jobTitlesFetchError'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -154,7 +154,7 @@ const TaskDrawer = () => {
|
||||
onClick={handleAddTimeLog}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
Add new time log
|
||||
{t('taskTimeLogTab.addTimeLog')}
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -519,7 +519,7 @@ const TaskListV2: React.FC = () => {
|
||||
|
||||
// Loading and error states
|
||||
if (loading || loadingColumns) return <Skeleton active />;
|
||||
if (error) return <div>Error: {error}</div>;
|
||||
if (error) return <div>{t('emptyStates.errorPrefix')} {error}</div>;
|
||||
|
||||
// Show message when no data
|
||||
if (groups.length === 0 && !loading) {
|
||||
@@ -531,10 +531,10 @@ const TaskListV2: React.FC = () => {
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-medium text-gray-900 dark:text-white mb-2">
|
||||
No task groups found
|
||||
{t('emptyStates.noTaskGroups')}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Tasks will appear here when they are created or when filters are applied.
|
||||
{t('emptyStates.noTaskGroupsDescription')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -623,7 +623,7 @@ const TaskListV2: React.FC = () => {
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{allTasks.find(task => task.id === activeId)?.name ||
|
||||
allTasks.find(task => task.id === activeId)?.title ||
|
||||
'Task'}
|
||||
t('emptyStates.dragTaskFallback')}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{allTasks.find(task => task.id === activeId)?.task_key}
|
||||
|
||||
@@ -213,7 +213,7 @@ const useFilterData = (position: 'board' | 'list'): FilterSection[] => {
|
||||
return [
|
||||
{
|
||||
id: 'priority',
|
||||
label: 'Priority',
|
||||
label: t('priorityText'),
|
||||
options: filterData.priorities.map((p: any) => ({
|
||||
value: p.id,
|
||||
label: p.name,
|
||||
@@ -288,7 +288,7 @@ const useFilterData = (position: 'board' | 'list'): FilterSection[] => {
|
||||
return [
|
||||
{
|
||||
id: 'priority',
|
||||
label: 'Priority',
|
||||
label: t('priorityText'),
|
||||
options: filterData.priorities.map((p: any) => ({
|
||||
value: p.id,
|
||||
label: p.name,
|
||||
@@ -719,7 +719,34 @@ const FieldsDropdown: React.FC<{ themeClasses: any; isDarkMode: boolean }> = ({
|
||||
isDarkMode,
|
||||
}) => {
|
||||
const { t } = useTranslation('task-list-filters');
|
||||
const { t: tTable } = useTranslation('task-list-table');
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// Helper function to get translated field label using existing task-list-table translations
|
||||
const getFieldLabel = useCallback((fieldKey: string) => {
|
||||
const keyMappings: Record<string, string> = {
|
||||
'KEY': 'keyColumn',
|
||||
'DESCRIPTION': 'descriptionColumn',
|
||||
'PROGRESS': 'progressColumn',
|
||||
'ASSIGNEES': 'assigneesColumn',
|
||||
'LABELS': 'labelsColumn',
|
||||
'PHASE': 'phaseColumn',
|
||||
'STATUS': 'statusColumn',
|
||||
'PRIORITY': 'priorityColumn',
|
||||
'TIME_TRACKING': 'timeTrackingColumn',
|
||||
'ESTIMATION': 'estimationColumn',
|
||||
'START_DATE': 'startDateColumn',
|
||||
'DUE_DATE': 'dueDateColumn',
|
||||
'DUE_TIME': 'dueTimeColumn',
|
||||
'COMPLETED_DATE': 'completedDateColumn',
|
||||
'CREATED_DATE': 'createdDateColumn',
|
||||
'LAST_UPDATED': 'lastUpdatedColumn',
|
||||
'REPORTER': 'reporterColumn',
|
||||
};
|
||||
|
||||
const translationKey = keyMappings[fieldKey];
|
||||
return translationKey ? tTable(translationKey) : fieldKey;
|
||||
}, [tTable]);
|
||||
const fieldsRaw = useSelector((state: RootState) => state.taskManagementFields);
|
||||
const columns = useSelector(selectColumns);
|
||||
const projectId = useAppSelector(state => state.projectReducer.projectId);
|
||||
@@ -857,7 +884,7 @@ const FieldsDropdown: React.FC<{ themeClasses: any; isDarkMode: boolean }> = ({
|
||||
|
||||
{/* Label and Count */}
|
||||
<div className="flex-1 flex items-center justify-between">
|
||||
<span className="truncate">{field.label}</span>
|
||||
<span className="truncate">{getFieldLabel(field.key)}</span>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { ReactNode, Suspense } from 'react';
|
||||
import { InlineSuspenseFallback } from '@/components/suspense-fallback/suspense-fallback';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
// Import core components synchronously to avoid suspense in main tabs
|
||||
import ProjectViewEnhancedBoard from '@/pages/projects/projectView/enhancedBoard/project-view-enhanced-board';
|
||||
@@ -28,26 +29,31 @@ type TabItems = {
|
||||
element: ReactNode;
|
||||
};
|
||||
|
||||
// Function to get translated labels
|
||||
const getTabLabel = (key: string): string => {
|
||||
return i18n.t(`project-view:${key}`);
|
||||
};
|
||||
|
||||
// settings all element items use for tabs
|
||||
export const tabItems: TabItems[] = [
|
||||
{
|
||||
index: 0,
|
||||
key: 'tasks-list',
|
||||
label: 'Task List',
|
||||
label: getTabLabel('taskList'),
|
||||
isPinned: true,
|
||||
element: React.createElement(TaskListV2),
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
key: 'board',
|
||||
label: 'Board',
|
||||
label: getTabLabel('board'),
|
||||
isPinned: true,
|
||||
element: React.createElement(ProjectViewEnhancedBoard),
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
key: 'project-insights-member-overview',
|
||||
label: 'Insights',
|
||||
label: getTabLabel('insights'),
|
||||
element: React.createElement(
|
||||
Suspense,
|
||||
{ fallback: React.createElement(InlineSuspenseFallback) },
|
||||
@@ -57,7 +63,7 @@ export const tabItems: TabItems[] = [
|
||||
{
|
||||
index: 3,
|
||||
key: 'all-attachments',
|
||||
label: 'Files',
|
||||
label: getTabLabel('files'),
|
||||
element: React.createElement(
|
||||
Suspense,
|
||||
{ fallback: React.createElement(InlineSuspenseFallback) },
|
||||
@@ -67,7 +73,7 @@ export const tabItems: TabItems[] = [
|
||||
{
|
||||
index: 4,
|
||||
key: 'members',
|
||||
label: 'Members',
|
||||
label: getTabLabel('members'),
|
||||
element: React.createElement(
|
||||
Suspense,
|
||||
{ fallback: React.createElement(InlineSuspenseFallback) },
|
||||
@@ -77,7 +83,7 @@ export const tabItems: TabItems[] = [
|
||||
{
|
||||
index: 5,
|
||||
key: 'updates',
|
||||
label: 'Updates',
|
||||
label: getTabLabel('updates'),
|
||||
element: React.createElement(
|
||||
Suspense,
|
||||
{ fallback: React.createElement(InlineSuspenseFallback) },
|
||||
@@ -85,3 +91,29 @@ export const tabItems: TabItems[] = [
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// Function to update tab labels when language changes
|
||||
export const updateTabLabels = () => {
|
||||
tabItems.forEach(item => {
|
||||
switch (item.key) {
|
||||
case 'tasks-list':
|
||||
item.label = getTabLabel('taskList');
|
||||
break;
|
||||
case 'board':
|
||||
item.label = getTabLabel('board');
|
||||
break;
|
||||
case 'project-insights-member-overview':
|
||||
item.label = getTabLabel('insights');
|
||||
break;
|
||||
case 'all-attachments':
|
||||
item.label = getTabLabel('files');
|
||||
break;
|
||||
case 'members':
|
||||
item.label = getTabLabel('members');
|
||||
break;
|
||||
case 'updates':
|
||||
item.label = getTabLabel('updates');
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -268,7 +268,7 @@ const ProjectViewHeader = memo(() => {
|
||||
{
|
||||
key: 'import',
|
||||
label: (
|
||||
<div style={{ width: '100%', margin: 0, padding: 0 }} onClick={handleImportTaskTemplate}>
|
||||
<div style={{ width: '100%', margin: 0, padding: 0 }} onClick={handleImportTaskTemplate} title={t('importTaskTooltip')}>
|
||||
<ImportOutlined /> {t('importTask')}
|
||||
</div>
|
||||
),
|
||||
@@ -285,19 +285,21 @@ const ProjectViewHeader = memo(() => {
|
||||
|
||||
if (selectedProject.category_id) {
|
||||
elements.push(
|
||||
<Tag
|
||||
key="category"
|
||||
color={colors.vibrantOrange}
|
||||
style={{ borderRadius: 24, paddingInline: 8, margin: 0 }}
|
||||
>
|
||||
{selectedProject.category_name}
|
||||
</Tag>
|
||||
<Tooltip key="category-tooltip" title={`${t('projectCategoryTooltip')}: ${selectedProject.category_name}`}>
|
||||
<Tag
|
||||
key="category"
|
||||
color={colors.vibrantOrange}
|
||||
style={{ borderRadius: 24, paddingInline: 8, margin: 0 }}
|
||||
>
|
||||
{selectedProject.category_name}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedProject.status) {
|
||||
elements.push(
|
||||
<Tooltip key="status" title={selectedProject.status}>
|
||||
<Tooltip key="status" title={`${t('projectStatusTooltip')}: ${selectedProject.status}`}>
|
||||
<ProjectStatusIcon
|
||||
iconName={selectedProject.status_icon || ''}
|
||||
color={selectedProject.status_color || ''}
|
||||
@@ -309,6 +311,8 @@ const ProjectViewHeader = memo(() => {
|
||||
if (selectedProject.start_date || selectedProject.end_date) {
|
||||
const tooltipContent = (
|
||||
<Typography.Text style={{ color: colors.white }}>
|
||||
{t('projectDatesInfo')}
|
||||
<br />
|
||||
{selectedProject.start_date &&
|
||||
`${t('startDate')}: ${formatDate(new Date(selectedProject.start_date))}`}
|
||||
{selectedProject.end_date && (
|
||||
@@ -348,7 +352,7 @@ const ProjectViewHeader = memo(() => {
|
||||
|
||||
// Refresh button
|
||||
actions.push(
|
||||
<Tooltip key="refresh" title={t('refreshProject')}>
|
||||
<Tooltip key="refresh" title={t('refreshTooltip')}>
|
||||
<Button
|
||||
shape="circle"
|
||||
icon={<SyncOutlined spin={loadingGroups} />}
|
||||
@@ -360,7 +364,7 @@ const ProjectViewHeader = memo(() => {
|
||||
// Save as template (owner/admin only)
|
||||
if (isOwnerOrAdmin) {
|
||||
actions.push(
|
||||
<Tooltip key="template" title={t('saveAsTemplate')}>
|
||||
<Tooltip key="template" title={t('saveAsTemplateTooltip')}>
|
||||
<Button shape="circle" icon={<SaveOutlined />} onClick={handleSaveAsTemplate} />
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -368,14 +372,14 @@ const ProjectViewHeader = memo(() => {
|
||||
|
||||
// Settings button
|
||||
actions.push(
|
||||
<Tooltip key="settings" title={t('projectSettings')}>
|
||||
<Tooltip key="settings" title={t('settingsTooltip')}>
|
||||
<Button shape="circle" icon={<SettingOutlined />} onClick={handleSettingsClick} />
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
// Subscribe button
|
||||
actions.push(
|
||||
<Tooltip key="subscribe" title={t('subscribe')}>
|
||||
<Tooltip key="subscribe" title={selectedProject?.subscribed ? t('unsubscribeTooltip') : t('subscribeTooltip')}>
|
||||
<Button
|
||||
shape="round"
|
||||
loading={subscriptionLoading}
|
||||
@@ -390,38 +394,44 @@ const ProjectViewHeader = memo(() => {
|
||||
// Invite button (owner/admin/project manager only)
|
||||
if (isOwnerOrAdmin || isProjectManager) {
|
||||
actions.push(
|
||||
<Button key="invite" type="primary" icon={<UsergroupAddOutlined />} onClick={handleInvite}>
|
||||
{t('invite')}
|
||||
</Button>
|
||||
<Tooltip key="invite-tooltip" title={t('inviteTooltip')}>
|
||||
<Button key="invite" type="primary" icon={<UsergroupAddOutlined />} onClick={handleInvite}>
|
||||
{t('invite')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
// Create task button
|
||||
if (isOwnerOrAdmin) {
|
||||
actions.push(
|
||||
<Dropdown.Button
|
||||
key="create-task-dropdown"
|
||||
loading={creatingTask}
|
||||
type="primary"
|
||||
icon={<DownOutlined />}
|
||||
menu={{ items: dropdownItems }}
|
||||
trigger={['click']}
|
||||
onClick={handleCreateTask}
|
||||
>
|
||||
<EditOutlined /> {t('createTask')}
|
||||
</Dropdown.Button>
|
||||
<Tooltip key="create-task-tooltip" title={t('createTaskTooltip')}>
|
||||
<Dropdown.Button
|
||||
key="create-task-dropdown"
|
||||
loading={creatingTask}
|
||||
type="primary"
|
||||
icon={<DownOutlined />}
|
||||
menu={{ items: dropdownItems }}
|
||||
trigger={['click']}
|
||||
onClick={handleCreateTask}
|
||||
>
|
||||
<EditOutlined /> {t('createTask')}
|
||||
</Dropdown.Button>
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
actions.push(
|
||||
<Button
|
||||
key="create-task"
|
||||
loading={creatingTask}
|
||||
type="primary"
|
||||
icon={<EditOutlined />}
|
||||
onClick={handleCreateTask}
|
||||
>
|
||||
{t('createTask')}
|
||||
</Button>
|
||||
<Tooltip key="create-task-tooltip" title={t('createTaskTooltip')}>
|
||||
<Button
|
||||
key="create-task"
|
||||
loading={creatingTask}
|
||||
type="primary"
|
||||
icon={<EditOutlined />}
|
||||
onClick={handleCreateTask}
|
||||
>
|
||||
{t('createTask')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -451,14 +461,16 @@ const ProjectViewHeader = memo(() => {
|
||||
const pageHeaderTitle = useMemo(
|
||||
() => (
|
||||
<Flex gap={8} align="center">
|
||||
<ArrowLeftOutlined style={{ fontSize: 16 }} onClick={handleNavigateToProjects} />
|
||||
<Tooltip title={t('navigateBackTooltip')}>
|
||||
<ArrowLeftOutlined style={{ fontSize: 16, cursor: 'pointer' }} onClick={handleNavigateToProjects} />
|
||||
</Tooltip>
|
||||
<Typography.Title level={4} style={{ marginBlockEnd: 0, marginInlineStart: 12 }}>
|
||||
{selectedProject?.name}
|
||||
</Typography.Title>
|
||||
{projectAttributes}
|
||||
</Flex>
|
||||
),
|
||||
[handleNavigateToProjects, selectedProject?.name, projectAttributes]
|
||||
[handleNavigateToProjects, selectedProject?.name, projectAttributes, t]
|
||||
);
|
||||
|
||||
// Memoized page header styles
|
||||
|
||||
@@ -32,7 +32,7 @@ import { resetSelection } from '@/features/task-management/selection.slice';
|
||||
import { resetFields } from '@/features/task-management/taskListFields.slice';
|
||||
import { fetchLabels } from '@/features/taskAttributes/taskLabelSlice';
|
||||
import { deselectAll } from '@/features/projects/bulkActions/bulkActionSlice';
|
||||
import { tabItems } from '@/lib/project/project-view-constants';
|
||||
import { tabItems, updateTabLabels } from '@/lib/project/project-view-constants';
|
||||
import {
|
||||
setSelectedTaskId,
|
||||
setShowTaskDrawer,
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
import { resetState as resetEnhancedKanbanState } from '@/features/enhanced-kanban/enhanced-kanban.slice';
|
||||
import { setProjectId as setInsightsProjectId } from '@/features/projects/insights/project-insights.slice';
|
||||
import { SuspenseFallback } from '@/components/suspense-fallback/suspense-fallback';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
// Import critical components synchronously to avoid suspense interruptions
|
||||
import TaskDrawer from '@components/task-drawer/task-drawer';
|
||||
@@ -63,13 +64,14 @@ const ProjectView = React.memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { projectId } = useParams();
|
||||
const { t } = useTranslation('project-view');
|
||||
|
||||
// Memoized selectors to prevent unnecessary re-renders
|
||||
const selectedProject = useAppSelector(state => state.projectReducer.project);
|
||||
const projectLoading = useAppSelector(state => state.projectReducer.projectLoading);
|
||||
|
||||
// Optimize document title updates
|
||||
useDocumentTitle(selectedProject?.name || 'Project View');
|
||||
useDocumentTitle(selectedProject?.name || t('projectView'));
|
||||
|
||||
// Memoize URL params to prevent unnecessary state updates
|
||||
const urlParams = useMemo(
|
||||
@@ -174,6 +176,11 @@ const ProjectView = React.memo(() => {
|
||||
setIsInitialized(false);
|
||||
}, [projectId]);
|
||||
|
||||
// Update tab labels when language changes
|
||||
useEffect(() => {
|
||||
updateTabLabels();
|
||||
}, [t]);
|
||||
|
||||
// Effect for handling task drawer opening from URL params
|
||||
useEffect(() => {
|
||||
if (taskid && isInitialized) {
|
||||
@@ -287,6 +294,7 @@ const ProjectView = React.memo(() => {
|
||||
e.stopPropagation();
|
||||
pinToDefaultTab(item.key);
|
||||
}}
|
||||
title={item.key === pinnedTab ? t('unpinTab') : t('pinTab')}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
)}
|
||||
@@ -296,7 +304,7 @@ const ProjectView = React.memo(() => {
|
||||
}));
|
||||
|
||||
return menuItems;
|
||||
}, [pinnedTab, pinToDefaultTab]);
|
||||
}, [pinnedTab, pinToDefaultTab, t]);
|
||||
|
||||
// Optimized secondary components loading with better UX
|
||||
const [shouldLoadSecondaryComponents, setShouldLoadSecondaryComponents] = useState(false);
|
||||
|
||||
@@ -24,7 +24,7 @@ const SettingSidebar: React.FC = () => {
|
||||
const items: Required<MenuProps>['items'] = accessibleSettings
|
||||
.map(item => {
|
||||
if (currentSession?.is_google && item.key === 'change-password') {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
key: item.key,
|
||||
@@ -39,7 +39,7 @@ const SettingSidebar: React.FC = () => {
|
||||
),
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
.filter((item): item is NonNullable<typeof item> => item !== null);
|
||||
|
||||
return (
|
||||
<ConfigProvider
|
||||
|
||||
@@ -24,6 +24,7 @@ import { useEffect, useState, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
||||
import { useSocket } from '@/socket/socketContext';
|
||||
import { SocketEvents } from '@/shared/socket-events';
|
||||
import UpdateMemberDrawer from '@/components/settings/update-member-drawer';
|
||||
@@ -43,6 +44,8 @@ const TeamMembersSettings = () => {
|
||||
const { socket } = useSocket();
|
||||
const refreshTeamMembers = useAppSelector(state => state.memberReducer.refreshTeamMembers); // Listen to refresh flag
|
||||
|
||||
useDocumentTitle(t('title') || 'Team Members');
|
||||
|
||||
const [model, setModel] = useState<ITeamMembersViewModel>({ total: 0, data: [] });
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { durationDateFormat } from '@utils/durationDateFormat';
|
||||
import { EditOutlined } from '@ant-design/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import EditTeamModal from '@/components/settings/edit-team-name-modal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { fetchTeams } from '@features/teams/teamSlice';
|
||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||
@@ -12,7 +13,8 @@ import { useDocumentTitle } from '@/hooks/useDoumentTItle';
|
||||
import { ITeamGetResponse } from '@/types/teams/team.type';
|
||||
|
||||
const TeamsSettings = () => {
|
||||
useDocumentTitle('Teams');
|
||||
const { t } = useTranslation('settings/teams');
|
||||
useDocumentTitle(t('title'));
|
||||
|
||||
const [selectedTeam, setSelectedTeam] = useState<ITeamGetResponse | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
@@ -26,27 +28,27 @@ const TeamsSettings = () => {
|
||||
const columns: TableProps['columns'] = [
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Name',
|
||||
title: t('name'),
|
||||
render: (record: ITeamGetResponse) => <Typography.Text>{record.name}</Typography.Text>,
|
||||
},
|
||||
|
||||
{
|
||||
key: 'created',
|
||||
title: 'Created',
|
||||
title: t('created'),
|
||||
render: (record: ITeamGetResponse) => (
|
||||
<Typography.Text>{durationDateFormat(record.created_at)}</Typography.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'ownsBy',
|
||||
title: 'Owns By',
|
||||
title: t('ownsBy'),
|
||||
render: (record: ITeamGetResponse) => <Typography.Text>{record.owns_by}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
key: 'actionBtns',
|
||||
width: 60,
|
||||
render: (record: ITeamGetResponse) => (
|
||||
<Tooltip title="Edit" trigger={'hover'}>
|
||||
<Tooltip title={t('edit')} trigger={'hover'}>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<EditOutlined />}
|
||||
@@ -69,13 +71,12 @@ const TeamsSettings = () => {
|
||||
<div style={{ width: '100%' }}>
|
||||
<Flex align="center" justify="space-between" style={{ marginBlockEnd: 24 }}>
|
||||
<Typography.Title level={4} style={{ marginBlockEnd: 0 }}>
|
||||
{teamsList.length} Team
|
||||
{teamsList.length !== 1 && 's'}
|
||||
{teamsList.length} {teamsList.length === 1 ? t('team') : t('teams')}
|
||||
</Typography.Title>
|
||||
|
||||
<Tooltip title={'Click to pin this into the main menu'} trigger={'hover'}>
|
||||
<Tooltip title={t('pinTooltip')} trigger={'hover'}>
|
||||
{/* this button pin this route to navbar */}
|
||||
<PinRouteToNavbarButton name="teams" path="/worklenz/settings/teams" />
|
||||
<PinRouteToNavbarButton name="teams" path="/worklenz/settings/teams" adminOnly={true} />
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
|
||||
|
||||
@@ -4,9 +4,56 @@ import { getLanguageFromLocalStorage } from './language-utils';
|
||||
export const currentDateString = (): string => {
|
||||
const date = dayjs();
|
||||
const localeString = getLanguageFromLocalStorage();
|
||||
const locale = localeString === 'en' ? 'en' : localeString === 'es' ? 'es' : 'pt';
|
||||
|
||||
// Map language codes to dayjs locales
|
||||
let locale = 'en'; // Default to English
|
||||
switch (localeString) {
|
||||
case 'en':
|
||||
locale = 'en';
|
||||
break;
|
||||
case 'es':
|
||||
locale = 'es';
|
||||
break;
|
||||
case 'pt':
|
||||
locale = 'pt';
|
||||
break;
|
||||
case 'de':
|
||||
locale = 'de';
|
||||
break;
|
||||
case 'zh_cn':
|
||||
locale = 'zh-cn';
|
||||
break;
|
||||
case 'alb':
|
||||
locale = 'sq'; // Albanian locale code for dayjs
|
||||
break;
|
||||
default:
|
||||
locale = 'en';
|
||||
}
|
||||
|
||||
// Get localized "Today is" text
|
||||
let todayText = 'Today is'; // Default English
|
||||
switch (localeString) {
|
||||
case 'en':
|
||||
todayText = 'Today is';
|
||||
break;
|
||||
case 'es':
|
||||
todayText = 'Hoy es';
|
||||
break;
|
||||
case 'pt':
|
||||
todayText = 'Hoje é';
|
||||
break;
|
||||
case 'de':
|
||||
todayText = 'Heute ist';
|
||||
break;
|
||||
case 'zh_cn':
|
||||
todayText = '今天是';
|
||||
break;
|
||||
case 'alb':
|
||||
todayText = 'Sot është';
|
||||
break;
|
||||
default:
|
||||
todayText = 'Today is';
|
||||
}
|
||||
|
||||
const todayText =
|
||||
localeString === 'en' ? 'Today is' : localeString === 'es' ? 'Hoy es' : 'Hoje é';
|
||||
return `${todayText} ${date.locale(locale).format('dddd, MMMM DD, YYYY')}`;
|
||||
};
|
||||
|
||||
@@ -49,5 +49,16 @@ export const greetingString = (name: string): string => {
|
||||
evening = '晚上好';
|
||||
}
|
||||
|
||||
return `${greetingPrefix} ${name}, ${greetingSuffix} ${greet}!`;
|
||||
// Get the localized time period based on the current time
|
||||
let localizedTimePeriod;
|
||||
if (greet === 'morning') localizedTimePeriod = morning;
|
||||
else if (greet === 'afternoon') localizedTimePeriod = afternoon;
|
||||
else localizedTimePeriod = evening;
|
||||
|
||||
// Handle Chinese language which has different structure
|
||||
if (language === 'zh_cn') {
|
||||
return `${greetingPrefix} ${name}, ${localizedTimePeriod}!`;
|
||||
}
|
||||
|
||||
return `${greetingPrefix} ${name}, ${greetingSuffix} ${localizedTimePeriod}!`;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user