diff --git a/src-tauri/src/commands/handler_list.inc b/src-tauri/src/commands/handler_list.inc index facae3a5f..7663db9c8 100644 --- a/src-tauri/src/commands/handler_list.inc +++ b/src-tauri/src/commands/handler_list.inc @@ -594,6 +594,12 @@ project_management::projects::commands::project_allocate_work_item_id, project_management::projects::commands::work_item_allocate_standalone_id, project_management::projects::commands::project_batch_delete_work_items, project_management::projects::commands::project_batch_update_work_items, +// Team Inbox read model (viewer member IDs are explicit at the IPC boundary). +project_management::team_inbox::commands::team_inbox_list_page, +project_management::team_inbox::commands::team_inbox_unread_count, +project_management::team_inbox::commands::team_inbox_mark_read, +project_management::team_inbox::commands::team_inbox_mark_all_read, +project_management::team_inbox::commands::team_inbox_mark_unread, project_management::projects::commands::project_list_routines, project_management::projects::commands::project_read_routine, project_management::projects::commands::project_upsert_routine, diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 07ec56f99..c30d9c64c 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -1,4 +1,162 @@ { + "teamInbox": { + "title": "Team-Posteingang", + "listLabel": "Liste des Team-Posteingangs", + "itemsLabel": "Einträge im Team-Posteingang", + "unreadCount": "{{count}} ungelesen", + "allRead": "Alles gelesen", + "loadMore": "Mehr laden", + "filters": { + "all": "Alle", + "mentions": "Erwähnungen", + "assigned": "Zugewiesen" + }, + "status": { + "read": "Gelesen", + "unread": "Ungelesen" + }, + "row": { + "assignedSummary": "{{status}} · {{priority}}", + "ariaLabel": "{{title}}, {{status}}" + }, + "search": { + "placeholder": "Posteingang durchsuchen", + "ariaLabel": "Team-Posteingang durchsuchen" + }, + "groups": { + "today": "Heute", + "yesterday": "Gestern", + "thisWeek": "Diese Woche", + "earlier": "Früher" + }, + "empty": { + "title": "Noch nichts hier", + "subtitle": "Erwähnungen und zugewiesene Arbeitselemente werden hier angezeigt.", + "selectTitle": "Ein Element auswählen", + "selectSubtitle": "Kommentarkontext oder Details des Arbeitselements ansehen.", + "mentions": { + "title": "Keine Erwähnungen", + "subtitle": "Wenn dich ein Teammitglied in einem Kommentar mit @ erwähnt, erscheint es hier." + }, + "assigned": { + "title": "Dir ist nichts zugewiesen", + "subtitle": "Dir zugewiesene Arbeitselemente werden hier angezeigt." + }, + "noResults": { + "title": "Keine Treffer", + "subtitle": "Keine Elemente entsprechen „{{query}}“." + } + }, + "loading": "Team-Posteingang wird geladen…", + "drop": { + "title": "Zum Erstellen eines Arbeitselements hier ablegen", + "subtitle": "Prüfe den Sitzungs-Schnappschuss und erstelle oder übergib dann das Arbeitselement.", + "processing": "Arbeitselement wird aus „{{title}}“ erstellt…", + "processingHint": "Sitzung wird gelesen und Projektmitglieder werden aufgelöst.", + "success": "Arbeitselement erstellt", + "reused": "Vorhandenes Arbeitselement aktualisiert", + "failed": "Arbeitselement konnte nicht erstellt werden", + "error": "Aus dieser Sitzung kann kein Arbeitselement erstellt werden.", + "open": "Öffnen", + "dismiss": "Verwerfen" + }, + "handoff": { + "title": "Aus Sitzung erstellen", + "createFromSession": "Team-Arbeitselement erstellen…", + "project": "Zielprojekt", + "chooseProject": "Projekt auswählen", + "recipientSelf": "{{name}} (ich)", + "chooseRecipient": "Empfänger auswählen", + "todoCount_one": "{{count}} Aufgabe", + "todoCount_other": "{{count}} Aufgaben", + "workItemTitle": "Titel des Arbeitselements", + "assignTo": "Zuweisen an", + "note": "Übergabehinweis", + "notePlaceholder": "Teile mit, was fertig ist, was offen ist und was als Nächstes passieren sollte.", + "selfHint": "Wenn du dir dies selbst zuweist, entsteht ein normales Arbeitselement ohne Übergabeanfrage.", + "submitHandoff": "Erstellen & übergeben", + "submitCreate": "Arbeitselement erstellen", + "preparing": "„{{title}}“ wird vorbereitet…", + "preparationError": { + "session_unavailable": "Diese Sitzung ist nicht mehr verfügbar. Öffne sie erneut und versuche es noch einmal.", + "project_unavailable": "Das Projekt dieser Sitzung ist nicht mehr verfügbar.", + "identity_unavailable": "Deine Identität ist kein Mitglied dieses Sitzungsprojekts.", + "no_project": "Kein geeignetes Projekt verfügbar. Erstelle oder tritt einem Projekt bei und versuche es erneut.", + "unknown": "Diese Sitzung kann nicht vorbereitet werden. Aktualisiere den Team-Posteingang und versuche es erneut." + }, + "submitError": "Das Arbeitselement konnte nicht erstellt werden. Überprüfe den Empfänger und versuche es erneut.", + "pendingTitle": "Übergabe von {{name}}", + "acceptedTitle": "Angenommen von {{name}}", + "returnedTitle": "Zurückgegeben von {{name}}", + "noNote": "Kein Übergabehinweis.", + "statusLabel": "Übergabe des Arbeitselements", + "return": "Zurückgeben", + "accept": "Annehmen", + "returnTitle": "Diese Übergabe zurückgeben?", + "confirmReturn": "An Absender zurückgeben", + "returnHint": "Teile {{name}} mit, was geklärt oder nachgebessert werden muss. Das Arbeitselement wird erneut zugewiesen.", + "returnPlaceholder": "Was muss sich vor der nächsten Übergabe ändern?", + "responseError": "Die Antwort auf die Übergabe konnte nicht gespeichert werden. Versuche es erneut.", + "identityUnavailable": "Deine Teamidentität konnte nicht bestätigt werden. Prüfe dein Projektprofil, bevor du antwortest.", + "rowPending": "Von {{name}} · Antwort ausstehend", + "rowAccepted": "Übergabe angenommen · {{status}} · {{priority}}", + "rowReturned": "Zurückgegeben von {{name}} · {{status}} · {{priority}}" + }, + "errors": { + "loadTitle": "Team-Posteingang konnte nicht geladen werden", + "load": "Team-Posteingang konnte nicht geladen werden", + "loadMore": "Weitere Einträge im Team-Posteingang konnten nicht geladen werden. Versuche es erneut.", + "refresh": "Team-Posteingang konnte nicht aktualisiert werden", + "markRead": "Dieses Element konnte nicht als gelesen markiert werden. Versuche es erneut.", + "markUnread": "Dieses Element konnte nicht als ungelesen markiert werden. Versuche es erneut.", + "markAllRead": "Alle Elemente konnten nicht als gelesen markiert werden. Versuche es erneut.", + "identity": "Dein Konto konnte keinem Projektmitglied zugeordnet werden. Überprüfe die E-Mail-Adresse in deinem Projektprofil.", + "partialLoad": "Einige Quellen des Team-Posteingangs konnten nicht aktualisiert werden. Verfügbare Elemente werden weiterhin angezeigt.", + "workItemContext": "Einiger Projektkontext ist nicht verfügbar. Das Arbeitselement bleibt nutzbar.", + "workItemLoad": "Dieses Arbeitselement konnte nicht geladen werden. Versuche es erneut.", + "workItemUpdate": "Die letzte Änderung am Arbeitselement konnte nicht gespeichert werden. Versuche es erneut." + }, + "detail": { + "assignedSubtitle": "Zugewiesenes Arbeitselement", + "standaloneProject": "Eigenständig", + "mentionSubtitle": "In einem Kommentar erwähnt", + "mentionedYou": "hat dich erwähnt", + "threadComments_one": "{{count}} Kommentar in diesem Thread", + "threadComments_other": "{{count}} Kommentare in diesem Thread" + }, + "actions": { + "markRead": "Als gelesen markieren", + "markUnread": "Als ungelesen markieren", + "openWorkItem": "Arbeitselement öffnen", + "openSession": "Sitzung öffnen" + }, + "fields": { + "status": "Status", + "priority": "Priorität", + "assignee": "Zugewiesen an", + "workItemId": "Arbeitselement-ID", + "session": "Sitzung", + "comments": "Kommentare", + "threadId": "Thread-ID", + "commentId": "Kommentar-ID" + }, + "workItemStatus": { + "backlog": "Backlog", + "todo": "Zu erledigen", + "in_progress": "In Bearbeitung", + "in_review": "In Prüfung", + "blocked": "Blockiert", + "done": "Erledigt", + "cancelled": "Abgebrochen" + }, + "priority": { + "none": "Keine Priorität", + "low": "Niedrig", + "medium": "Mittel", + "high": "Hoch", + "urgent": "Dringend" + } + }, "toasts": { "replyEmpty": "Antwort darf nicht leer sein", "sessionNotFound": "Sitzung nicht gefunden", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index dea676221..b1e69ff93 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -1,4 +1,162 @@ { + "teamInbox": { + "title": "Bandeja del equipo", + "listLabel": "Lista de la bandeja del equipo", + "itemsLabel": "Elementos de la bandeja del equipo", + "unreadCount": "{{count}} sin leer", + "allRead": "Todo al día", + "loadMore": "Cargar más", + "filters": { + "all": "Todos", + "mentions": "Menciones", + "assigned": "Asignados" + }, + "status": { + "read": "Leído", + "unread": "Sin leer" + }, + "row": { + "assignedSummary": "{{status}} · {{priority}}", + "ariaLabel": "{{title}}, {{status}}" + }, + "search": { + "placeholder": "Buscar en la bandeja", + "ariaLabel": "Buscar en la bandeja del equipo" + }, + "groups": { + "today": "Hoy", + "yesterday": "Ayer", + "thisWeek": "Esta semana", + "earlier": "Anteriores" + }, + "empty": { + "title": "Todavía no hay nada aquí", + "subtitle": "Las menciones y los elementos de trabajo asignados aparecerán aquí.", + "selectTitle": "Selecciona un elemento", + "selectSubtitle": "Consulta el contexto del comentario o los detalles del elemento de trabajo.", + "mentions": { + "title": "Sin menciones", + "subtitle": "Cuando un compañero te mencione con @ en un comentario, aparecerá aquí." + }, + "assigned": { + "title": "No tienes nada asignado", + "subtitle": "Los elementos de trabajo asignados a ti aparecerán aquí." + }, + "noResults": { + "title": "Sin coincidencias", + "subtitle": "Ningún elemento coincide con «{{query}}»." + } + }, + "loading": "Cargando la bandeja del equipo…", + "drop": { + "title": "Suelta aquí para crear un elemento de trabajo", + "subtitle": "Revisa la instantánea de la sesión y luego crea o transfiere el elemento de trabajo.", + "processing": "Creando un elemento de trabajo a partir de «{{title}}»…", + "processingHint": "Leyendo la sesión y resolviendo los miembros del proyecto.", + "success": "Elemento de trabajo creado", + "reused": "Elemento de trabajo existente actualizado", + "failed": "No se pudo crear el elemento de trabajo", + "error": "No se puede crear un elemento de trabajo a partir de esta sesión.", + "open": "Abrir", + "dismiss": "Descartar" + }, + "handoff": { + "title": "Crear desde la sesión", + "createFromSession": "Crear elemento de trabajo del equipo…", + "project": "Proyecto de destino", + "chooseProject": "Elegir un proyecto", + "recipientSelf": "{{name}} (yo)", + "chooseRecipient": "Elegir un destinatario", + "todoCount_one": "{{count}} pendiente", + "todoCount_other": "{{count}} pendientes", + "workItemTitle": "Título del elemento de trabajo", + "assignTo": "Asignar a", + "note": "Nota de traspaso", + "notePlaceholder": "Indica qué está listo, qué queda pendiente y qué debería ocurrir a continuación.", + "selfHint": "Asignarte esto a ti mismo crea un elemento de trabajo normal, sin solicitud de traspaso.", + "submitHandoff": "Crear y traspasar", + "submitCreate": "Crear elemento de trabajo", + "preparing": "Preparando «{{title}}»…", + "preparationError": { + "session_unavailable": "Esta sesión ya no está disponible. Vuelve a abrirla e inténtalo de nuevo.", + "project_unavailable": "El proyecto de esta sesión ya no está disponible.", + "identity_unavailable": "Tu identidad no es miembro del proyecto de esta sesión.", + "no_project": "No hay ningún proyecto elegible disponible. Crea o únete a un proyecto e inténtalo de nuevo.", + "unknown": "No se puede preparar esta sesión. Actualiza la bandeja del equipo e inténtalo de nuevo." + }, + "submitError": "No se pudo crear el elemento de trabajo. Revisa el destinatario e inténtalo de nuevo.", + "pendingTitle": "Traspaso de {{name}}", + "acceptedTitle": "Aceptado por {{name}}", + "returnedTitle": "Devuelto por {{name}}", + "noNote": "Sin nota de traspaso.", + "statusLabel": "Traspaso del elemento de trabajo", + "return": "Devolver", + "accept": "Aceptar", + "returnTitle": "¿Devolver este traspaso?", + "confirmReturn": "Devolver al remitente", + "returnHint": "Indica a {{name}} qué necesita aclaración o seguimiento. El elemento de trabajo se le reasignará.", + "returnPlaceholder": "¿Qué debe cambiar antes de poder traspasarlo?", + "responseError": "No se pudo guardar la respuesta del traspaso. Inténtalo de nuevo.", + "identityUnavailable": "No se pudo verificar tu identidad de equipo. Revisa tu perfil de proyecto antes de responder.", + "rowPending": "De {{name}} · Esperando respuesta", + "rowAccepted": "Traspaso aceptado · {{status}} · {{priority}}", + "rowReturned": "Devuelto por {{name}} · {{status}} · {{priority}}" + }, + "errors": { + "loadTitle": "No se pudo cargar la bandeja del equipo", + "load": "No se pudo cargar la bandeja del equipo", + "loadMore": "No se pudieron cargar más elementos de la bandeja del equipo. Inténtalo de nuevo.", + "refresh": "No se pudo actualizar la bandeja del equipo", + "markRead": "No se pudo marcar este elemento como leído. Inténtalo de nuevo.", + "markUnread": "No se pudo marcar este elemento como no leído. Inténtalo de nuevo.", + "markAllRead": "No se pudieron marcar todos los elementos como leídos. Inténtalo de nuevo.", + "identity": "Tu cuenta no se pudo asociar a ningún miembro del proyecto. Revisa el correo electrónico de tu perfil de proyecto.", + "partialLoad": "Algunas fuentes de la bandeja del equipo no se pudieron actualizar. Los elementos disponibles se siguen mostrando.", + "workItemContext": "Parte del contexto del proyecto no está disponible. El elemento de trabajo sigue siendo utilizable.", + "workItemLoad": "No se pudo cargar este elemento de trabajo. Inténtalo de nuevo.", + "workItemUpdate": "No se pudo guardar el último cambio del elemento de trabajo. Inténtalo de nuevo." + }, + "detail": { + "assignedSubtitle": "Elemento de trabajo asignado", + "standaloneProject": "Independiente", + "mentionSubtitle": "Mencionado en un comentario", + "mentionedYou": "te mencionó", + "threadComments_one": "{{count}} comentario en este hilo", + "threadComments_other": "{{count}} comentarios en este hilo" + }, + "actions": { + "markRead": "Marcar como leído", + "markUnread": "Marcar como no leído", + "openWorkItem": "Abrir elemento de trabajo", + "openSession": "Abrir sesión" + }, + "fields": { + "status": "Estado", + "priority": "Prioridad", + "assignee": "Responsable", + "workItemId": "ID del elemento de trabajo", + "session": "Sesión", + "comments": "Comentarios", + "threadId": "ID del hilo", + "commentId": "ID del comentario" + }, + "workItemStatus": { + "backlog": "Pendiente", + "todo": "Por hacer", + "in_progress": "En curso", + "in_review": "En revisión", + "blocked": "Bloqueado", + "done": "Hecho", + "cancelled": "Cancelado" + }, + "priority": { + "none": "Sin prioridad", + "low": "Baja", + "medium": "Media", + "high": "Alta", + "urgent": "Urgente" + } + }, "toasts": { "replyEmpty": "La respuesta no puede estar vacía", "sessionNotFound": "Sesión no encontrada", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index 35ebae47a..9d12abd3e 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -1,4 +1,162 @@ { + "teamInbox": { + "title": "Boîte de réception d’équipe", + "listLabel": "Liste de la boîte de réception d’équipe", + "itemsLabel": "Éléments de la boîte de réception d’équipe", + "unreadCount": "{{count}} non lu(s)", + "allRead": "Tout est à jour", + "loadMore": "Charger plus", + "filters": { + "all": "Tous", + "mentions": "Mentions", + "assigned": "Assignés" + }, + "status": { + "read": "Lu", + "unread": "Non lu" + }, + "row": { + "assignedSummary": "{{status}} · {{priority}}", + "ariaLabel": "{{title}}, {{status}}" + }, + "search": { + "placeholder": "Rechercher dans la boîte de réception", + "ariaLabel": "Rechercher dans la boîte de réception d’équipe" + }, + "groups": { + "today": "Aujourd’hui", + "yesterday": "Hier", + "thisWeek": "Cette semaine", + "earlier": "Plus tôt" + }, + "empty": { + "title": "Rien ici pour l’instant", + "subtitle": "Les mentions et les éléments de travail assignés apparaîtront ici.", + "selectTitle": "Sélectionner un élément", + "selectSubtitle": "Voir le contexte du commentaire ou les détails de l’élément de travail.", + "mentions": { + "title": "Aucune mention", + "subtitle": "Lorsqu’un coéquipier vous mentionne avec @ dans un commentaire, cela apparaît ici." + }, + "assigned": { + "title": "Rien ne vous est assigné", + "subtitle": "Les éléments de travail qui vous sont assignés apparaîtront ici." + }, + "noResults": { + "title": "Aucun résultat", + "subtitle": "Aucun élément ne correspond à « {{query}} »." + } + }, + "loading": "Chargement de la boîte de réception d’équipe…", + "drop": { + "title": "Déposer ici pour créer un élément de travail", + "subtitle": "Vérifiez l’aperçu de la session, puis créez ou transférez l’élément de travail.", + "processing": "Création d’un élément de travail à partir de « {{title}} »…", + "processingHint": "Lecture de la session et résolution des membres du projet.", + "success": "Élément de travail créé", + "reused": "Élément de travail existant mis à jour", + "failed": "Impossible de créer l’élément de travail", + "error": "Impossible de créer un élément de travail à partir de cette session.", + "open": "Ouvrir", + "dismiss": "Ignorer" + }, + "handoff": { + "title": "Créer depuis la session", + "createFromSession": "Créer un élément de travail d’équipe…", + "project": "Projet de destination", + "chooseProject": "Choisir un projet", + "recipientSelf": "{{name}} (moi)", + "chooseRecipient": "Choisir un destinataire", + "todoCount_one": "{{count}} tâche", + "todoCount_other": "{{count}} tâches", + "workItemTitle": "Titre de l’élément de travail", + "assignTo": "Assigner à", + "note": "Note de transfert", + "notePlaceholder": "Indiquez ce qui est prêt, ce qui reste en suspens et ce qui devrait suivre.", + "selfHint": "Vous l’assigner à vous-même crée un élément de travail normal, sans demande de transfert.", + "submitHandoff": "Créer et transférer", + "submitCreate": "Créer l’élément de travail", + "preparing": "Préparation de « {{title}} »…", + "preparationError": { + "session_unavailable": "Cette session n’est plus disponible. Rouvrez-la et réessayez.", + "project_unavailable": "Le projet de cette session n’est plus disponible.", + "identity_unavailable": "Votre identité n’est pas membre du projet de cette session.", + "no_project": "Aucun projet éligible disponible. Créez ou rejoignez un projet, puis réessayez.", + "unknown": "Impossible de préparer cette session. Actualisez la boîte de réception d’équipe et réessayez." + }, + "submitError": "L’élément de travail n’a pas pu être créé. Vérifiez le destinataire et réessayez.", + "pendingTitle": "Transfert de {{name}}", + "acceptedTitle": "Accepté par {{name}}", + "returnedTitle": "Renvoyé par {{name}}", + "noNote": "Aucune note de transfert.", + "statusLabel": "Transfert de l’élément de travail", + "return": "Renvoyer", + "accept": "Accepter", + "returnTitle": "Renvoyer ce transfert ?", + "confirmReturn": "Renvoyer à l’expéditeur", + "returnHint": "Indiquez à {{name}} ce qui doit être clarifié ou complété. L’élément de travail lui sera réassigné.", + "returnPlaceholder": "Que faut-il changer avant un nouveau transfert ?", + "responseError": "La réponse au transfert n’a pas pu être enregistrée. Réessayez.", + "identityUnavailable": "Votre identité d’équipe n’a pas pu être vérifiée. Vérifiez votre profil de projet avant de répondre.", + "rowPending": "De {{name}} · En attente de réponse", + "rowAccepted": "Transfert accepté · {{status}} · {{priority}}", + "rowReturned": "Renvoyé par {{name}} · {{status}} · {{priority}}" + }, + "errors": { + "loadTitle": "Impossible de charger la boîte de réception d’équipe", + "load": "Impossible de charger la boîte de réception d’équipe", + "loadMore": "Impossible de charger d’autres éléments de la boîte de réception d’équipe. Réessayez.", + "refresh": "Impossible d’actualiser la boîte de réception d’équipe", + "markRead": "Impossible de marquer cet élément comme lu. Réessayez.", + "markUnread": "Impossible de marquer cet élément comme non lu. Réessayez.", + "markAllRead": "Impossible de marquer tous les éléments comme lus. Réessayez.", + "identity": "Votre compte n’a pas pu être associé à un membre du projet. Vérifiez l’e-mail de votre profil de projet.", + "partialLoad": "Certaines sources de la boîte de réception d’équipe n’ont pas pu être actualisées. Les éléments disponibles restent affichés.", + "workItemContext": "Une partie du contexte du projet n’est pas disponible. L’élément de travail reste utilisable.", + "workItemLoad": "Impossible de charger cet élément de travail. Réessayez.", + "workItemUpdate": "Impossible d’enregistrer la dernière modification de l’élément de travail. Réessayez." + }, + "detail": { + "assignedSubtitle": "Élément de travail assigné", + "standaloneProject": "Autonome", + "mentionSubtitle": "Mentionné dans un commentaire", + "mentionedYou": "vous a mentionné", + "threadComments_one": "{{count}} commentaire dans ce fil", + "threadComments_other": "{{count}} commentaires dans ce fil" + }, + "actions": { + "markRead": "Marquer comme lu", + "markUnread": "Marquer comme non lu", + "openWorkItem": "Ouvrir l’élément de travail", + "openSession": "Ouvrir la session" + }, + "fields": { + "status": "Statut", + "priority": "Priorité", + "assignee": "Assigné à", + "workItemId": "ID de l’élément de travail", + "session": "Session", + "comments": "Commentaires", + "threadId": "ID du fil", + "commentId": "ID du commentaire" + }, + "workItemStatus": { + "backlog": "Backlog", + "todo": "À faire", + "in_progress": "En cours", + "in_review": "En revue", + "blocked": "Bloqué", + "done": "Terminé", + "cancelled": "Annulé" + }, + "priority": { + "none": "Aucune priorité", + "low": "Basse", + "medium": "Moyenne", + "high": "Haute", + "urgent": "Urgente" + } + }, "toasts": { "replyEmpty": "La réponse ne peut pas être vide", "sessionNotFound": "Session introuvable", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index 38bec2b1b..d42326d06 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -1,4 +1,160 @@ { + "teamInbox": { + "title": "チームの受信トレイ", + "listLabel": "チーム受信トレイの一覧", + "itemsLabel": "チーム受信トレイの項目", + "unreadCount": "未読 {{count}} 件", + "allRead": "すべて確認済み", + "loadMore": "さらに読み込む", + "filters": { + "all": "すべて", + "mentions": "メンション", + "assigned": "自分の担当" + }, + "status": { + "read": "既読", + "unread": "未読" + }, + "row": { + "assignedSummary": "{{status}} · {{priority}}", + "ariaLabel": "{{title}}、{{status}}" + }, + "search": { + "placeholder": "受信トレイを検索", + "ariaLabel": "チーム受信トレイを検索" + }, + "groups": { + "today": "今日", + "yesterday": "昨日", + "thisWeek": "今週", + "earlier": "それ以前" + }, + "empty": { + "title": "まだ何もありません", + "subtitle": "メンションや割り当てられた作業項目がここに表示されます。", + "selectTitle": "項目を選択してください", + "selectSubtitle": "コメントの文脈や作業項目の詳細を確認できます。", + "mentions": { + "title": "メンションはありません", + "subtitle": "チームメンバーがコメントで @ メンションすると、ここに表示されます。" + }, + "assigned": { + "title": "自分に割り当てられた項目はありません", + "subtitle": "自分に割り当てられた作業項目がここに表示されます。" + }, + "noResults": { + "title": "一致する項目がありません", + "subtitle": "「{{query}}」に一致する項目はありません。" + } + }, + "loading": "チーム受信トレイを読み込んでいます…", + "drop": { + "title": "ここにドロップして作業項目を作成", + "subtitle": "セッションのスナップショットを確認してから、作業項目を作成または引き継いでください。", + "processing": "「{{title}}」から作業項目を作成しています…", + "processingHint": "セッションを読み込み、プロジェクトメンバーを解決しています。", + "success": "作業項目を作成しました", + "reused": "既存の作業項目を更新しました", + "failed": "作業項目を作成できませんでした", + "error": "このセッションから作業項目を作成できません。", + "open": "開く", + "dismiss": "閉じる" + }, + "handoff": { + "title": "セッションから作成", + "createFromSession": "チーム作業項目を作成…", + "project": "移動先プロジェクト", + "chooseProject": "プロジェクトを選択", + "recipientSelf": "{{name}}(自分)", + "chooseRecipient": "受信者を選択", + "todoCount_other": "To-do {{count}} 件", + "workItemTitle": "作業項目のタイトル", + "assignTo": "担当者", + "note": "引き継ぎメモ", + "notePlaceholder": "完了していること、未解決の点、次に行うべきことを共有してください。", + "selfHint": "自分自身に割り当てると、引き継ぎ依頼のない通常の作業項目が作成されます。", + "submitHandoff": "作成して引き継ぐ", + "submitCreate": "作業項目を作成", + "preparing": "「{{title}}」を準備しています…", + "preparationError": { + "session_unavailable": "このセッションは利用できなくなりました。再度開いてやり直してください。", + "project_unavailable": "このセッションのプロジェクトは利用できなくなりました。", + "identity_unavailable": "あなたはこのセッションのプロジェクトのメンバーではありません。", + "no_project": "利用可能なプロジェクトがありません。プロジェクトを作成または参加してから再試行してください。", + "unknown": "このセッションを準備できません。チーム受信トレイを更新してから再試行してください。" + }, + "submitError": "作業項目を作成できませんでした。受信者を確認してから再試行してください。", + "pendingTitle": "{{name}} からの引き継ぎ", + "acceptedTitle": "{{name}} が受諾しました", + "returnedTitle": "{{name}} が差し戻しました", + "noNote": "引き継ぎメモはありません。", + "statusLabel": "作業項目の引き継ぎ", + "return": "差し戻す", + "accept": "受諾する", + "returnTitle": "この引き継ぎを差し戻しますか?", + "confirmReturn": "送信者に差し戻す", + "returnHint": "確認や対応が必要な内容を {{name}} に伝えてください。作業項目は {{name}} に再割り当てされます。", + "returnPlaceholder": "次に引き継ぐ前に何を変更する必要がありますか?", + "responseError": "引き継ぎへの返信を保存できませんでした。再試行してください。", + "identityUnavailable": "チームでの本人確認ができませんでした。返信する前にプロジェクトのプロフィールを確認してください。", + "rowPending": "{{name}} から · 返信待ち", + "rowAccepted": "引き継ぎ受諾済み · {{status}} · {{priority}}", + "rowReturned": "{{name}} が差し戻し · {{status}} · {{priority}}" + }, + "errors": { + "loadTitle": "チーム受信トレイを読み込めませんでした", + "load": "チーム受信トレイを読み込めませんでした", + "loadMore": "チーム受信トレイの項目をこれ以上読み込めませんでした。再試行してください。", + "refresh": "チーム受信トレイを更新できませんでした", + "markRead": "この項目を既読にできませんでした。再試行してください。", + "markUnread": "この項目を未読にできませんでした。再試行してください。", + "markAllRead": "すべての項目を既読にできませんでした。再試行してください。", + "identity": "アカウントをプロジェクトメンバーに一致させられませんでした。プロジェクトのプロフィールのメールアドレスを確認してください。", + "partialLoad": "一部のチーム受信トレイのソースを更新できませんでした。利用可能な項目は引き続き表示されます。", + "workItemContext": "一部のプロジェクト情報が利用できません。作業項目は引き続き使用できます。", + "workItemLoad": "この作業項目を読み込めませんでした。再試行してください。", + "workItemUpdate": "作業項目への最新の変更を保存できませんでした。再試行してください。" + }, + "detail": { + "assignedSubtitle": "割り当てられた作業項目", + "standaloneProject": "単独", + "mentionSubtitle": "コメントでメンションされました", + "mentionedYou": "があなたをメンションしました", + "threadComments_other": "このスレッドには {{count}} 件のコメントがあります" + }, + "actions": { + "markRead": "既読にする", + "markUnread": "未読にする", + "openWorkItem": "作業項目を開く", + "openSession": "セッションを開く" + }, + "fields": { + "status": "ステータス", + "priority": "優先度", + "assignee": "担当者", + "workItemId": "作業項目 ID", + "session": "セッション", + "comments": "コメント", + "threadId": "スレッド ID", + "commentId": "コメント ID" + }, + "workItemStatus": { + "backlog": "バックログ", + "todo": "未着手", + "in_progress": "進行中", + "in_review": "レビュー中", + "blocked": "ブロック中", + "done": "完了", + "cancelled": "キャンセル" + }, + "priority": { + "none": "優先度なし", + "low": "低", + "medium": "中", + "high": "高", + "urgent": "緊急" + } + }, "toasts": { "replyEmpty": "返信を入力してください", "sessionNotFound": "セッションが見つかりません", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 490b02ff0..971bf1e0e 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -1,4 +1,160 @@ { + "teamInbox": { + "title": "팀 수신함", + "listLabel": "팀 수신함 목록", + "itemsLabel": "팀 수신함 항목", + "unreadCount": "읽지 않음 {{count}}건", + "allRead": "모두 확인했습니다", + "loadMore": "더 불러오기", + "filters": { + "all": "전체", + "mentions": "멘션", + "assigned": "할당됨" + }, + "status": { + "read": "읽음", + "unread": "읽지 않음" + }, + "row": { + "assignedSummary": "{{status}} · {{priority}}", + "ariaLabel": "{{title}}, {{status}}" + }, + "search": { + "placeholder": "수신함 검색", + "ariaLabel": "팀 수신함 검색" + }, + "groups": { + "today": "오늘", + "yesterday": "어제", + "thisWeek": "이번 주", + "earlier": "이전" + }, + "empty": { + "title": "아직 항목이 없습니다", + "subtitle": "멘션과 할당된 작업 항목이 여기에 표시됩니다.", + "selectTitle": "항목을 선택하세요", + "selectSubtitle": "댓글 맥락이나 작업 항목 세부 정보를 확인하세요.", + "mentions": { + "title": "멘션이 없습니다", + "subtitle": "팀원이 댓글에서 @로 멘션하면 여기에 표시됩니다." + }, + "assigned": { + "title": "할당된 항목이 없습니다", + "subtitle": "나에게 할당된 작업 항목이 여기에 표시됩니다." + }, + "noResults": { + "title": "일치하는 항목이 없습니다", + "subtitle": "“{{query}}”와(과) 일치하는 항목이 없습니다." + } + }, + "loading": "팀 수신함을 불러오는 중…", + "drop": { + "title": "여기로 끌어다 놓아 작업 항목 만들기", + "subtitle": "세션 스냅샷을 확인한 다음 작업 항목을 만들거나 인계하세요.", + "processing": "“{{title}}”에서 작업 항목을 만드는 중…", + "processingHint": "세션을 읽고 프로젝트 구성원을 확인하는 중입니다.", + "success": "작업 항목을 만들었습니다", + "reused": "기존 작업 항목을 업데이트했습니다", + "failed": "작업 항목을 만들지 못했습니다", + "error": "이 세션에서 작업 항목을 만들 수 없습니다.", + "open": "열기", + "dismiss": "닫기" + }, + "handoff": { + "title": "세션에서 만들기", + "createFromSession": "팀 작업 항목 만들기…", + "project": "대상 프로젝트", + "chooseProject": "프로젝트 선택", + "recipientSelf": "{{name}}(나)", + "chooseRecipient": "받는 사람 선택", + "todoCount_other": "할 일 {{count}}개", + "workItemTitle": "작업 항목 제목", + "assignTo": "담당자", + "note": "인계 메모", + "notePlaceholder": "완료된 사항, 남은 미해결 사항, 다음에 해야 할 일을 알려주세요.", + "selfHint": "자신에게 할당하면 인계 요청 없이 일반 작업 항목이 생성됩니다.", + "submitHandoff": "만들고 인계하기", + "submitCreate": "작업 항목 만들기", + "preparing": "“{{title}}” 준비하는 중…", + "preparationError": { + "session_unavailable": "이 세션을 더 이상 사용할 수 없습니다. 다시 열어서 시도해 주세요.", + "project_unavailable": "이 세션의 프로젝트를 더 이상 사용할 수 없습니다.", + "identity_unavailable": "회원님은 이 세션 프로젝트의 구성원이 아닙니다.", + "no_project": "사용 가능한 프로젝트가 없습니다. 프로젝트를 만들거나 참여한 후 다시 시도해 주세요.", + "unknown": "이 세션을 준비할 수 없습니다. 팀 수신함을 새로 고친 후 다시 시도해 주세요." + }, + "submitError": "작업 항목을 만들지 못했습니다. 받는 사람을 확인한 후 다시 시도해 주세요.", + "pendingTitle": "{{name}} 님의 인계", + "acceptedTitle": "{{name}} 님이 수락함", + "returnedTitle": "{{name}} 님이 반려함", + "noNote": "인계 메모가 없습니다.", + "statusLabel": "작업 항목 인계", + "return": "반려", + "accept": "수락", + "returnTitle": "이 인계를 반려하시겠어요?", + "confirmReturn": "보낸 사람에게 반려", + "returnHint": "{{name}} 님에게 확인이나 후속 조치가 필요한 내용을 알려주세요. 작업 항목이 다시 할당됩니다.", + "returnPlaceholder": "다시 인계하기 전에 무엇을 변경해야 하나요?", + "responseError": "인계 응답을 저장하지 못했습니다. 다시 시도해 주세요.", + "identityUnavailable": "팀 신원을 확인할 수 없습니다. 응답하기 전에 프로젝트 프로필을 확인하세요.", + "rowPending": "{{name}} 님으로부터 · 응답 대기 중", + "rowAccepted": "인계 수락됨 · {{status}} · {{priority}}", + "rowReturned": "{{name}} 님이 반려함 · {{status}} · {{priority}}" + }, + "errors": { + "loadTitle": "팀 수신함을 불러올 수 없습니다", + "load": "팀 수신함을 불러올 수 없습니다", + "loadMore": "팀 수신함 항목을 더 불러올 수 없습니다. 다시 시도해 주세요.", + "refresh": "팀 수신함을 새로 고칠 수 없습니다", + "markRead": "이 항목을 읽음으로 표시할 수 없습니다. 다시 시도해 주세요.", + "markUnread": "이 항목을 읽지 않음으로 표시할 수 없습니다. 다시 시도해 주세요.", + "markAllRead": "모든 항목을 읽음으로 표시할 수 없습니다. 다시 시도해 주세요.", + "identity": "계정을 프로젝트 구성원과 일치시킬 수 없습니다. 프로젝트 프로필의 이메일을 확인하세요.", + "partialLoad": "일부 팀 수신함 소스를 새로 고칠 수 없습니다. 사용 가능한 항목은 계속 표시됩니다.", + "workItemContext": "일부 프로젝트 정보를 사용할 수 없습니다. 작업 항목은 계속 사용할 수 있습니다.", + "workItemLoad": "이 작업 항목을 불러올 수 없습니다. 다시 시도해 주세요.", + "workItemUpdate": "작업 항목의 최신 변경 사항을 저장할 수 없습니다. 다시 시도해 주세요." + }, + "detail": { + "assignedSubtitle": "할당된 작업 항목", + "standaloneProject": "독립 항목", + "mentionSubtitle": "댓글에서 멘션됨", + "mentionedYou": "님이 회원님을 멘션했습니다", + "threadComments_other": "이 스레드에 댓글 {{count}}개가 있습니다" + }, + "actions": { + "markRead": "읽음으로 표시", + "markUnread": "읽지 않음으로 표시", + "openWorkItem": "작업 항목 열기", + "openSession": "세션 열기" + }, + "fields": { + "status": "상태", + "priority": "우선순위", + "assignee": "담당자", + "workItemId": "작업 항목 ID", + "session": "세션", + "comments": "댓글", + "threadId": "스레드 ID", + "commentId": "댓글 ID" + }, + "workItemStatus": { + "backlog": "백로그", + "todo": "할 일", + "in_progress": "진행 중", + "in_review": "검토 중", + "blocked": "차단됨", + "done": "완료", + "cancelled": "취소됨" + }, + "priority": { + "none": "우선순위 없음", + "low": "낮음", + "medium": "중간", + "high": "높음", + "urgent": "긴급" + } + }, "toasts": { "replyEmpty": "답장을 입력하세요", "sessionNotFound": "세션을 찾을 수 없습니다", diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index bd10dff0f..6b849b912 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -1,4 +1,166 @@ { + "teamInbox": { + "title": "Skrzynka zespołu", + "listLabel": "Lista skrzynki zespołu", + "itemsLabel": "Elementy skrzynki zespołu", + "unreadCount": "Nieprzeczytane: {{count}}", + "allRead": "Wszystko przeczytane", + "loadMore": "Wczytaj więcej", + "filters": { + "all": "Wszystkie", + "mentions": "Wzmianki", + "assigned": "Przypisane" + }, + "status": { + "read": "Przeczytane", + "unread": "Nieprzeczytane" + }, + "row": { + "assignedSummary": "{{status}} · {{priority}}", + "ariaLabel": "{{title}}, {{status}}" + }, + "search": { + "placeholder": "Przeszukaj skrzynkę", + "ariaLabel": "Przeszukaj skrzynkę zespołu" + }, + "groups": { + "today": "Dzisiaj", + "yesterday": "Wczoraj", + "thisWeek": "W tym tygodniu", + "earlier": "Wcześniej" + }, + "empty": { + "title": "Nic tu jeszcze nie ma", + "subtitle": "Wzmianki i przypisane elementy pracy będą się tu pojawiać.", + "selectTitle": "Wybierz element", + "selectSubtitle": "Zobacz kontekst komentarza lub szczegóły elementu pracy.", + "mentions": { + "title": "Brak wzmianek", + "subtitle": "Gdy członek zespołu wspomni o Tobie za pomocą @ w komentarzu, pojawi się to tutaj." + }, + "assigned": { + "title": "Nic Ci nie przypisano", + "subtitle": "Przypisane do Ciebie elementy pracy pojawią się tutaj." + }, + "noResults": { + "title": "Brak wyników", + "subtitle": "Żaden element nie pasuje do „{{query}}”." + } + }, + "loading": "Wczytywanie skrzynki zespołu…", + "drop": { + "title": "Upuść tutaj, aby utworzyć element pracy", + "subtitle": "Sprawdź migawkę sesji, a następnie utwórz lub przekaż element pracy.", + "processing": "Tworzenie elementu pracy z „{{title}}”…", + "processingHint": "Odczytywanie sesji i ustalanie członków projektu.", + "success": "Utworzono element pracy", + "reused": "Zaktualizowano istniejący element pracy", + "failed": "Nie udało się utworzyć elementu pracy", + "error": "Nie można utworzyć elementu pracy na podstawie tej sesji.", + "open": "Otwórz", + "dismiss": "Odrzuć" + }, + "handoff": { + "title": "Utwórz z sesji", + "createFromSession": "Utwórz zespołowy element pracy…", + "project": "Projekt docelowy", + "chooseProject": "Wybierz projekt", + "recipientSelf": "{{name}} (ja)", + "chooseRecipient": "Wybierz odbiorcę", + "todoCount_one": "{{count}} zadanie", + "todoCount_few": "{{count}} zadania", + "todoCount_many": "{{count}} zadań", + "todoCount_other": "{{count}} zadania", + "workItemTitle": "Tytuł elementu pracy", + "assignTo": "Przypisz do", + "note": "Notatka przekazania", + "notePlaceholder": "Opisz, co jest gotowe, co pozostało nierozwiązane i co powinno się wydarzyć dalej.", + "selfHint": "Przypisanie tego do siebie tworzy zwykły element pracy bez żądania przekazania.", + "submitHandoff": "Utwórz i przekaż", + "submitCreate": "Utwórz element pracy", + "preparing": "Przygotowywanie „{{title}}”…", + "preparationError": { + "session_unavailable": "Ta sesja nie jest już dostępna. Otwórz ją ponownie i spróbuj jeszcze raz.", + "project_unavailable": "Projekt tej sesji nie jest już dostępny.", + "identity_unavailable": "Twoja tożsamość nie jest członkiem projektu tej sesji.", + "no_project": "Brak dostępnego kwalifikującego się projektu. Utwórz projekt lub dołącz do niego, a następnie spróbuj ponownie.", + "unknown": "Nie można przygotować tej sesji. Odśwież skrzynkę zespołu i spróbuj ponownie." + }, + "submitError": "Nie udało się utworzyć elementu pracy. Sprawdź odbiorcę i spróbuj ponownie.", + "pendingTitle": "Przekazanie od {{name}}", + "acceptedTitle": "Zaakceptowane przez {{name}}", + "returnedTitle": "Zwrócone przez {{name}}", + "noNote": "Brak notatki przekazania.", + "statusLabel": "Przekazanie elementu pracy", + "return": "Zwróć", + "accept": "Zaakceptuj", + "returnTitle": "Zwrócić to przekazanie?", + "confirmReturn": "Zwróć do nadawcy", + "returnHint": "Poinformuj {{name}}, co wymaga wyjaśnienia lub dalszych działań. Element pracy zostanie ponownie przypisany.", + "returnPlaceholder": "Co należy zmienić przed kolejnym przekazaniem?", + "responseError": "Nie udało się zapisać odpowiedzi na przekazanie. Spróbuj ponownie.", + "identityUnavailable": "Nie udało się zweryfikować Twojej tożsamości zespołowej. Sprawdź swój profil projektu przed odpowiedzią.", + "rowPending": "Od {{name}} · Oczekuje na odpowiedź", + "rowAccepted": "Przekazanie zaakceptowane · {{status}} · {{priority}}", + "rowReturned": "Zwrócone przez {{name}} · {{status}} · {{priority}}" + }, + "errors": { + "loadTitle": "Nie można wczytać skrzynki zespołu", + "load": "Nie można wczytać skrzynki zespołu", + "loadMore": "Nie można wczytać kolejnych elementów skrzynki zespołu. Spróbuj ponownie.", + "refresh": "Nie można odświeżyć skrzynki zespołu", + "markRead": "Nie można oznaczyć tego elementu jako przeczytanego. Spróbuj ponownie.", + "markUnread": "Nie można oznaczyć tego elementu jako nieprzeczytanego. Spróbuj ponownie.", + "markAllRead": "Nie można oznaczyć wszystkich elementów jako przeczytanych. Spróbuj ponownie.", + "identity": "Nie udało się dopasować Twojego konta do członka projektu. Sprawdź adres e-mail w profilu projektu.", + "partialLoad": "Nie udało się odświeżyć niektórych źródeł skrzynki zespołu. Dostępne elementy są nadal wyświetlane.", + "workItemContext": "Część kontekstu projektu jest niedostępna. Element pracy jest nadal użyteczny.", + "workItemLoad": "Nie można wczytać tego elementu pracy. Spróbuj ponownie.", + "workItemUpdate": "Nie można zapisać ostatniej zmiany elementu pracy. Spróbuj ponownie." + }, + "detail": { + "assignedSubtitle": "Przypisany element pracy", + "standaloneProject": "Samodzielny", + "mentionSubtitle": "Wspomniano w komentarzu", + "mentionedYou": "wspomniał(a) o Tobie", + "threadComments_one": "{{count}} komentarz w tym wątku", + "threadComments_few": "{{count}} komentarze w tym wątku", + "threadComments_many": "{{count}} komentarzy w tym wątku", + "threadComments_other": "{{count}} komentarza w tym wątku" + }, + "actions": { + "markRead": "Oznacz jako przeczytane", + "markUnread": "Oznacz jako nieprzeczytane", + "openWorkItem": "Otwórz element pracy", + "openSession": "Otwórz sesję" + }, + "fields": { + "status": "Status", + "priority": "Priorytet", + "assignee": "Przypisano do", + "workItemId": "ID elementu pracy", + "session": "Sesja", + "comments": "Komentarze", + "threadId": "ID wątku", + "commentId": "ID komentarza" + }, + "workItemStatus": { + "backlog": "Zaległości", + "todo": "Do zrobienia", + "in_progress": "W toku", + "in_review": "W recenzji", + "blocked": "Zablokowane", + "done": "Ukończone", + "cancelled": "Anulowane" + }, + "priority": { + "none": "Brak priorytetu", + "low": "Niski", + "medium": "Średni", + "high": "Wysoki", + "urgent": "Pilny" + } + }, "toasts": { "replyEmpty": "Odpowiedź nie może być pusta", "sessionNotFound": "Nie znaleziono sesji", diff --git a/src/i18n/locales/pt/common.json b/src/i18n/locales/pt/common.json index 6bed7b2a8..3b3c018b7 100644 --- a/src/i18n/locales/pt/common.json +++ b/src/i18n/locales/pt/common.json @@ -1,4 +1,162 @@ { + "teamInbox": { + "title": "Caixa de entrada da equipe", + "listLabel": "Lista da caixa de entrada da equipe", + "itemsLabel": "Itens da caixa de entrada da equipe", + "unreadCount": "{{count}} não lidas", + "allRead": "Tudo em dia", + "loadMore": "Carregar mais", + "filters": { + "all": "Todos", + "mentions": "Menções", + "assigned": "Atribuídos" + }, + "status": { + "read": "Lido", + "unread": "Não lido" + }, + "row": { + "assignedSummary": "{{status}} · {{priority}}", + "ariaLabel": "{{title}}, {{status}}" + }, + "search": { + "placeholder": "Pesquisar na caixa de entrada", + "ariaLabel": "Pesquisar na caixa de entrada da equipe" + }, + "groups": { + "today": "Hoje", + "yesterday": "Ontem", + "thisWeek": "Esta semana", + "earlier": "Anteriores" + }, + "empty": { + "title": "Ainda não há nada aqui", + "subtitle": "Menções e itens de trabalho atribuídos aparecerão aqui.", + "selectTitle": "Selecione um item", + "selectSubtitle": "Veja o contexto do comentário ou os detalhes do item de trabalho.", + "mentions": { + "title": "Nenhuma menção", + "subtitle": "Quando um colega de equipe mencionar você com @ em um comentário, isso aparecerá aqui." + }, + "assigned": { + "title": "Nada atribuído a você", + "subtitle": "Os itens de trabalho atribuídos a você aparecerão aqui." + }, + "noResults": { + "title": "Nenhuma correspondência", + "subtitle": "Nenhum item corresponde a “{{query}}”." + } + }, + "loading": "Carregando a caixa de entrada da equipe…", + "drop": { + "title": "Solte aqui para criar um item de trabalho", + "subtitle": "Revise o snapshot da sessão e, em seguida, crie ou repasse o item de trabalho.", + "processing": "Criando um item de trabalho a partir de “{{title}}”…", + "processingHint": "Lendo a sessão e resolvendo os membros do projeto.", + "success": "Item de trabalho criado", + "reused": "Item de trabalho existente atualizado", + "failed": "Não foi possível criar o item de trabalho", + "error": "Não é possível criar um item de trabalho a partir desta sessão.", + "open": "Abrir", + "dismiss": "Descartar" + }, + "handoff": { + "title": "Criar a partir da sessão", + "createFromSession": "Criar item de trabalho da equipe…", + "project": "Projeto de destino", + "chooseProject": "Escolher um projeto", + "recipientSelf": "{{name}} (eu)", + "chooseRecipient": "Escolher um destinatário", + "todoCount_one": "{{count}} tarefa", + "todoCount_other": "{{count}} tarefas", + "workItemTitle": "Título do item de trabalho", + "assignTo": "Atribuir a", + "note": "Nota de repasse", + "notePlaceholder": "Compartilhe o que está pronto, o que está pendente e o que deve acontecer a seguir.", + "selfHint": "Atribuir isto a você mesmo cria um item de trabalho normal, sem solicitação de repasse.", + "submitHandoff": "Criar e repassar", + "submitCreate": "Criar item de trabalho", + "preparing": "Preparando “{{title}}”…", + "preparationError": { + "session_unavailable": "Esta sessão não está mais disponível. Reabra-a e tente novamente.", + "project_unavailable": "O projeto desta sessão não está mais disponível.", + "identity_unavailable": "Sua identidade não é membro do projeto desta sessão.", + "no_project": "Nenhum projeto elegível disponível. Crie ou participe de um projeto e tente novamente.", + "unknown": "Não foi possível preparar esta sessão. Atualize a caixa de entrada da equipe e tente novamente." + }, + "submitError": "Não foi possível criar o item de trabalho. Verifique o destinatário e tente novamente.", + "pendingTitle": "Repasse de {{name}}", + "acceptedTitle": "Aceito por {{name}}", + "returnedTitle": "Devolvido por {{name}}", + "noNote": "Sem nota de repasse.", + "statusLabel": "Repasse do item de trabalho", + "return": "Devolver", + "accept": "Aceitar", + "returnTitle": "Devolver este repasse?", + "confirmReturn": "Devolver ao remetente", + "returnHint": "Informe a {{name}} o que precisa de esclarecimento ou acompanhamento. O item de trabalho será reatribuído a essa pessoa.", + "returnPlaceholder": "O que precisa mudar antes de um novo repasse?", + "responseError": "Não foi possível salvar a resposta do repasse. Tente novamente.", + "identityUnavailable": "Não foi possível verificar sua identidade de equipe. Confira seu perfil do projeto antes de responder.", + "rowPending": "De {{name}} · Aguardando resposta", + "rowAccepted": "Repasse aceito · {{status}} · {{priority}}", + "rowReturned": "Devolvido por {{name}} · {{status}} · {{priority}}" + }, + "errors": { + "loadTitle": "Não foi possível carregar a caixa de entrada da equipe", + "load": "Não foi possível carregar a caixa de entrada da equipe", + "loadMore": "Não foi possível carregar mais itens da caixa de entrada da equipe. Tente novamente.", + "refresh": "Não foi possível atualizar a caixa de entrada da equipe", + "markRead": "Não foi possível marcar este item como lido. Tente novamente.", + "markUnread": "Não foi possível marcar este item como não lido. Tente novamente.", + "markAllRead": "Não foi possível marcar todos os itens como lidos. Tente novamente.", + "identity": "Sua conta não pôde ser associada a um membro do projeto. Verifique o e-mail no seu perfil do projeto.", + "partialLoad": "Algumas fontes da caixa de entrada da equipe não puderam ser atualizadas. Os itens disponíveis continuam sendo exibidos.", + "workItemContext": "Parte do contexto do projeto não está disponível. O item de trabalho continua utilizável.", + "workItemLoad": "Não foi possível carregar este item de trabalho. Tente novamente.", + "workItemUpdate": "Não foi possível salvar a última alteração do item de trabalho. Tente novamente." + }, + "detail": { + "assignedSubtitle": "Item de trabalho atribuído", + "standaloneProject": "Independente", + "mentionSubtitle": "Mencionado em um comentário", + "mentionedYou": "mencionou você", + "threadComments_one": "{{count}} comentário nesta conversa", + "threadComments_other": "{{count}} comentários nesta conversa" + }, + "actions": { + "markRead": "Marcar como lido", + "markUnread": "Marcar como não lido", + "openWorkItem": "Abrir item de trabalho", + "openSession": "Abrir sessão" + }, + "fields": { + "status": "Status", + "priority": "Prioridade", + "assignee": "Responsável", + "workItemId": "ID do item de trabalho", + "session": "Sessão", + "comments": "Comentários", + "threadId": "ID da conversa", + "commentId": "ID do comentário" + }, + "workItemStatus": { + "backlog": "Backlog", + "todo": "A fazer", + "in_progress": "Em andamento", + "in_review": "Em revisão", + "blocked": "Bloqueado", + "done": "Concluído", + "cancelled": "Cancelado" + }, + "priority": { + "none": "Sem prioridade", + "low": "Baixa", + "medium": "Média", + "high": "Alta", + "urgent": "Urgente" + } + }, "toasts": { "replyEmpty": "A resposta não pode estar vazia", "sessionNotFound": "Sessão não encontrada", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 1adc60023..24f7146c0 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -1,4 +1,166 @@ { + "teamInbox": { + "title": "Общий почтовый ящик команды", + "listLabel": "Список общего почтового ящика команды", + "itemsLabel": "Элементы общего почтового ящика команды", + "unreadCount": "Непрочитанных: {{count}}", + "allRead": "Всё прочитано", + "loadMore": "Загрузить ещё", + "filters": { + "all": "Все", + "mentions": "Упоминания", + "assigned": "Назначенные" + }, + "status": { + "read": "Прочитано", + "unread": "Не прочитано" + }, + "row": { + "assignedSummary": "{{status}} · {{priority}}", + "ariaLabel": "{{title}}, {{status}}" + }, + "search": { + "placeholder": "Поиск в почтовом ящике", + "ariaLabel": "Поиск в почтовом ящике команды" + }, + "groups": { + "today": "Сегодня", + "yesterday": "Вчера", + "thisWeek": "На этой неделе", + "earlier": "Ранее" + }, + "empty": { + "title": "Здесь пока ничего нет", + "subtitle": "Упоминания и назначенные рабочие задачи будут отображаться здесь.", + "selectTitle": "Выберите элемент", + "selectSubtitle": "Просмотрите контекст комментария или детали рабочей задачи.", + "mentions": { + "title": "Нет упоминаний", + "subtitle": "Когда коллега упомянет вас через @ в комментарии, это появится здесь." + }, + "assigned": { + "title": "Вам ничего не назначено", + "subtitle": "Назначенные вам рабочие задачи будут отображаться здесь." + }, + "noResults": { + "title": "Совпадений нет", + "subtitle": "Нет элементов, соответствующих «{{query}}»." + } + }, + "loading": "Загрузка общего почтового ящика команды…", + "drop": { + "title": "Перетащите сюда, чтобы создать рабочую задачу", + "subtitle": "Просмотрите снимок сессии, затем создайте или передайте рабочую задачу.", + "processing": "Создание рабочей задачи из «{{title}}»…", + "processingHint": "Чтение сессии и определение участников проекта.", + "success": "Рабочая задача создана", + "reused": "Существующая рабочая задача обновлена", + "failed": "Не удалось создать рабочую задачу", + "error": "Невозможно создать рабочую задачу на основе этой сессии.", + "open": "Открыть", + "dismiss": "Скрыть" + }, + "handoff": { + "title": "Создать из сессии", + "createFromSession": "Создать командную рабочую задачу…", + "project": "Проект назначения", + "chooseProject": "Выбрать проект", + "recipientSelf": "{{name}} (я)", + "chooseRecipient": "Выбрать получателя", + "todoCount_one": "{{count}} задача", + "todoCount_few": "{{count}} задачи", + "todoCount_many": "{{count}} задач", + "todoCount_other": "{{count}} задачи", + "workItemTitle": "Название рабочей задачи", + "assignTo": "Назначить на", + "note": "Примечание к передаче", + "notePlaceholder": "Опишите, что готово, что осталось нерешённым и что должно произойти дальше.", + "selfHint": "Назначение этого себе создаёт обычную рабочую задачу без запроса на передачу.", + "submitHandoff": "Создать и передать", + "submitCreate": "Создать рабочую задачу", + "preparing": "Подготовка «{{title}}»…", + "preparationError": { + "session_unavailable": "Эта сессия больше недоступна. Откройте её заново и повторите попытку.", + "project_unavailable": "Проект этой сессии больше недоступен.", + "identity_unavailable": "Ваша учётная запись не является участником проекта этой сессии.", + "no_project": "Нет доступных подходящих проектов. Создайте проект или присоединитесь к нему и повторите попытку.", + "unknown": "Не удалось подготовить эту сессию. Обновите общий почтовый ящик команды и повторите попытку." + }, + "submitError": "Не удалось создать рабочую задачу. Проверьте получателя и повторите попытку.", + "pendingTitle": "Передача от {{name}}", + "acceptedTitle": "Принято пользователем {{name}}", + "returnedTitle": "Возвращено пользователем {{name}}", + "noNote": "Нет примечания к передаче.", + "statusLabel": "Передача рабочей задачи", + "return": "Вернуть", + "accept": "Принять", + "returnTitle": "Вернуть эту передачу?", + "confirmReturn": "Вернуть отправителю", + "returnHint": "Сообщите {{name}}, что требует уточнения или доработки. Рабочая задача будет переназначена этому человеку.", + "returnPlaceholder": "Что нужно изменить перед следующей передачей?", + "responseError": "Не удалось сохранить ответ на передачу. Повторите попытку.", + "identityUnavailable": "Не удалось подтвердить вашу командную учётную запись. Проверьте профиль проекта перед ответом.", + "rowPending": "От {{name}} · Ожидает ответа", + "rowAccepted": "Передача принята · {{status}} · {{priority}}", + "rowReturned": "Возвращено пользователем {{name}} · {{status}} · {{priority}}" + }, + "errors": { + "loadTitle": "Не удалось загрузить общий почтовый ящик команды", + "load": "Не удалось загрузить общий почтовый ящик команды", + "loadMore": "Не удалось загрузить дополнительные элементы общего почтового ящика команды. Повторите попытку.", + "refresh": "Не удалось обновить общий почтовый ящик команды", + "markRead": "Не удалось отметить этот элемент как прочитанный. Повторите попытку.", + "markUnread": "Не удалось отметить этот элемент как непрочитанный. Повторите попытку.", + "markAllRead": "Не удалось отметить все элементы как прочитанные. Повторите попытку.", + "identity": "Не удалось сопоставить вашу учётную запись с участником проекта. Проверьте адрес электронной почты в профиле проекта.", + "partialLoad": "Некоторые источники общего почтового ящика команды не удалось обновить. Доступные элементы по-прежнему отображаются.", + "workItemContext": "Часть контекста проекта недоступна. Рабочая задача остаётся доступной для использования.", + "workItemLoad": "Не удалось загрузить эту рабочую задачу. Повторите попытку.", + "workItemUpdate": "Не удалось сохранить последнее изменение рабочей задачи. Повторите попытку." + }, + "detail": { + "assignedSubtitle": "Назначенная рабочая задача", + "standaloneProject": "Отдельная", + "mentionSubtitle": "Упомянуто в комментарии", + "mentionedYou": "упомянул(а) вас", + "threadComments_one": "{{count}} комментарий в этой теме", + "threadComments_few": "{{count}} комментария в этой теме", + "threadComments_many": "{{count}} комментариев в этой теме", + "threadComments_other": "{{count}} комментария в этой теме" + }, + "actions": { + "markRead": "Отметить как прочитанное", + "markUnread": "Отметить как непрочитанное", + "openWorkItem": "Открыть рабочую задачу", + "openSession": "Открыть сессию" + }, + "fields": { + "status": "Статус", + "priority": "Приоритет", + "assignee": "Исполнитель", + "workItemId": "ID рабочей задачи", + "session": "Сессия", + "comments": "Комментарии", + "threadId": "ID темы", + "commentId": "ID комментария" + }, + "workItemStatus": { + "backlog": "Бэклог", + "todo": "К выполнению", + "in_progress": "В работе", + "in_review": "На проверке", + "blocked": "Заблокировано", + "done": "Готово", + "cancelled": "Отменено" + }, + "priority": { + "none": "Без приоритета", + "low": "Низкий", + "medium": "Средний", + "high": "Высокий", + "urgent": "Срочный" + } + }, "toasts": { "replyEmpty": "Ответ не может быть пустым", "sessionNotFound": "Сессия не найдена", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index a196d2b79..3357b223c 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -1,4 +1,162 @@ { + "teamInbox": { + "title": "Ekip Gelen Kutusu", + "listLabel": "Ekip Gelen Kutusu listesi", + "itemsLabel": "Ekip Gelen Kutusu öğeleri", + "unreadCount": "{{count}} okunmadı", + "allRead": "Hepsi okundu", + "loadMore": "Daha fazla yükle", + "filters": { + "all": "Tümü", + "mentions": "Bahsedilenler", + "assigned": "Atananlar" + }, + "status": { + "read": "Okundu", + "unread": "Okunmadı" + }, + "row": { + "assignedSummary": "{{status}} · {{priority}}", + "ariaLabel": "{{title}}, {{status}}" + }, + "search": { + "placeholder": "Gelen kutusunda ara", + "ariaLabel": "Ekip Gelen Kutusunda ara" + }, + "groups": { + "today": "Bugün", + "yesterday": "Dün", + "thisWeek": "Bu hafta", + "earlier": "Daha önce" + }, + "empty": { + "title": "Henüz burada bir şey yok", + "subtitle": "Bahsetmeler ve atanan iş öğeleri burada görünecek.", + "selectTitle": "Bir öğe seçin", + "selectSubtitle": "Yorum bağlamını veya iş öğesi ayrıntılarını görüntüleyin.", + "mentions": { + "title": "Bahsetme yok", + "subtitle": "Bir takım arkadaşı bir yorumda sizden @ ile bahsettiğinde burada görünür." + }, + "assigned": { + "title": "Size atanan hiçbir şey yok", + "subtitle": "Size atanan iş öğeleri burada görünecek." + }, + "noResults": { + "title": "Eşleşme yok", + "subtitle": "“{{query}}” ile eşleşen öğe yok." + } + }, + "loading": "Ekip Gelen Kutusu yükleniyor…", + "drop": { + "title": "İş öğesi oluşturmak için buraya bırakın", + "subtitle": "Önce oturum anlık görüntüsünü inceleyin, ardından iş öğesini oluşturun veya devredin.", + "processing": "“{{title}}” içinden iş öğesi oluşturuluyor…", + "processingHint": "Oturum okunuyor ve proje üyeleri çözümleniyor.", + "success": "İş öğesi oluşturuldu", + "reused": "Mevcut iş öğesi güncellendi", + "failed": "İş öğesi oluşturulamadı", + "error": "Bu oturumdan bir iş öğesi oluşturulamıyor.", + "open": "Aç", + "dismiss": "Kapat" + }, + "handoff": { + "title": "Oturumdan oluştur", + "createFromSession": "Ekip iş öğesi oluştur…", + "project": "Hedef proje", + "chooseProject": "Proje seçin", + "recipientSelf": "{{name}} (ben)", + "chooseRecipient": "Alıcı seçin", + "todoCount_one": "{{count}} yapılacak", + "todoCount_other": "{{count}} yapılacak", + "workItemTitle": "İş öğesi başlığı", + "assignTo": "Şuna ata", + "note": "Devir notu", + "notePlaceholder": "Neyin hazır olduğunu, neyin çözülmediğini ve sonra ne olması gerektiğini paylaşın.", + "selfHint": "Bunu kendinize atamak, devir talebi olmayan normal bir iş öğesi oluşturur.", + "submitHandoff": "Oluştur ve devret", + "submitCreate": "İş öğesi oluştur", + "preparing": "“{{title}}” hazırlanıyor…", + "preparationError": { + "session_unavailable": "Bu oturum artık kullanılamıyor. Yeniden açıp tekrar deneyin.", + "project_unavailable": "Bu oturumun projesi artık kullanılamıyor.", + "identity_unavailable": "Kimliğiniz bu oturumun projesinin üyesi değil.", + "no_project": "Uygun bir proje yok. Bir proje oluşturun veya katılın, ardından tekrar deneyin.", + "unknown": "Bu oturum hazırlanamıyor. Ekip Gelen Kutusunu yenileyip tekrar deneyin." + }, + "submitError": "İş öğesi oluşturulamadı. Alıcıyı kontrol edip tekrar deneyin.", + "pendingTitle": "{{name}} kişisinden devir", + "acceptedTitle": "{{name}} tarafından kabul edildi", + "returnedTitle": "{{name}} tarafından iade edildi", + "noNote": "Devir notu yok.", + "statusLabel": "İş öğesi devri", + "return": "İade et", + "accept": "Kabul et", + "returnTitle": "Bu devir iade edilsin mi?", + "confirmReturn": "Gönderene iade et", + "returnHint": "{{name}} kişisine neyin netleştirilmesi veya takip edilmesi gerektiğini belirtin. İş öğesi kendisine yeniden atanacak.", + "returnPlaceholder": "Bir sonraki devirden önce neyin değişmesi gerekiyor?", + "responseError": "Devir yanıtı kaydedilemedi. Tekrar deneyin.", + "identityUnavailable": "Ekip kimliğiniz doğrulanamadı. Yanıtlamadan önce proje profilinizi kontrol edin.", + "rowPending": "{{name}} kişisinden · Yanıt bekleniyor", + "rowAccepted": "Devir kabul edildi · {{status}} · {{priority}}", + "rowReturned": "{{name}} tarafından iade edildi · {{status}} · {{priority}}" + }, + "errors": { + "loadTitle": "Ekip Gelen Kutusu yüklenemedi", + "load": "Ekip Gelen Kutusu yüklenemedi", + "loadMore": "Daha fazla Ekip Gelen Kutusu öğesi yüklenemedi. Tekrar deneyin.", + "refresh": "Ekip Gelen Kutusu yenilenemedi", + "markRead": "Bu öğe okundu olarak işaretlenemedi. Tekrar deneyin.", + "markUnread": "Bu öğe okunmadı olarak işaretlenemedi. Tekrar deneyin.", + "markAllRead": "Tüm öğeler okundu olarak işaretlenemedi. Tekrar deneyin.", + "identity": "Hesabınız bir proje üyesiyle eşleştirilemedi. Proje profilinizdeki e-postayı kontrol edin.", + "partialLoad": "Bazı Ekip Gelen Kutusu kaynakları yenilenemedi. Kullanılabilir öğeler gösterilmeye devam ediyor.", + "workItemContext": "Bazı proje bağlamı kullanılamıyor. İş öğesi kullanılabilir durumda kalır.", + "workItemLoad": "Bu iş öğesi yüklenemedi. Tekrar deneyin.", + "workItemUpdate": "İş öğesindeki son değişiklik kaydedilemedi. Tekrar deneyin." + }, + "detail": { + "assignedSubtitle": "Atanan iş öğesi", + "standaloneProject": "Bağımsız", + "mentionSubtitle": "Bir yorumda bahsedildi", + "mentionedYou": "sizden bahsetti", + "threadComments_one": "Bu konuda {{count}} yorum var", + "threadComments_other": "Bu konuda {{count}} yorum var" + }, + "actions": { + "markRead": "Okundu olarak işaretle", + "markUnread": "Okunmadı olarak işaretle", + "openWorkItem": "İş öğesini aç", + "openSession": "Oturumu aç" + }, + "fields": { + "status": "Durum", + "priority": "Öncelik", + "assignee": "Atanan kişi", + "workItemId": "İş öğesi kimliği", + "session": "Oturum", + "comments": "Yorumlar", + "threadId": "Konu kimliği", + "commentId": "Yorum kimliği" + }, + "workItemStatus": { + "backlog": "Birikmiş İşler", + "todo": "Yapılacak", + "in_progress": "Devam Ediyor", + "in_review": "İncelemede", + "blocked": "Engellendi", + "done": "Tamamlandı", + "cancelled": "İptal Edildi" + }, + "priority": { + "none": "Öncelik yok", + "low": "Düşük", + "medium": "Orta", + "high": "Yüksek", + "urgent": "Acil" + } + }, "toasts": { "replyEmpty": "Yanıt boş olamaz", "sessionNotFound": "Oturum bulunamadı", diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 7ebb0b930..4c0129f49 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -1,4 +1,160 @@ { + "teamInbox": { + "title": "Hộp thư nhóm", + "listLabel": "Danh sách hộp thư nhóm", + "itemsLabel": "Mục trong hộp thư nhóm", + "unreadCount": "{{count}} chưa đọc", + "allRead": "Đã xem hết", + "loadMore": "Tải thêm", + "filters": { + "all": "Tất cả", + "mentions": "Được nhắc đến", + "assigned": "Được giao" + }, + "status": { + "read": "Đã đọc", + "unread": "Chưa đọc" + }, + "row": { + "assignedSummary": "{{status}} · {{priority}}", + "ariaLabel": "{{title}}, {{status}}" + }, + "search": { + "placeholder": "Tìm trong hộp thư", + "ariaLabel": "Tìm trong hộp thư nhóm" + }, + "groups": { + "today": "Hôm nay", + "yesterday": "Hôm qua", + "thisWeek": "Tuần này", + "earlier": "Trước đó" + }, + "empty": { + "title": "Chưa có gì ở đây", + "subtitle": "Các lượt nhắc đến và mục công việc được giao sẽ xuất hiện ở đây.", + "selectTitle": "Chọn một mục", + "selectSubtitle": "Xem ngữ cảnh bình luận hoặc chi tiết mục công việc.", + "mentions": { + "title": "Không có lượt nhắc đến nào", + "subtitle": "Khi đồng đội nhắc đến bạn bằng @ trong bình luận, mục đó sẽ xuất hiện ở đây." + }, + "assigned": { + "title": "Không có gì được giao cho bạn", + "subtitle": "Các mục công việc được giao cho bạn sẽ xuất hiện ở đây." + }, + "noResults": { + "title": "Không có kết quả phù hợp", + "subtitle": "Không có mục nào khớp với “{{query}}”." + } + }, + "loading": "Đang tải hộp thư nhóm…", + "drop": { + "title": "Thả vào đây để tạo mục công việc", + "subtitle": "Xem lại ảnh chụp phiên làm việc, sau đó tạo hoặc bàn giao mục công việc.", + "processing": "Đang tạo mục công việc từ “{{title}}”…", + "processingHint": "Đang đọc phiên làm việc và xác định thành viên dự án.", + "success": "Đã tạo mục công việc", + "reused": "Đã cập nhật mục công việc hiện có", + "failed": "Không thể tạo mục công việc", + "error": "Không thể tạo mục công việc từ phiên làm việc này.", + "open": "Mở", + "dismiss": "Bỏ qua" + }, + "handoff": { + "title": "Tạo từ phiên làm việc", + "createFromSession": "Tạo mục công việc của nhóm…", + "project": "Dự án đích", + "chooseProject": "Chọn dự án", + "recipientSelf": "{{name}} (tôi)", + "chooseRecipient": "Chọn người nhận", + "todoCount_other": "{{count}} việc cần làm", + "workItemTitle": "Tiêu đề mục công việc", + "assignTo": "Giao cho", + "note": "Ghi chú bàn giao", + "notePlaceholder": "Cho biết những gì đã sẵn sàng, những gì còn tồn đọng và bước tiếp theo nên là gì.", + "selfHint": "Giao việc này cho chính bạn sẽ tạo một mục công việc bình thường, không có yêu cầu bàn giao.", + "submitHandoff": "Tạo & bàn giao", + "submitCreate": "Tạo mục công việc", + "preparing": "Đang chuẩn bị “{{title}}”…", + "preparationError": { + "session_unavailable": "Phiên làm việc này không còn khả dụng. Hãy mở lại và thử lại.", + "project_unavailable": "Dự án của phiên làm việc này không còn khả dụng.", + "identity_unavailable": "Danh tính của bạn không phải là thành viên của dự án thuộc phiên làm việc này.", + "no_project": "Không có dự án phù hợp nào khả dụng. Hãy tạo hoặc tham gia một dự án rồi thử lại.", + "unknown": "Không thể chuẩn bị phiên làm việc này. Hãy làm mới hộp thư nhóm rồi thử lại." + }, + "submitError": "Không thể tạo mục công việc. Hãy kiểm tra người nhận rồi thử lại.", + "pendingTitle": "Bàn giao từ {{name}}", + "acceptedTitle": "Đã được {{name}} chấp nhận", + "returnedTitle": "Đã được {{name}} trả lại", + "noNote": "Không có ghi chú bàn giao.", + "statusLabel": "Bàn giao mục công việc", + "return": "Trả lại", + "accept": "Chấp nhận", + "returnTitle": "Trả lại lượt bàn giao này?", + "confirmReturn": "Trả lại cho người gửi", + "returnHint": "Cho {{name}} biết điều gì cần làm rõ hoặc theo dõi thêm. Mục công việc sẽ được giao lại cho họ.", + "returnPlaceholder": "Cần thay đổi điều gì trước khi bàn giao lại?", + "responseError": "Không thể lưu phản hồi bàn giao. Hãy thử lại.", + "identityUnavailable": "Không thể xác minh danh tính nhóm của bạn. Hãy kiểm tra hồ sơ dự án trước khi phản hồi.", + "rowPending": "Từ {{name}} · Đang chờ phản hồi", + "rowAccepted": "Đã chấp nhận bàn giao · {{status}} · {{priority}}", + "rowReturned": "Đã được {{name}} trả lại · {{status}} · {{priority}}" + }, + "errors": { + "loadTitle": "Không thể tải hộp thư nhóm", + "load": "Không thể tải hộp thư nhóm", + "loadMore": "Không thể tải thêm mục trong hộp thư nhóm. Hãy thử lại.", + "refresh": "Không thể làm mới hộp thư nhóm", + "markRead": "Không thể đánh dấu mục này là đã đọc. Hãy thử lại.", + "markUnread": "Không thể đánh dấu mục này là chưa đọc. Hãy thử lại.", + "markAllRead": "Không thể đánh dấu tất cả mục là đã đọc. Hãy thử lại.", + "identity": "Không thể khớp tài khoản của bạn với thành viên dự án. Hãy kiểm tra email trong hồ sơ dự án của bạn.", + "partialLoad": "Một số nguồn của hộp thư nhóm không thể làm mới. Các mục khả dụng vẫn được hiển thị.", + "workItemContext": "Một phần ngữ cảnh dự án không khả dụng. Mục công việc vẫn có thể sử dụng được.", + "workItemLoad": "Không thể tải mục công việc này. Hãy thử lại.", + "workItemUpdate": "Không thể lưu thay đổi mới nhất của mục công việc. Hãy thử lại." + }, + "detail": { + "assignedSubtitle": "Mục công việc được giao", + "standaloneProject": "Độc lập", + "mentionSubtitle": "Được nhắc đến trong một bình luận", + "mentionedYou": "đã nhắc đến bạn", + "threadComments_other": "{{count}} bình luận trong chuỗi này" + }, + "actions": { + "markRead": "Đánh dấu đã đọc", + "markUnread": "Đánh dấu chưa đọc", + "openWorkItem": "Mở mục công việc", + "openSession": "Mở phiên làm việc" + }, + "fields": { + "status": "Trạng thái", + "priority": "Mức độ ưu tiên", + "assignee": "Người phụ trách", + "workItemId": "ID mục công việc", + "session": "Phiên làm việc", + "comments": "Bình luận", + "threadId": "ID chuỗi", + "commentId": "ID bình luận" + }, + "workItemStatus": { + "backlog": "Tồn đọng", + "todo": "Cần làm", + "in_progress": "Đang thực hiện", + "in_review": "Đang xem xét", + "blocked": "Bị chặn", + "done": "Hoàn thành", + "cancelled": "Đã hủy" + }, + "priority": { + "none": "Không ưu tiên", + "low": "Thấp", + "medium": "Trung bình", + "high": "Cao", + "urgent": "Khẩn cấp" + } + }, "toasts": { "replyEmpty": "Câu trả lời không được để trống", "sessionNotFound": "Không tìm thấy phiên", diff --git a/src/i18n/locales/zh-Hant/common.json b/src/i18n/locales/zh-Hant/common.json index ccf142f07..e08976b3a 100644 --- a/src/i18n/locales/zh-Hant/common.json +++ b/src/i18n/locales/zh-Hant/common.json @@ -1,4 +1,160 @@ { + "teamInbox": { + "title": "團隊收件匣", + "listLabel": "團隊收件匣清單", + "itemsLabel": "團隊收件匣項目", + "unreadCount": "{{count}} 則未讀", + "allRead": "已全部閱讀", + "loadMore": "載入更多", + "filters": { + "all": "全部", + "mentions": "提及", + "assigned": "已指派給我" + }, + "status": { + "read": "已讀", + "unread": "未讀" + }, + "row": { + "assignedSummary": "{{status}} · {{priority}}", + "ariaLabel": "{{title}},{{status}}" + }, + "search": { + "placeholder": "搜尋收件匣", + "ariaLabel": "搜尋團隊收件匣" + }, + "groups": { + "today": "今天", + "yesterday": "昨天", + "thisWeek": "本週", + "earlier": "更早" + }, + "empty": { + "title": "目前尚無項目", + "subtitle": "新的提及與指派的工作項目會顯示在這裡。", + "selectTitle": "選擇一個項目", + "selectSubtitle": "查看留言脈絡或工作項目詳情。", + "mentions": { + "title": "尚無提及", + "subtitle": "當同事在留言中以 @ 提及你時,會顯示在這裡。" + }, + "assigned": { + "title": "尚無指派給你的項目", + "subtitle": "指派給你的工作項目會顯示在這裡。" + }, + "noResults": { + "title": "沒有相符結果", + "subtitle": "沒有與「{{query}}」相符的項目。" + } + }, + "loading": "正在載入團隊收件匣…", + "drop": { + "title": "拖曳到這裡以建立工作項目", + "subtitle": "先確認工作階段快照,再建立或交接工作項目。", + "processing": "正在從「{{title}}」建立工作項目…", + "processingHint": "正在讀取工作階段並解析專案成員。", + "success": "工作項目已建立", + "reused": "已更新現有工作項目", + "failed": "無法建立工作項目", + "error": "無法根據此工作階段建立工作項目。", + "open": "開啟", + "dismiss": "關閉" + }, + "handoff": { + "title": "從工作階段建立", + "createFromSession": "建立團隊工作項目…", + "project": "目標專案", + "chooseProject": "選擇專案", + "recipientSelf": "{{name}}(我)", + "chooseRecipient": "選擇接收人", + "todoCount_other": "{{count}} 項待辦", + "workItemTitle": "工作項目標題", + "assignTo": "指派給", + "note": "交接說明", + "notePlaceholder": "說明已完成的事項、尚未解決的部分,以及接下來該做什麼。", + "selfHint": "指派給自己會建立一般工作項目,不會發出交接請求。", + "submitHandoff": "建立並交接", + "submitCreate": "建立工作項目", + "preparing": "正在準備「{{title}}」…", + "preparationError": { + "session_unavailable": "此工作階段已無法使用,請重新開啟後再試一次。", + "project_unavailable": "此工作階段所屬的專案已無法使用。", + "identity_unavailable": "你的身分並非此工作階段所屬專案的成員。", + "no_project": "沒有可用的合適專案,請先建立或加入專案後再試一次。", + "unknown": "無法準備此工作階段,請重新整理團隊收件匣後再試一次。" + }, + "submitError": "無法建立工作項目,請檢查接收人後再試一次。", + "pendingTitle": "來自 {{name}} 的交接", + "acceptedTitle": "{{name}} 已接受", + "returnedTitle": "{{name}} 已退回", + "noNote": "沒有交接說明。", + "statusLabel": "工作項目交接", + "return": "退回", + "accept": "接受", + "returnTitle": "要退回這次交接嗎?", + "confirmReturn": "退回給發送者", + "returnHint": "告訴 {{name}} 需要釐清或補充的內容,工作項目將重新指派給對方。", + "returnPlaceholder": "在再次交接之前需要調整什麼?", + "responseError": "無法儲存交接回應,請再試一次。", + "identityUnavailable": "無法驗證你的團隊身分,請先檢查專案個人資料再回應。", + "rowPending": "來自 {{name}} · 等待回應", + "rowAccepted": "已接受交接 · {{status}} · {{priority}}", + "rowReturned": "{{name}} 已退回 · {{status}} · {{priority}}" + }, + "errors": { + "loadTitle": "無法載入團隊收件匣", + "load": "無法載入團隊收件匣", + "loadMore": "無法載入更多團隊收件匣項目,請再試一次。", + "refresh": "無法重新整理團隊收件匣", + "markRead": "無法將此項目標記為已讀,請再試一次。", + "markUnread": "無法將此項目標記為未讀,請再試一次。", + "markAllRead": "無法將所有項目標記為已讀,請再試一次。", + "identity": "無法將你的帳戶與專案成員配對,請檢查專案個人資料中的電子郵件。", + "partialLoad": "部分團隊收件匣來源無法重新整理,現有項目仍會繼續顯示。", + "workItemContext": "部分專案內容暫時無法使用,工作項目仍可繼續操作。", + "workItemLoad": "無法載入此工作項目,請再試一次。", + "workItemUpdate": "無法儲存工作項目的最新變更,請再試一次。" + }, + "detail": { + "assignedSubtitle": "已指派的工作項目", + "standaloneProject": "獨立項目", + "mentionSubtitle": "在留言中被提及", + "mentionedYou": "提及了你", + "threadComments_other": "此討論串中有 {{count}} 則留言" + }, + "actions": { + "markRead": "標記為已讀", + "markUnread": "標記為未讀", + "openWorkItem": "開啟工作項目", + "openSession": "開啟工作階段" + }, + "fields": { + "status": "狀態", + "priority": "優先順序", + "assignee": "負責人", + "workItemId": "工作項目 ID", + "session": "工作階段", + "comments": "留言", + "threadId": "討論串 ID", + "commentId": "留言 ID" + }, + "workItemStatus": { + "backlog": "待辦清單", + "todo": "待辦", + "in_progress": "進行中", + "in_review": "審核中", + "blocked": "受阻", + "done": "已完成", + "cancelled": "已取消" + }, + "priority": { + "none": "無優先順序", + "low": "低", + "medium": "中", + "high": "高", + "urgent": "緊急" + } + }, "toasts": { "replyEmpty": "回覆不能為空", "sessionNotFound": "找不到 Session", diff --git a/src/modules/MainApp/TeamInbox/TeamInboxView.tsx b/src/modules/MainApp/TeamInbox/TeamInboxView.tsx index 40fc601e9..c40028c79 100644 --- a/src/modules/MainApp/TeamInbox/TeamInboxView.tsx +++ b/src/modules/MainApp/TeamInbox/TeamInboxView.tsx @@ -30,6 +30,7 @@ import { selectTeamInboxItems, toTeamInboxNavigationIntent, } from "./domain"; +import { performTeamInboxReadTransition } from "./teamInboxReadTransitions"; export interface TeamInboxViewProps { dataSource?: TeamInboxDataSource; @@ -210,32 +211,44 @@ const TeamInboxView: React.FC = ({ const handleSelect = (item: TeamInboxItem) => { setRequestedItemId(getTeamInboxItemKey(item)); if (item.readAt !== null) return; - void dataSource.markRead?.(item).catch(() => { - setLoadState({ - status: "error", - message: t("teamInbox.errors.markRead"), - }); - }); + void performTeamInboxReadTransition("read", item, dataSource).then( + (result) => { + if (!result.ok) { + setLoadState({ + status: "error", + message: t("teamInbox.errors.markRead"), + }); + } + } + ); }; const handleMarkRead = (item: TeamInboxItem) => { if (item.readAt !== null) return; - void dataSource.markRead?.(item).catch(() => { - setLoadState({ - status: "error", - message: t("teamInbox.errors.markRead"), - }); - }); + void performTeamInboxReadTransition("read", item, dataSource).then( + (result) => { + if (!result.ok) { + setLoadState({ + status: "error", + message: t("teamInbox.errors.markRead"), + }); + } + } + ); }; const handleMarkUnread = (item: TeamInboxItem) => { if (item.readAt === null) return; - void dataSource.markUnread?.(item).catch(() => { - setLoadState({ - status: "error", - message: t("teamInbox.errors.markUnread"), - }); - }); + void performTeamInboxReadTransition("unread", item, dataSource).then( + (result) => { + if (!result.ok) { + setLoadState({ + status: "error", + message: t("teamInbox.errors.markUnread"), + }); + } + } + ); }; const handleMarkAllRead = () => { diff --git a/src/modules/MainApp/TeamInbox/__tests__/teamInboxReadTransitions.test.ts b/src/modules/MainApp/TeamInbox/__tests__/teamInboxReadTransitions.test.ts new file mode 100644 index 000000000..339e9ddf4 --- /dev/null +++ b/src/modules/MainApp/TeamInbox/__tests__/teamInboxReadTransitions.test.ts @@ -0,0 +1,92 @@ +import { describe, expect, it, vi } from "vitest"; + +import type { TeamInboxItem } from "../domain"; +import { performTeamInboxReadTransition } from "../teamInboxReadTransitions"; + +function assignedItem(readAt: string | null): TeamInboxItem { + return { + kind: "assigned_work_item", + id: "wi-1", + occurredAt: "2026-07-29T00:00:00.000Z", + readAt, + workItem: { + id: "wi-1", + title: "Ship the thing", + status: "todo", + priority: "medium", + }, + } as unknown as TeamInboxItem; +} + +describe("performTeamInboxReadTransition", () => { + it("resolves ok without touching refresh on the success path", async () => { + const markRead = vi.fn().mockResolvedValue(undefined); + const refresh = vi.fn().mockResolvedValue(undefined); + + const result = await performTeamInboxReadTransition( + "read", + assignedItem(null), + { markRead, refresh } + ); + + expect(result).toEqual({ ok: true, resyncStarted: false }); + expect(markRead).toHaveBeenCalledTimes(1); + expect(refresh).not.toHaveBeenCalled(); + }); + + it("triggers a data source refresh when the mutation rejects", async () => { + const markRead = vi.fn().mockRejectedValue(new Error("network down")); + const refresh = vi.fn().mockResolvedValue(undefined); + + const result = await performTeamInboxReadTransition( + "read", + assignedItem(null), + { markRead, refresh } + ); + + expect(result).toEqual({ ok: false, resyncStarted: true }); + expect(refresh).toHaveBeenCalledTimes(1); + }); + + it("still reports resyncStarted even if the refresh itself fails", async () => { + const markUnread = vi + .fn() + .mockRejectedValue(new Error("no longer visible")); + const refresh = vi.fn().mockRejectedValue(new Error("offline")); + + const result = await performTeamInboxReadTransition( + "unread", + assignedItem("2026-07-28T00:00:00.000Z"), + { markUnread, refresh } + ); + + expect(result).toEqual({ ok: false, resyncStarted: true }); + expect(markUnread).toHaveBeenCalledTimes(1); + expect(refresh).toHaveBeenCalledTimes(1); + }); + + it("does not attempt a resync when the data source has no refresh", async () => { + const markRead = vi.fn().mockRejectedValue(new Error("boom")); + + const result = await performTeamInboxReadTransition( + "read", + assignedItem(null), + { markRead } + ); + + expect(result).toEqual({ ok: false, resyncStarted: false }); + }); + + it("is a no-op when the data source has no mutator for the requested kind", async () => { + const refresh = vi.fn(); + + const result = await performTeamInboxReadTransition( + "unread", + assignedItem(null), + { refresh } + ); + + expect(result).toEqual({ ok: true, resyncStarted: false }); + expect(refresh).not.toHaveBeenCalled(); + }); +}); diff --git a/src/modules/MainApp/TeamInbox/teamInboxCoordinator.ts b/src/modules/MainApp/TeamInbox/teamInboxCoordinator.ts index adc50adbb..f854932da 100644 --- a/src/modules/MainApp/TeamInbox/teamInboxCoordinator.ts +++ b/src/modules/MainApp/TeamInbox/teamInboxCoordinator.ts @@ -606,6 +606,18 @@ export class TeamInboxCoordinator { ): Promise { const runtime = this.ensureScope(store, scope.key); const itemKey = getTeamInboxItemKey(item); + // Idempotency lives here, not just at the call site, so any caller — + // including future ones — can't double-fire a read/unread mutation and + // send a redundant network round-trip that risks drifting unreadCount. + // Check the live cache rather than the passed-in `item`, which may be a + // stale snapshot from the caller's render. + const currentItem = store + .get(teamInboxCacheAtom) + .items.find((candidate) => getTeamInboxItemKey(candidate) === itemKey); + const currentlyRead = (currentItem ?? item).readAt !== null; + if (currentlyRead === read) { + return Promise.resolve(); + } const epoch = ++runtime.mutationEpoch; runtime.mutationEpochByItem.set(itemKey, epoch); this.patchReadState( diff --git a/src/modules/MainApp/TeamInbox/teamInboxReadTransitions.ts b/src/modules/MainApp/TeamInbox/teamInboxReadTransitions.ts new file mode 100644 index 000000000..4194bc25e --- /dev/null +++ b/src/modules/MainApp/TeamInbox/teamInboxReadTransitions.ts @@ -0,0 +1,53 @@ +import type { TeamInboxDataSource, TeamInboxItem } from "./domain"; + +export type TeamInboxReadTransitionKind = "read" | "unread"; + +export interface TeamInboxReadTransitionResult { + ok: boolean; + /** + * Only meaningful when `ok` is false. `true` means a resync against the + * data source's server-truth state was kicked off after the failed + * optimistic mutation (regardless of whether that resync itself + * succeeded) so the caller knows a follow-up render is coming. + */ + resyncStarted: boolean; +} + +/** + * Drives a single Team Inbox mark-read / mark-unread mutation. + * + * The data source (`teamInboxCoordinator`) already reverts its own + * optimistic item mutation when the underlying local/cloud call rejects. + * This helper adds the second half of that contract: it also triggers a + * `refresh()` on failure so the list and unread badge resync against + * server truth instead of relying solely on the coordinator's local + * revert, which guards against drift if the revert races with another + * concurrent update (e.g. a mutation from another client). It is kept + * framework-free (no React) so it can be unit tested directly — repo + * policy is not to unit test `.tsx` files. + */ +export async function performTeamInboxReadTransition( + kind: TeamInboxReadTransitionKind, + item: TeamInboxItem, + dataSource: Pick +): Promise { + const mutate = kind === "read" ? dataSource.markRead : dataSource.markUnread; + if (!mutate) return { ok: true, resyncStarted: false }; + + try { + await mutate(item); + return { ok: true, resyncStarted: false }; + } catch { + if (!dataSource.refresh) return { ok: false, resyncStarted: false }; + // Best-effort: the resync's own outcome doesn't change the fact that + // the mutation itself failed and the caller still needs to surface an + // error — it only affects whether a follow-up render is coming. + try { + await dataSource.refresh(); + } catch { + // Swallow: the refresh failure is secondary to the mutation failure + // already being reported via `ok: false`. + } + return { ok: false, resyncStarted: true }; + } +} diff --git a/src/scaffold/NavigationSidebar/connectors/WorkstationSidebarConnector/sidebarMenuCollections.ts b/src/scaffold/NavigationSidebar/connectors/WorkstationSidebarConnector/sidebarMenuCollections.ts index 21dd54714..e1c6ca6af 100644 --- a/src/scaffold/NavigationSidebar/connectors/WorkstationSidebarConnector/sidebarMenuCollections.ts +++ b/src/scaffold/NavigationSidebar/connectors/WorkstationSidebarConnector/sidebarMenuCollections.ts @@ -51,6 +51,13 @@ export function usePinnedMenuItems({ workItemDestinations, t, }: UsePinnedMenuItemsParams): UsePinnedMenuItemsResult { + const teamInboxUnreadAriaLabel = useMemo( + () => + teamInboxUnreadCount + ? t("common:teamInbox.unreadCount", { count: teamInboxUnreadCount }) + : undefined, + [t, teamInboxUnreadCount] + ); const sessionPinnedMenuItems = useMemo( () => buildPinnedMenuItems({ @@ -63,6 +70,7 @@ export function usePinnedMenuItems({ runtimeLabel, teamInboxLabel, teamInboxUnreadCount, + teamInboxUnreadAriaLabel, }), [ kanbanLabel, @@ -70,6 +78,7 @@ export function usePinnedMenuItems({ runtimeLabel, teamInboxLabel, teamInboxUnreadCount, + teamInboxUnreadAriaLabel, workItemDestinations, t, ] diff --git a/src/scaffold/NavigationSidebar/connectors/workstationSidebarMenuItems.test.ts b/src/scaffold/NavigationSidebar/connectors/workstationSidebarMenuItems.test.ts index d65b85290..af28a7a2a 100644 --- a/src/scaffold/NavigationSidebar/connectors/workstationSidebarMenuItems.test.ts +++ b/src/scaffold/NavigationSidebar/connectors/workstationSidebarMenuItems.test.ts @@ -1,3 +1,4 @@ +import type React from "react"; import { describe, expect, it } from "vitest"; import { @@ -53,6 +54,45 @@ describe("buildPinnedMenuItems", () => { expect(items[0]?.openContextMenuOnSelectedClick).toBeUndefined(); }); + it("translates the unread badge aria-label when one is supplied", () => { + const items = buildPinnedMenuItems({ + newSessionLabel: "New Session", + newSessionShortcut: "⌘N", + workItemsLabel: "Work Items", + workItemDestinations: [], + kanbanLabel: "Kanban", + kanbanShortcut: "⌘O", + runtimeLabel: "Runtime", + teamInboxLabel: "Team Inbox", + teamInboxUnreadCount: 3, + teamInboxUnreadAriaLabel: "3 no leídos", + }); + + const badge = items[3]?.trailingElement as React.ReactElement<{ + "aria-label"?: string; + }>; + expect(badge?.props["aria-label"]).toBe("3 no leídos"); + }); + + it("falls back to an English badge aria-label when none is supplied", () => { + const items = buildPinnedMenuItems({ + newSessionLabel: "New Session", + newSessionShortcut: "⌘N", + workItemsLabel: "Work Items", + workItemDestinations: [], + kanbanLabel: "Kanban", + kanbanShortcut: "⌘O", + runtimeLabel: "Runtime", + teamInboxLabel: "Team Inbox", + teamInboxUnreadCount: 5, + }); + + const badge = items[3]?.trailingElement as React.ReactElement<{ + "aria-label"?: string; + }>; + expect(badge?.props["aria-label"]).toBe("5 unread"); + }); + it("keeps destination navigation available inside the Work Items layer", () => { const items = buildProjectsPinnedMenuItems({ createProjectLabel: "Create Project", diff --git a/src/scaffold/NavigationSidebar/connectors/workstationSidebarMenuItems.tsx b/src/scaffold/NavigationSidebar/connectors/workstationSidebarMenuItems.tsx index afb89c26d..7ec57ad16 100644 --- a/src/scaffold/NavigationSidebar/connectors/workstationSidebarMenuItems.tsx +++ b/src/scaffold/NavigationSidebar/connectors/workstationSidebarMenuItems.tsx @@ -38,6 +38,7 @@ interface BuildPinnedMenuItemsParams { runtimeLabel: string; teamInboxLabel: string; teamInboxUnreadCount?: number; + teamInboxUnreadAriaLabel?: string; } interface BuildProjectsPinnedMenuItemsParams { @@ -57,6 +58,7 @@ export function buildPinnedMenuItems({ runtimeLabel, teamInboxLabel, teamInboxUnreadCount = 0, + teamInboxUnreadAriaLabel, }: BuildPinnedMenuItemsParams): NavigationMenuItem[] { return [ { @@ -94,7 +96,9 @@ export function buildPinnedMenuItems({ trailingElement: teamInboxUnreadCount > 0 ? ( {teamInboxUnreadCount > 99 ? "99+" : teamInboxUnreadCount}