diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index d1f4a3d5ce9..5a673157558 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -522,7 +522,10 @@ "public": "Public", "private": "Private" }, + "done": "Done", "sub_work_items": "Sub-work items", + "comment": "Comment", + "workspace_level": "Workspace level", "order_by": { "label": "Order by", "manual": "Manual", @@ -543,7 +546,9 @@ "comments": "Comments", "updates": "Updates", "clear_all": "Clear all", - "link_copied": "Link copied", + "copied": "Copied!", + "link_copied": "Link copied!", + "link_copied_to_clipboard": "Link copied to clipboard", "copied_to_clipboard": "Work item link copied to clipboard", "is_copied_to_clipboard": "Work item is copied to clipboard", "no_links_added_yet": "No links added yet", @@ -556,6 +561,7 @@ "go_back": "Go back", "continue": "Continue", "resend": "Resend", + "relations": "Relations", "errors": { "default": { "title": "Error!", @@ -571,6 +577,18 @@ "url_is_invalid": "URL is invalid", "display_title": "Display title", "link_title_placeholder": "What you'd like to see this link as", + "url": "URL", + "side_peek": "Side Peek", + "modal": "Modal", + "full_screen": "Full Screen", + "close_peek_view": "Close the peek view", + "toggle_peek_view_layout": "Toggle peek view layout", + "options": "Options", + "duration": "Duration", + "today": "Today", + "week": "Week", + "month": "Month", + "quarter": "Quarter", "search": { "label": "Search", "placeholder": "Type to search...", @@ -583,7 +601,12 @@ "open_in_new_tab": "Open in new tab", "copy_link": "Copy link", "archive": "Archive", - "delete": "Delete" + "delete": "Delete", + "remove_relation": "Remove relation", + "subscribe": "Subscribe", + "unsubscribe": "Unsubscribe", + "clear_sorting": "Clear sorting", + "show_weekends": "Show weekends" } }, @@ -596,7 +619,7 @@ "entity": { "grouping_title": "{entity} Grouping", - "priority": "{entity} ", + "priority": "{entity} Priority", "all": "All {entity}", "drop_here_to_move": "Drop here to move the {entity}", "delete": { @@ -605,7 +628,16 @@ "failed": "{entity} delete failed" }, "update": { - "failed": "{entity} update failed" + "failed": "{entity} update failed", + "success": "{entity} updated successfully" + }, + "link_copied_to_clipboard": "{entity} link copied to clipboard", + "fetch": { + "failed": "Error fetching {entity}" + }, + "add": { + "success": "{entity} added successfully", + "failed": "Error adding {entity}" } }, @@ -618,7 +650,8 @@ "success": "Epic created successfully" }, "add": { - "press_enter": "Press 'Enter' to add another epic" + "press_enter": "Press 'Enter' to add another epic", + "label": "Add Epic" }, "title": { "label": "Epic Title", @@ -629,6 +662,7 @@ "issue": { "label": "{count, plural, one {Work item} other {Work items}}", "all": "All Work items", + "edit": "Edit work item", "title": { "label": "Work item title", "required": "Work item title is required." @@ -637,7 +671,9 @@ "press_enter": "Press 'Enter' to add another work item", "label": "Add Work item", "cycle": { - "failed": "Work item could not be added to the cycle. Please try again." + "failed": "Work item could not be added to the cycle. Please try again.", + "success": "{count, plural, one {Work item} other {Work items}} added to the cycle successfully.", + "loading": "Adding {count, plural, one {work item} other {work items}} to the cycle..." }, "assignee": "Add assignees", "start_date": "Add start date", @@ -645,9 +681,11 @@ "parent": "Add parent work item", "sub_issue": "Add sub-work item", "relation": "Add relation", - "link": "Add link" + "link": "Add link", + "existing": "Add existing work item" }, "remove": { + "label": "Remove work item", "cycle": { "loading": "Removing work item from the cycle...", "success": "Work item removed from the cycle successfully.", @@ -708,6 +746,7 @@ "backlog": "Backlog" }, "comments": { + "placeholder": "Add comment...", "create": { "success": "Comment created successfully", "error": "Comment creation failed. Please try again later." @@ -748,11 +787,66 @@ "message": "Work item could not be archived. Please try again." } }, + "restore": { + "success": { + "title": "Restore success", + "message": "Your work item can be found in project work items." + }, + "failed": { + "message": "Work item could not be restored. Please try again." + } + }, "relation": { "relates_to": "Relates to", "duplicate": "Duplicate of", "blocked_by": "Blocked by", "blocking": "Blocking" + }, + "copy_link": "Copy work item link", + "delete": { + "label": "Delete work item", + "error": "Error deleting work item" + }, + "subscription": { + "actions": { + "subscribed": "Work item subscribed successfully", + "unsubscribed": "Work item unsubscribed successfully" + } + }, + "select": { + "error": "Please select at least one work item", + "empty": "No work items selected", + "add_selected": "Add selected issues" + }, + "open_in_full_screen": "Open work item in full screen" + }, + + "attachment": { + "error": "File could not be attached. Try uploading again.", + "only_one_file_allowed": "Only one file can be uploaded at a time.", + "file_size_limit": "File must be of {size}MB or less in size.", + "drag_and_drop": "Drag and drop anywhere to upload", + "delete": "Delete attachment" + }, + + "label": { + "select": "Select label", + "create": { + "success": "Label created successfully", + "failed": "Label creation failed", + "already_exists": "Label already exists", + "type": "Type to add a new label" + } + }, + + "sub_work_item": { + "update": { + "success": "Sub-work item updated successfully", + "error": "Error updating sub-work item" + }, + "remove": { + "success": "Sub-work item removed successfully", + "error": "Error removing sub-work item" } }, diff --git a/packages/i18n/src/locales/es/translations.json b/packages/i18n/src/locales/es/translations.json index 62b62aff12f..bf5c115cc0d 100644 --- a/packages/i18n/src/locales/es/translations.json +++ b/packages/i18n/src/locales/es/translations.json @@ -522,7 +522,10 @@ "public": "Public", "private": "Private" }, + "done": "Hecho", "sub_work_items": "Sub-work items", + "comment": "Comentario", + "workspace_level": "Nivel del workspace", "order_by": { "label": "Ordenar por", "manual": "Manual", @@ -544,6 +547,8 @@ "updates": "Actualizaciones", "clear_all": "Limpiar todo", "link_copied": "Enlace copiado", + "copied": "Copiado!", + "link_copied_to_clipboard": "Enlace copiado al portapapeles", "copied_to_clipboard": "Enlace del work item copiado al portapapeles", "is_copied_to_clipboard": "Work item copiado al portapapeles", "no_links_added_yet": "No hay enlaces agregados aún", @@ -556,6 +561,7 @@ "go_back": "Volver", "continue": "Continuar", "resend": "Reenviar", + "relations": "Relaciones", "errors": { "default": { "title": "¡Error!", @@ -571,6 +577,18 @@ "url_is_invalid": "La URL no es válida", "display_title": "Título a mostrar", "link_title_placeholder": "Cómo te gustaría ver este enlace", + "url": "URL", + "side_peek": "Peek lateral", + "modal": "Modal", + "full_screen": "Pantalla completa", + "close_peek_view": "Cerrar el peek", + "toggle_peek_view_layout": "Cambiar el diseño del peek", + "options": "Opciones", + "duration": "Duración", + "today": "Hoy", + "week": "Semana", + "month": "Mes", + "quarter": "Trimestre", "search": { "label": "Buscar", "placeholder": "Escribe para buscar...", @@ -583,7 +601,12 @@ "open_in_new_tab": "Abrir en nueva pestaña", "copy_link": "Copiar enlace", "archive": "Archivar", - "delete": "Eliminar" + "delete": "Eliminar", + "remove_relation": "Eliminar relación", + "subscribe": "Suscríbete", + "unsubscribe": "Desuscribir", + "clear_sorting": "Limpiar ordenación", + "show_weekends": "Mostrar fines de semana" } }, @@ -596,7 +619,7 @@ "entity": { "grouping_title": "Agrupación de {entity}", - "priority": "{entity} ", + "priority": "{entity} Prioridad", "all": "Todos los {entity}", "drop_here_to_move": "Suelta aquí para mover el {entity}", "delete": { @@ -605,7 +628,16 @@ "failed": "Error al eliminar {entity}" }, "update": { - "failed": "Error al actualizar {entity}" + "failed": "Error al actualizar {entity}", + "success": "{entity} actualizado correctamente" + }, + "link_copied_to_clipboard": "Enlace de {entity} copiado al portapapeles", + "fetch": { + "failed": "Error al obtener {entity}" + }, + "add": { + "success": "{entity} agregado correctamente", + "failed": "Error al agregar {entity}" } }, @@ -618,7 +650,8 @@ "success": "Epic creado correctamente" }, "add": { - "press_enter": "Presiona 'Enter' para agregar otro epic" + "press_enter": "Presiona 'Enter' para agregar otro epic", + "label": "Agregar Epic" }, "title": { "label": "Título del Epic", @@ -629,6 +662,7 @@ "issue": { "label": "{count, plural, one {Work item} other {Work items}}", "all": "Todos los Work items", + "edit": "Editar work item", "title": { "label": "Título del work item", "required": "El título del work item es obligatorio." @@ -637,7 +671,9 @@ "press_enter": "Presiona 'Enter' para agregar otro work item", "label": "Agregar Work item", "cycle": { - "failed": "No se pudo agregar el work item al cycle. Por favor, inténtalo de nuevo." + "failed": "No se pudo agregar el work item al cycle. Por favor, inténtalo de nuevo.", + "success": "{count, plural, one {Work item} other {Work items}} agregado al cycle correctamente.", + "loading": "Agregando {count, plural, one {work item} other {work items}} al cycle..." }, "assignee": "Agregar asignados", "start_date": "Agregar fecha de inicio", @@ -645,9 +681,11 @@ "parent": "Agregar work item padre", "sub_issue": "Agregar sub-work item", "relation": "Agregar relación", - "link": "Agregar enlace" + "link": "Agregar enlace", + "existing": "Agregar work item existente" }, "remove": { + "label": "Eliminar work item", "cycle": { "loading": "Eliminando work item del cycle...", "success": "Work item eliminado del cycle correctamente.", @@ -708,6 +746,7 @@ "backlog": "Backlog" }, "comments": { + "placeholder": "Añadir comentario...", "create": { "success": "Comentario creado correctamente", "error": "Error al crear el comentario. Por favor, inténtalo más tarde." @@ -748,11 +787,66 @@ "message": "No se pudo archivar el work item. Por favor, inténtalo de nuevo." } }, + "restaurar": { + "exito": { + "titulo": "Restauración exitosa", + "mensaje": "Tu work item se puede encontrar en los problemas del proyecto." + }, + "fallido": { + "mensaje": "No se pudo restaurar el work item. Por favor, inténtalo de nuevo." + } + }, "relation": { "relates_to": "Se relaciona con", "duplicate": "Duplicado de", "blocked_by": "Bloqueado por", "blocking": "Bloqueando" + }, + "copy_link": "Copiar enlace del work item", + "delete": { + "label": "Eliminar work item", + "error": "Error al eliminar el work item" + }, + "subscription": { + "actions": { + "subscribed": "Work item suscrito exitosamente", + "unsubscribed": "Work item desuscrito exitosamente" + } + }, + "select": { + "error": "Por favor, selecciona al menos un work item", + "empty": "No se han seleccionado work items", + "add_selected": "Agregar issues seleccionados" + }, + "open_in_full_screen": "Abrir work item en pantalla completa" + }, + + "attachment": { + "error": "No se pudo adjuntar el archivo. Intenta subirlo de nuevo.", + "only_one_file_allowed": "Solo se puede subir un archivo a la vez.", + "file_size_limit": "El archivo debe tener un tamaño de {size}MB o menos.", + "drag_and_drop": "Arrastra y suelta en cualquier lugar para subir", + "delete": "Eliminar adjunto" + }, + + "label": { + "select": "Seleccionar etiqueta", + "create": { + "success": "Etiqueta creada correctamente", + "failed": "Error al crear la etiqueta", + "already_exists": "La etiqueta ya existe", + "type": "Escribe para agregar una nueva etiqueta" + } + }, + + "sub_work_item": { + "update": { + "success": "Sub-work item actualizado exitosamente", + "error": "Error al actualizar el sub-work item" + }, + "remove": { + "success": "Sub-work item eliminado exitosamente", + "error": "Error al eliminar el sub-work item" } }, diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json index 7ef7cdcb402..dd4976f873c 100644 --- a/packages/i18n/src/locales/fr/translations.json +++ b/packages/i18n/src/locales/fr/translations.json @@ -523,6 +523,9 @@ "private": "Privé" }, "sub_work_items": "Sous-worké items", + "done": "Terminé", + "comment": "Commentaire", + "workspace_level": "Niveau du workspace", "order_by": { "label": "Trier par", "manual": "Manuel", @@ -543,7 +546,9 @@ "comments": "Commentaires", "updates": "Mises à jour", "clear_all": "Tout effacer", + "copied": "Copié !", "link_copied": "Lien copié", + "link_copied_to_clipboard": "Lien copié dans le clipboard", "copied_to_clipboard": "Lien du worké item copié", "is_copied_to_clipboard": "Worké item copié", "no_links_added_yet": "Aucun lien ajouté", @@ -556,6 +561,7 @@ "go_back": "Retour", "continue": "Continuer", "resend": "Renvoyer", + "relations": "Relations", "errors": { "default": { "title": "Erreur!", @@ -571,6 +577,18 @@ "url_is_invalid": "URL invalide", "display_title": "Titre d'affichage", "link_title_placeholder": "Comment voulez-vous voir ce lien", + "url": "URL", + "side_peek": "Peek latéral", + "modal": "Modal", + "full_screen": "Plein écran", + "close_peek_view": "Fermer le peek", + "toggle_peek_view_layout": "Changer le design du peek", + "options": "Options", + "duration": "Durée", + "today": "Aujourd'hui", + "week": "Semaine", + "month": "Mois", + "quarter": "Trimestre", "search": { "label": "Recherche", "placeholder": "Tapez pour rechercher...", @@ -583,7 +601,12 @@ "open_in_new_tab": "Ouvrir dans un nouvel onglet", "copy_link": "Copier le lien", "archive": "Archiver", - "delete": "Supprimer" + "delete": "Supprimer", + "remove_relation": "Supprimer la relation", + "subscribe": "S'abonner", + "unsubscribe": "Se désabonner", + "clear_sorting": "Effacer le tri", + "show_weekends": "Afficher les week-ends" } }, @@ -596,7 +619,7 @@ "entity": { "grouping_title": "Groupement {entity}", - "priority": "{entity} ", + "priority": "{entity} Priorité", "all": "Tous les {entity}", "drop_here_to_move": "Déposez ici pour déplacer le {entity}", "delete": { @@ -605,7 +628,16 @@ "failed": "Échec de la suppression de {entity}" }, "update": { - "failed": "Échec de la mise à jour de {entity}" + "failed": "Échec de la mise à jour de {entity}", + "success": "{entity} updated successfully" + }, + "link_copied_to_clipboard": "Lien {entity} copié dans le presse-papiers", + "fetch": { + "failed": "Erreur lors de la récupération de {entity}" + }, + "add": { + "success": "{entity} ajouté avec succès", + "failed": "Erreur lors de l'ajout de {entity}" } }, @@ -618,7 +650,8 @@ "success": "Epiqué créé avec succès" }, "add": { - "press_enter": "Appuyez sur 'Entrée' pour ajouter un autre epiqué" + "press_enter": "Appuyez sur 'Entrée' pour ajouter un autre epiqué", + "label": "Ajouter Epiqué" }, "title": { "label": "Titre de l'Epiqué", @@ -629,6 +662,7 @@ "issue": { "label": "{count, plural, one {Worké Item} other {Workés Items}}", "all": "Tous les Workés Items", + "edit": "Modifier worké item", "title": { "label": "Titre du worké item", "required": "Le titre du worké item est requis." @@ -637,7 +671,9 @@ "press_enter": "Appuyez sur 'Entrée' pour ajouter un autre worké item", "label": "Ajouter Worké Item", "cycle": { - "failed": "Le worké item n'a pas pu être ajouté au cyclé. Veuillez réessayer." + "failed": "Le worké item n'a pas pu être ajouté au cyclé. Veuillez réessayer.", + "success": "{count, plural, one {Work item} other {Work items}} added to the cycle successfully.", + "loading": "Adding {count, plural, one {work item} other {work items}} to the cycle..." }, "assignee": "Ajouter assignés", "start_date": "Ajouter date de début", @@ -645,9 +681,11 @@ "parent": "Ajouter worké item parent", "sub_issue": "Ajouter sous-worké item", "relation": "Ajouter relation", - "link": "Ajouter lien" + "link": "Ajouter lien", + "existing": "Ajouter worké item existant" }, "remove": { + "label": "Supprimer worké item", "cycle": { "loading": "Suppression du worké item du cyclé...", "success": "Worké item supprimé du cyclé avec succès.", @@ -708,6 +746,7 @@ "backlog": "Backlogué" }, "comments": { + "placeholder": "Ajouter un commentaire...", "create": { "success": "Commentaire créé avec succès", "error": "Échec de la création du commentaire. Veuillez réessayer plus tard." @@ -748,11 +787,66 @@ "message": "Le worké item n'a pas pu être archivé. Veuillez réessayer." } }, + "restore": { + "success": { + "title": "Restauration réussie", + "message": "Votre worké item peut être trouvé dans les workés items du projét." + }, + "failed": { + "message": "Le worké item n'a pas pu être restauré. Veuillez réessayer." + } + }, "relation": { "relates_to": "Lié à", "duplicate": "Dupliqué de", "blocked_by": "Bloqué par", "blocking": "Bloque" + }, + "copy_link": "Copier le lien du worké item", + "delete": { + "label": "Supprimer worké item", + "error": "Erreur lors de la suppression du worké item" + }, + "subscription": { + "actions": { + "subscribed": "Worké item abonné avec succès", + "unsubscribed": "Worké item désabonné avec succès" + } + }, + "select": { + "error": "Veuillez sélectionner au moins un work item", + "empty": "Aucun work item sélectionné", + "add_selected": "Ajouter les work items sélectionnés" + }, + "open_in_full_screen": "Ouvrir work item en plein écran" + }, + + "attachment": { + "error": "Le fichier n'a pas pu être joint. Essayez de le télécharger à nouveau.", + "only_one_file_allowed": "Un seul fichier peut être téléchargé à la fois.", + "file_size_limit": "Le fichier doit faire {size} Mo ou moins.", + "drag_and_drop": "Glissez et déposez n'importe où pour télécharger", + "delete": "Supprimer la pièce jointe" + }, + + "label": { + "select": "Sélectionner étiquette", + "create": { + "success": "Étiquette créée avec succès", + "failed": "Échec de la création de l'étiquette", + "already_exists": "L'étiquette existe déjà", + "type": "Tapez pour ajouter une nouvelle étiquette" + } + }, + + "sub_work_item": { + "update": { + "success": "Sous-worké item mis à jour avec succès", + "error": "Erreur lors de la mise à jour du sous-worké item" + }, + "remove": { + "success": "Sous-worké item supprimé avec succès", + "error": "Erreur lors de la suppression du sous-worké item" } }, diff --git a/packages/i18n/src/locales/ja/translations.json b/packages/i18n/src/locales/ja/translations.json index 4b666dcfb41..6b56ce7fa20 100644 --- a/packages/i18n/src/locales/ja/translations.json +++ b/packages/i18n/src/locales/ja/translations.json @@ -522,7 +522,10 @@ "public": "公開", "private": "非公開" }, + "done": "完了", "sub_work_items": "サブ作業項目", + "comment": "コメント", + "workspace_level": "ワークスペースレベル", "order_by": { "label": "並び順", "manual": "手動", @@ -543,7 +546,9 @@ "comments": "コメント", "updates": "更新", "clear_all": "すべてクリア", + "copied": "コピーしました!", "link_copied": "リンクをコピーしました", + "link_copied_to_clipboard": "リンクをクリップボードにコピーしました", "copied_to_clipboard": "作業項目のリンクをクリップボードにコピーしました", "is_copied_to_clipboard": "作業項目をクリップボードにコピーしました", "no_links_added_yet": "まだリンクが追加されていません", @@ -556,6 +561,7 @@ "go_back": "戻る", "continue": "続ける", "resend": "再送信", + "relations": "関係", "errors": { "default": { "title": "エラー!", @@ -571,6 +577,18 @@ "url_is_invalid": "URLが無効です", "display_title": "表示タイトル", "link_title_placeholder": "このリンクの表示名", + "url": "ユーアールエル", + "side_peek": "サイドピーク", + "modal": "モーダル", + "full_screen": "フルスクリーン", + "close_peek_view": "ピークを閉じる", + "toggle_peek_view_layout": "ピークのレイアウトを切り替え", + "options": "オプション", + "duration": "期間", + "today": "今日", + "week": "週", + "month": "月", + "quarter": "四半期", "search": { "label": "検索", "placeholder": "検索するキーワードを入力...", @@ -583,7 +601,12 @@ "open_in_new_tab": "新しいタブで開く", "copy_link": "リンクをコピー", "archive": "アーカイブ", - "delete": "削除" + "delete": "削除", + "remove_relation": "関係を削除", + "subscribe": "購読", + "unsubscribe": "購読解除", + "clear_sorting": "並び順をクリア", + "show_weekends": "週末を表示" } }, @@ -605,7 +628,16 @@ "failed": "{entity}の削除に失敗しました" }, "update": { - "failed": "{entity}の更新に失敗しました" + "failed": "{entity}の更新に失敗しました", + "success": "{entity}を更新しました" + }, + "link_copied_to_clipboard": "{entity}のリンクをクリップボードにコピーしました", + "fetch": { + "failed": "{entity}の取得に失敗しました" + }, + "add": { + "success": "{entity}を追加しました", + "failed": "{entity}の追加に失敗しました" } }, @@ -618,7 +650,8 @@ "success": "エピックを作成しました" }, "add": { - "press_enter": "'Enter'キーを押して別のエピックを追加" + "press_enter": "'Enter'キーを押して別のエピックを追加", + "label": "エピックを追加" }, "title": { "label": "エピックのタイトル", @@ -629,6 +662,7 @@ "issue": { "label": "{count, plural, one {作業項目} other {作業項目}}", "all": "すべての作業項目", + "edit": "作業項目を編集", "title": { "label": "作業項目のタイトル", "required": "作業項目のタイトルは必須です" @@ -637,7 +671,9 @@ "press_enter": "'Enter'キーを押して別の作業項目を追加", "label": "作業項目を追加", "cycle": { - "failed": "作業項目をサイクルに追加できませんでした。もう一度お試しください。" + "failed": "作業項目をサイクルに追加できませんでした。もう一度お試しください。", + "success": "{count, plural, one {作業項目} other {作業項目}}をサイクルに追加しました", + "loading": "{count, plural, one {作業項目} other {作業項目}}をサイクルに追加中..." }, "assignee": "担当者を追加", "start_date": "開始日を追加", @@ -645,9 +681,11 @@ "parent": "親作業項目を追加", "sub_issue": "サブ作業項目を追加", "relation": "関連を追加", - "link": "リンクを追加" + "link": "リンクを追加", + "existing": "既存の作業項目を追加" }, "remove": { + "label": "作業項目を削除", "cycle": { "loading": "サイクルから作業項目を削除中...", "success": "作業項目をサイクルから削除しました", @@ -708,6 +746,7 @@ "backlog": "バックログ" }, "comments": { + "placeholder": "コメントを追加...", "create": { "success": "コメントを作成しました", "error": "コメントの作成に失敗しました。後でもう一度お試しください。" @@ -748,11 +787,66 @@ "message": "作業項目をアーカイブできませんでした。もう一度お試しください。" } }, + "restore": { + "success": { + "title": "復元成功", + "message": "あなたの作業項目はプロジェクトの作業項目に見つかります。" + }, + "failed": { + "message": "作業項目を復元できませんでした。もう一度お試しください。" + } + }, "relation": { "relates_to": "関連", "duplicate": "重複", "blocked_by": "ブロック元", "blocking": "ブロック中" + }, + "copy_link": "作業項目のリンクをコピー", + "delete": { + "label": "作業項目を削除", + "error": "作業項目の削除中にエラーが発生しました" + }, + "subscription": { + "actions": { + "subscribed": "作業項目に成功裏に登録されました", + "unsubscribed": "作業項目の登録を解除しました" + } + }, + "select": { + "error": "少なくとも1つの作業項目を選択してください", + "empty": "選択された作業項目はありません", + "add_selected": "選択された作業項目を追加" + }, + "open_in_full_screen": "作業項目をフルスクリーンで開く" + }, + + "attachment": { + "error": "ファイルを添付できませんでした。再度アップロードを試みてください。", + "only_one_file_allowed": "一度にアップロードできるファイルは1つだけです。", + "file_size_limit": "ファイルのサイズは{size}MB以下でなければなりません。", + "drag_and_drop": "アップロードするにはどこでもドラッグ&ドロップしてください。", + "delete": "添付ファイルを削除" + }, + + "label": { + "select": "ラベルを選択", + "create": { + "success": "ラベルが正常に作成されました", + "failed": "ラベルの作成に失敗しました", + "already_exists": "ラベルは既に存在します", + "type": "新しいラベルを追加するには入力してください" + } + }, + + "sub_work_item": { + "update": { + "success": "サブ作業項目が正常に更新されました", + "error": "サブ作業項目の更新中にエラーが発生しました" + }, + "remove": { + "success": "サブ作業項目が正常に削除されました", + "error": "サブ作業項目の削除中にエラーが発生しました" } }, @@ -1595,7 +1689,7 @@ "options": { "mark_all_as_read": "すべて既読にする", "mark_read": "既読にする", - "mark_unread": "未読にする", + "mark_unread": "未読にする", "refresh": "更新", "filters": "受信トレイフィルター", "show_unread": "未読を表示", @@ -1806,7 +1900,7 @@ "product_or_project_manager": "プロダクト/プロジェクトマネージャー", "development_or_engineering": "開発/エンジニアリング", "founder_or_executive": "創業者/エグゼクティブ", - "freelancer_or_consultant": "フリーランス/コンサルタント", + "freelancer_or_consultant": "フリーランス/コンサルタント", "marketing_or_growth": "マーケティング/グロース", "sales_or_business_development": "営業/ビジネス開発", "support_or_operations": "サポート/オペレーション", diff --git a/packages/i18n/src/locales/zh-CN/translations.json b/packages/i18n/src/locales/zh-CN/translations.json index 36a241b6534..c2df1ae4774 100644 --- a/packages/i18n/src/locales/zh-CN/translations.json +++ b/packages/i18n/src/locales/zh-CN/translations.json @@ -523,6 +523,9 @@ "private": "私有" }, "sub_work_items": "子工作项", + "done": "完成", + "comment": "评论", + "workspace_level": "沃克思配思级别", "order_by": { "label": "排序依据", "manual": "手动", @@ -543,7 +546,9 @@ "comments": "评论", "updates": "更新", "clear_all": "清除所有", + "copied": "已复制", "link_copied": "链接已复制", + "link_copied_to_clipboard": "链接已复制到剪贴板", "copied_to_clipboard": "工作项链接已复制到剪贴板", "is_copied_to_clipboard": "工作项已复制到剪贴板", "no_links_added_yet": "尚未添加链接", @@ -556,6 +561,7 @@ "go_back": "返回", "continue": "继续", "resend": "重新发送", + "relations": "关系", "errors": { "default": { "title": "错误!", @@ -571,6 +577,18 @@ "url_is_invalid": "URL无效", "display_title": "显示标题", "link_title_placeholder": "你希望这个链接显示为", + "url": "URL", + "side_peek": "侧边预览", + "modal": "模态", + "full_screen": "全屏", + "close_peek_view": "关闭预览", + "toggle_peek_view_layout": "切换预览布局", + "options": "选项", + "duration": "持续时间", + "today": "今天", + "week": "周", + "month": "月", + "quarter": "季度", "search": { "label": "搜索", "placeholder": "输入以搜索...", @@ -584,7 +602,12 @@ "copy_link": "复制链接", "archive": "归档", "delete": "删除" - } + }, + "remove_relation": "移除关系", + "subscribe": "订阅", + "unsubscribe": "取消订阅", + "clear_sorting": "清除排序", + "show_weekends": "显示周末" }, "form": { @@ -605,301 +628,372 @@ "failed": "{entity} 删除失败" }, "update": { - "failed": "{entity} 更新失败" + "failed": "{entity} 更新失败", + "success": "{entity} 更新成功" + }, + "link_copied_to_clipboard": "{entity} 链接已复制到剪贴板", + "fetch": { + "failed": "{entity} 获取失败" + }, + "add": { + "success": "{entity} 添加成功", + "failed": "{entity} 添加失败" } }, "epic": { - "all": "All Epics", - "label": "{count, plural, one {Epic} other {Epics}}", - "new": "New Epic", - "adding": "Adding epic...", + "all": "所有史诗", + "label": "{count, plural, one {史诗} other {史诗}}", + "new": "新史诗", + "adding": "添加史诗中...", "create": { - "success": "Epic created successfully" + "success": "史诗创建成功" }, "add": { - "press_enter": "Press 'Enter' to add another epic" + "press_enter": "按'回车'添加另一个史诗", + "label": "添加史诗" }, "title": { - "label": "Epic Title", - "required": "Epic title is required." + "label": "史诗标题", + "required": "史诗标题是必需的。" } }, "issue": { - "label": "{count, plural, one {Work item} other {Work items}}", - "all": "All Work items", + "label": "{count, plural, one {工作项} other {工作项}}", + "all": "所有工作项", + "edit": "编辑工作项", "title": { - "label": "Work item title", - "required": "Work item title is required." + "label": "工作项标题", + "required": "工作项标题是必需的。" }, "add": { - "press_enter": "Press 'Enter' to add another work item", - "label": "Add Work item", + "press_enter": "按'回车'添加另一个工作项", + "label": "添加工作项", "cycle": { - "failed": "Work item could not be added to the cycle. Please try again." - }, - "assignee": "Add assignees", - "start_date": "Add start date", - "due_date": "Add due date", - "parent": "Add parent work item", - "sub_issue": "Add sub-work item", - "relation": "Add relation", - "link": "Add link" + "failed": "无法将工作项添加到周期。请重试。", + "success": "{count, plural, one {工作项} other {工作项}} 添加到周期成功。", + "loading": "正在将 {count, plural, one {工作项} other {工作项}} 添加到周期..." + }, + "assignee": "添加负责人", + "start_date": "添加开始日期", + "due_date": "添加截止日期", + "parent": "添加父工作项", + "sub_issue": "添加子工作项", + "relation": "添加关系", + "link": "添加链接", + "existing": "添加现有工作项" }, "remove": { + "label": "移除工作项", "cycle": { - "loading": "Removing work item from the cycle...", - "success": "Work item removed from the cycle successfully.", - "failed": "Work item could not be removed from the cycle. Please try again." + "loading": "正在从周期中移除工作项...", + "success": "已成功从周期中移除工作项。", + "failed": "无法从周期中移除工作项。请重试。" }, "module": { - "loading": "Removing work item from the module...", - "success": "Work item removed from the module successfully.", - "failed": "Work item could not be removed from the module. Please try again." + "loading": "正在从模块中移除工作项...", + "success": "已成功从模块中移除工作项。", + "failed": "无法从模块中移除工作项。请重试。" }, "parent": { - "label": "Remove parent work item" + "label": "移除父工作项" } }, - "new": "New Work item", - "adding": "Adding work item...", + "new": "新工作项", + "adding": "添加工作项中...", "create": { - "success": "Work item created successfully" + "success": "工作项创建成功" }, "priority": { - "urgent": "Urgent", - "high": "High", - "medium": "Medium", - "low": "Low" + "urgent": "紧急", + "high": "高", + "medium": "中", + "low": "低" }, "display": { "properties": { - "label": "Display Properties", + "label": "显示属性", "id": "ID", - "issue_type": "Work item Type", - "sub_issue_count": "Sub-work item count", - "attachment_count": "Attachment count", - "created_on": "Created on", - "sub_issue": "Sub-work item" + "issue_type": "工作项类型", + "sub_issue_count": "子工作项数量", + "attachment_count": "附件数量", + "created_on": "创建于", + "sub_issue": "子工作项" }, "extra": { - "show_sub_issues": "Show sub-work items", - "show_empty_groups": "Show empty groups" + "show_sub_issues": "显示子工作项", + "show_empty_groups": "显示空组" } }, "layouts": { - "ordered_by_label": "This layout is ordered by", - "list": "List", - "kanban": "Board", - "calendar": "Calendar", - "spreadsheet": "Table", - "gantt": "Timeline", + "ordered_by_label": "此布局按以下方式排序", + "list": "列表", + "kanban": "看板", + "calendar": "日历", + "spreadsheet": "表格", + "gantt": "时间线", "title": { - "list": "List Layout", - "kanban": "Board Layout", - "calendar": "Calendar Layout", - "spreadsheet": "Table Layout", - "gantt": "Timeline Layout" + "list": "列表布局", + "kanban": "看板布局", + "calendar": "日历布局", + "spreadsheet": "表格布局", + "gantt": "时间线布局" } }, "states": { - "active": "Active", - "backlog": "Backlog" + "active": "活动", + "backlog": "待办" }, "comments": { + "placeholder": "添加评论...", "create": { - "success": "Comment created successfully", - "error": "Comment creation failed. Please try again later." + "success": "评论创建成功", + "error": "评论创建失败。请稍后重试。" }, "update": { - "success": "Comment updated successfully", - "error": "Comment update failed. Please try again later." + "success": "评论更新成功", + "error": "评论更新失败。请稍后重试。" }, "remove": { - "success": "Comment removed successfully", - "error": "Comment remove failed. Please try again later." + "success": "评论删除成功", + "error": "评论删除失败。请稍后重试。" }, "upload": { - "error": "Asset upload failed. Please try again later." + "error": "资源上传失败。请稍后重试。" } }, "empty_state": { "issue_detail": { - "title": "Work item does not exist", - "description": "The work item you are looking for does not exist, has been archived, or has been deleted.", + "title": "工作项不存在", + "description": "您要查找的工作项不存在、已归档或已删除。", "primary_button": { - "text": "View other work items" + "text": "查看其他工作项" } } }, "sibling": { - "label": "Sibling work items" + "label": "同级工作项" }, "archive": { - "description": "Only completed or canceled\nwork items can be archived", - "label": "Archive Work item", - "confirm_message": "Are you sure you want to archive the work item? All your archived work items can be restored later.", + "description": "只有已完成或已取消的\n工作项可以归档", + "label": "归档工作项", + "confirm_message": "您确定要归档此工作项吗?所有已归档的工作项稍后可以恢复。", "success": { - "label": "Archive success", - "message": "Your archives can be found in project archives." + "label": "归档成功", + "message": "您的归档可以在项目归档中找到。" }, "failed": { - "message": "Work item could not be archived. Please try again." + "message": "无法归档工作项。请重试。" + } + }, + "restore": { + "success": { + "title": "恢复成功", + "message": "您的工作项可以在项目工作项中找到。" + }, + "failed": { + "message": "无法恢复工作项。请重试。" } }, "relation": { - "relates_to": "Relates to", - "duplicate": "Duplicate of", - "blocked_by": "Blocked by", - "blocking": "Blocking" + "relates_to": "关联到", + "duplicate": "重复于", + "blocked_by": "被阻止于", + "blocking": "阻止" + }, + "copy_link": "复制工作项链接", + "delete": { + "label": "删除工作项", + "error": "删除工作项时出错" + }, + "subscription": { + "actions": { + "subscribed": "工作项订阅成功", + "unsubscribed": "工作项取消订阅成功" + } + }, + "select": { + "error": "请选择至少一个工作项", + "empty": "没有选择工作项", + "add_selected": "添加选中的工作项" + }, + "open_in_full_screen": "在工作项中打开全屏" + }, + + "attachment": { + "error": "无法附加文件。请重试上传。", + "only_one_file_allowed": "一次只能上传一个文件。", + "file_size_limit": "文件大小必须小于或等于 {size}MB。", + "drag_and_drop": "拖放到任何位置以上传", + "delete": "删除附件" + }, + + "label": { + "select": "选择标签", + "create": { + "success": "标签创建成功", + "failed": "标签创建失败", + "already_exists": "标签已存在", + "type": "输入以添加新标签" + } + }, + + "sub_work_item": { + "update": { + "success": "子工作项更新成功", + "error": "更新子工作项时出错" + }, + "remove": { + "success": "子工作项移除成功", + "error": "移除子工作项时出错" } }, "view": { - "label": "{count, plural, one {View} other {Views}}", + "label": "{count, plural, one {视图} other {视图}}", "create": { - "label": "Create View" + "label": "创建视图" }, "update": { - "label": "Update View" + "label": "更新视图" } }, "inbox_issue": { "status": { "pending": { - "title": "Pending", - "description": "Pending" + "title": "待处理", + "description": "待处理" }, "declined": { - "title": "Declined", - "description": "Declined" + "title": "已拒绝", + "description": "已拒绝" }, "snoozed": { - "title": "Snoozed", - "description": "{days, plural, one{# day} other{# days}} to go" + "title": "已暂停", + "description": "还剩 {days, plural, one{# 天} other{# 天}}" }, "accepted": { - "title": "Accepted", - "description": "Accepted" + "title": "已接受", + "description": "已接受" }, "duplicate": { - "title": "Duplicate", - "description": "Duplicate" + "title": "重复", + "description": "重复" } }, "modals": { "decline": { - "title": "Decline work item", - "content": "Are you sure you want to decline work item {value}?" + "title": "拒绝工作项", + "content": "您确定要拒绝工作项 {value} 吗?" }, "delete": { - "title": "Delete work item", - "content": "Are you sure you want to delete work item {value}?", - "success": "Work item deleted successfully" + "title": "删除工作项", + "content": "您确定要删除工作项 {value} 吗?", + "success": "工作项删除成功" } }, "errors": { - "snooze_permission": "Only project admins can snooze/Un-snooze work items", - "accept_permission": "Only project admins can accept work items", - "decline_permission": "Only project admins can deny work items" + "snooze_permission": "只有项目管理员可以暂停/取消暂停工作项", + "accept_permission": "只有项目管理员可以接受工作项", + "decline_permission": "只有项目管理员可以拒绝工作项" }, "actions": { - "accept": "Accept", - "decline": "Decline", - "snooze": "Snooze", - "unsnooze": "Un snooze", - "copy": "Copy work item link", - "delete": "Delete", - "open": "Open work item", - "mark_as_duplicate": "Mark as duplicate", - "move": "Move {value} to project work items" + "accept": "接受", + "decline": "拒绝", + "snooze": "暂停", + "unsnooze": "取消暂停", + "copy": "复制工作项链接", + "delete": "删除", + "open": "打开工作项", + "mark_as_duplicate": "标记为重复", + "move": "将 {value} 移至项目工作项" }, "source": { - "in-app": "in-app" + "in-app": "应用内" }, "order_by": { - "created_at": "Created at", - "updated_at": "Updated at", + "created_at": "创建时间", + "updated_at": "更新时间", "id": "ID" }, - "label": "Intake", - "page_label": "{workspace} - Intake", + "label": "收件箱", + "page_label": "{workspace} - 收件箱", "modal": { - "title": "Create intake work item" + "title": "创建收件箱工作项" }, "tabs": { - "open": "Open", - "closed": "Closed" + "open": "打开", + "closed": "已关闭" }, "empty_state": { "sidebar_open_tab": { - "title": "No open work items", - "description": "Find open work items here. Create new work item." + "title": "没有打开的工作项", + "description": "在这里查找打开的工作项。创建新工作项。" }, "sidebar_closed_tab": { - "title": "No closed work items", - "description": "All the work items whether accepted or declined can be found here." + "title": "没有关闭的工作项", + "description": "所有已接受或已拒绝的工作项都可以在这里找到。" }, "sidebar_filter": { - "title": "No matching work items", - "description": "No work item matches filter applied in intake. Create a new work item." + "title": "没有匹配的工作项", + "description": "收件箱中没有符合筛选条件的工作项。创建新工作项。" }, "detail": { - "title": "Select a work item to view its details." + "title": "选择一个工作项以查看其详细信息。" } } }, "workspace_creation": { - "heading": "Create your workspace", - "subheading": "To start using Plane, you need to create or join a workspace.", + "heading": "创建您的工作区", + "subheading": "要开始使用 Plane,您需要创建或加入一个工作区。", "form": { "name": { - "label": "Name your workspace", - "placeholder": "Something familiar and recognizable is always best." + "label": "为您的工作区命名", + "placeholder": "熟悉且易于识别的名称总是最好的。" }, "url": { - "label": "Set your workspace's URL", - "placeholder": "Type or paste a URL", - "edit_slug": "You can only edit the slug of the URL" + "label": "设置您的工作区 URL", + "placeholder": "输入或粘贴 URL", + "edit_slug": "您只能编辑 URL 的别名" }, "organization_size": { - "label": "How many people will use this workspace?", - "placeholder": "Select a range" + "label": "有多少人将使用这个工作区?", + "placeholder": "选择一个范围" } }, "errors": { "creation_disabled": { - "title": "Only your instance admin can create workspaces", - "description": "If you know your instance admin's email address, click the button below to get in touch with them.", - "request_button": "Request instance admin" + "title": "只有您的实例管理员可以创建工作区", + "description": "如果您知道您的实例管理员的电子邮件地址,请点击下面的按钮与他们联系。", + "request_button": "请求实例管理员" }, "validation": { - "name_alphanumeric": "Workspaces names can contain only (' '), ('-'), ('_') and alphanumeric characters.", - "name_length": "Limit your name to 80 characters.", - "url_alphanumeric": "URLs can contain only ('-') and alphanumeric characters.", - "url_length": "Limit your URL to 48 characters.", - "url_already_taken": "Workspace URL is already taken!" + "name_alphanumeric": "工作区名称只能包含 (' '), ('-'), ('_') 和字母数字字符。", + "name_length": "名称限制为 80 个字符。", + "url_alphanumeric": "URL 只能包含 ('-') 和字母数字字符。", + "url_length": "URL 限制为 48 个字符。", + "url_already_taken": "工作区 URL 已被占用!" } }, "request_email": { - "subject": "Requesting a new workspace", - "body": "Hi instance admin(s),\n\nPlease create a new workspace with the URL [/workspace-name] for [purpose of creating the workspace].\n\nThanks,\n{firstName} {lastName}\n{email}" + "subject": "请求新工作区", + "body": "您好,实例管理员:\n\n请为 [创建工作区的目的] 创建一个 URL 为 [/workspace-name] 的新工作区。\n\n谢谢,\n{firstName} {lastName}\n{email}" }, "button": { - "default": "Create workspace", - "loading": "Creating workspace" + "default": "创建工作区", + "loading": "创建工作区中" }, "toast": { "success": { - "title": "Success", - "message": "Workspace created successfully" + "title": "成功", + "message": "工作区创建成功" }, "error": { - "title": "Error", - "message": "Workspace could not be created. Please try again." + "title": "错误", + "message": "无法创建工作区。请重试。" } } }, diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/header.tsx index 44239006eea..5e127f2cd01 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/header.tsx @@ -2,6 +2,8 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; +// i18n +import { useTranslation } from "@plane/i18n"; // ui import { Breadcrumbs, LayersIcon, Header } from "@plane/ui"; // components @@ -14,6 +16,7 @@ import { useAppRouter } from "@/hooks/use-app-router"; import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; export const ProjectIssueDetailsHeader = observer(() => { + const { t } = useTranslation(); // router const router = useAppRouter(); const { workspaceSlug, projectId, issueId } = useParams(); @@ -37,7 +40,7 @@ export const ProjectIssueDetailsHeader = observer(() => { link={ } /> } diff --git a/web/ce/components/issues/header.tsx b/web/ce/components/issues/header.tsx index 36bb0007102..4e567874e7e 100644 --- a/web/ce/components/issues/header.tsx +++ b/web/ce/components/issues/header.tsx @@ -59,7 +59,12 @@ export const IssuesHeader = observer(() => { } />} + link={ + } + /> + } /> {issuesCount && issuesCount > 0 ? ( diff --git a/web/ce/constants/project/settings/tabs.ts b/web/ce/constants/project/settings/tabs.ts index dad92837b21..0d3785a3458 100644 --- a/web/ce/constants/project/settings/tabs.ts +++ b/web/ce/constants/project/settings/tabs.ts @@ -56,7 +56,7 @@ export const PROJECT_SETTINGS = { }, automations: { key: "automations", - i18n_label: "common.automations", + i18n_label: "project_settings.automations.label", href: `/settings/automations`, access: [EUserPermissions.ADMIN], highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/automations/`, diff --git a/web/core/components/core/modals/existing-issues-list-modal.tsx b/web/core/components/core/modals/existing-issues-list-modal.tsx index 659dd7bfb93..63ce2b470c7 100644 --- a/web/core/components/core/modals/existing-issues-list-modal.tsx +++ b/web/core/components/core/modals/existing-issues-list-modal.tsx @@ -3,6 +3,8 @@ import React, { useEffect, useState } from "react"; import { Rocket, Search, X } from "lucide-react"; import { Combobox, Dialog, Transition } from "@headlessui/react"; +// i18n +import { useTranslation } from "@plane/i18n"; // types import { ISearchIssueResponse, TProjectIssuesSearchParams } from "@plane/types"; // ui @@ -33,6 +35,8 @@ type Props = { const projectService = new ProjectService(); export const ExistingIssuesListModal: React.FC = (props) => { + const { t } = useTranslation(); + const { workspaceSlug, projectId, @@ -66,8 +70,8 @@ export const ExistingIssuesListModal: React.FC = (props) => { if (selectedIssues.length === 0) { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Please select at least one issue.", + title: t("toast.error"), + message: t("issue.select.error"), }); return; @@ -140,7 +144,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { /> setSearchTerm(e.target.value)} tabIndex={baseTabIndex} @@ -174,7 +178,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { ) : (
- No issues selected + {t("issue.select.empty")}
)} {workspaceLevelToggle && ( @@ -193,7 +197,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { onClick={() => setIsWorkspaceLevel((prevData) => !prevData)} className="flex-shrink-0" > - Workspace Level + {t("common.workspace_level")} @@ -204,6 +208,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { static className="vertical-scrollbar scrollbar-md max-h-80 scroll-py-2 overflow-y-auto" > + {/* TODO: Translate here */} {searchTerm !== "" && (
Search results for{" "} @@ -288,11 +293,11 @@ export const ExistingIssuesListModal: React.FC = (props) => {
{selectedIssues.length > 0 && ( )}
diff --git a/web/core/components/dropdowns/state.tsx b/web/core/components/dropdowns/state.tsx index 59178c14968..566d89fc362 100644 --- a/web/core/components/dropdowns/state.tsx +++ b/web/core/components/dropdowns/state.tsx @@ -222,7 +222,7 @@ export const StateDropdown: React.FC = observer((props) => { className="w-full bg-transparent py-1 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none" value={query} onChange={(e) => setQuery(e.target.value)} - placeholder="Search" + placeholder={t("common.search.label")} displayValue={(assigned: any) => assigned?.name} onKeyDown={searchInputKeyDown} /> diff --git a/web/core/components/editor/lite-text-editor/lite-text-editor.tsx b/web/core/components/editor/lite-text-editor/lite-text-editor.tsx index afaff3d7ea6..82cbd12257b 100644 --- a/web/core/components/editor/lite-text-editor/lite-text-editor.tsx +++ b/web/core/components/editor/lite-text-editor/lite-text-editor.tsx @@ -3,6 +3,8 @@ import React, { useState } from "react"; import { EIssueCommentAccessSpecifier } from "@plane/constants"; // plane editor import { EditorRefApi, ILiteTextEditor, LiteTextEditorWithRef } from "@plane/editor"; +// i18n +import { useTranslation } from "@plane/i18n"; // components import { EditorMentionsRoot, IssueCommentToolbar } from "@/components/editor"; // helpers @@ -34,6 +36,7 @@ interface LiteTextEditorWrapperProps } export const LiteTextEditor = React.forwardRef((props, ref) => { + const { t } = useTranslation(); const { containerClassName, workspaceSlug, @@ -46,7 +49,7 @@ export const LiteTextEditor = React.forwardRef = (props) => { + const { t } = useTranslation(); const { accessSpecifier, executeCommand, @@ -171,7 +174,7 @@ export const IssueCommentToolbar: React.FC = (props) => { disabled={isSubmitButtonDisabled} loading={isSubmitting} > - Comment + {t("common.comment")} )} diff --git a/web/core/components/gantt-chart/chart/header.tsx b/web/core/components/gantt-chart/chart/header.tsx index 5a8f4bb65ad..7eb858be35d 100644 --- a/web/core/components/gantt-chart/chart/header.tsx +++ b/web/core/components/gantt-chart/chart/header.tsx @@ -1,5 +1,6 @@ import { observer } from "mobx-react"; import { Expand, Shrink } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; // plane import { Row } from "@plane/ui"; // components @@ -23,6 +24,7 @@ type Props = { }; export const GanttChartHeader: React.FC = observer((props) => { + const { t } = useTranslation(); const { blockIds, fullScreenMode, handleChartView, handleToday, loaderTitle, toggleFullScreenMode, showToday } = props; // chart hook @@ -35,7 +37,7 @@ export const GanttChartHeader: React.FC = observer((props) => { >
- {blockIds ? `${blockIds.length} ${loaderTitle}` : "Loading..."} + {blockIds ? `${blockIds.length} ${loaderTitle}` : t("common.loading")}
@@ -49,7 +51,7 @@ export const GanttChartHeader: React.FC = observer((props) => { })} onClick={() => handleChartView(chartView?.key)} > - {chartView?.title} + {t(chartView?.i18n_title)} ))} @@ -60,7 +62,7 @@ export const GanttChartHeader: React.FC = observer((props) => { className="rounded-sm p-1 px-2 text-xs hover:bg-custom-background-80" onClick={handleToday} > - Today + {t("common.today")} )} diff --git a/web/core/components/gantt-chart/data/index.ts b/web/core/components/gantt-chart/data/index.ts index dc884033038..6db8dda659f 100644 --- a/web/core/components/gantt-chart/data/index.ts +++ b/web/core/components/gantt-chart/data/index.ts @@ -63,7 +63,7 @@ export const datePreview = (date: Date, includeTime: boolean = false) => { export const VIEWS_LIST: ChartDataType[] = [ { key: "week", - title: "Week", + i18n_title: "common.week", data: { startDate: new Date(), currentDate: new Date(), @@ -74,7 +74,7 @@ export const VIEWS_LIST: ChartDataType[] = [ }, { key: "month", - title: "Month", + i18n_title: "common.month", data: { startDate: new Date(), currentDate: new Date(), @@ -85,7 +85,7 @@ export const VIEWS_LIST: ChartDataType[] = [ }, { key: "quarter", - title: "Quarter", + i18n_title: "common.quarter", data: { startDate: new Date(), currentDate: new Date(), diff --git a/web/core/components/gantt-chart/sidebar/root.tsx b/web/core/components/gantt-chart/sidebar/root.tsx index 7202efc55d9..7f6866ad686 100644 --- a/web/core/components/gantt-chart/sidebar/root.tsx +++ b/web/core/components/gantt-chart/sidebar/root.tsx @@ -1,5 +1,6 @@ import { RefObject } from "react"; import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; // components import { Row, ERowVariant } from "@plane/ui"; import { MultipleSelectGroupAction } from "@/components/core"; @@ -27,6 +28,7 @@ type Props = { }; export const GanttChartSidebar: React.FC = observer((props) => { + const { t } = useTranslation(); const { blockIds, blockUpdateHandler, @@ -77,7 +79,7 @@ export const GanttChartSidebar: React.FC = observer((props) => { )}
{title}
-
Duration
+
{t("common.duration")}
diff --git a/web/core/components/gantt-chart/types/index.ts b/web/core/components/gantt-chart/types/index.ts index 83b64a2b5ad..ad5b2afde9e 100644 --- a/web/core/components/gantt-chart/types/index.ts +++ b/web/core/components/gantt-chart/types/index.ts @@ -39,7 +39,7 @@ export interface WeekMonthDataType { export interface ChartDataType { key: string; - title: string; + i18n_title: string; data: ChartDataTypeData; } diff --git a/web/core/components/issues/attachment/attachment-item-list.tsx b/web/core/components/issues/attachment/attachment-item-list.tsx index ca3c0ef9f11..5df3f49c21f 100644 --- a/web/core/components/issues/attachment/attachment-item-list.tsx +++ b/web/core/components/issues/attachment/attachment-item-list.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react"; import { FileRejection, useDropzone } from "react-dropzone"; import { UploadCloud } from "lucide-react"; import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssueServiceType } from "@plane/types"; // hooks import { TOAST_TYPE, setToast } from "@plane/ui"; @@ -35,6 +36,7 @@ export const IssueAttachmentItemList: FC = observer((p disabled, issueServiceType = EIssueServiceType.ISSUES, } = props; + const { t } = useTranslation(); // states const [isUploading, setIsUploading] = useState(false); // store hooks @@ -70,8 +72,8 @@ export const IssueAttachmentItemList: FC = observer((p .catch(() => { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "File could not be attached. Try uploading again.", + title: t("toast.error"), + message: t("attachment.error"), }); }) .finally(() => { @@ -83,11 +85,11 @@ export const IssueAttachmentItemList: FC = observer((p setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", + title: t("toast.error"), message: totalAttachedFiles > 1 - ? "Only one file can be uploaded at a time." - : `File must be of ${maxFileSize / 1024 / 1024}MB or less in size.`, + ? t("attachment.only_one_file_allowed") + : t("attachment.file_size_limit", { size: maxFileSize / 1024 / 1024 }), }); return; }, @@ -127,7 +129,7 @@ export const IssueAttachmentItemList: FC = observer((p
- Drag and drop anywhere to upload + {t("attachment.drag_and_drop")}
diff --git a/web/core/components/issues/attachment/attachment-list-item.tsx b/web/core/components/issues/attachment/attachment-list-item.tsx index dfdbde0741e..911eb9bd6e9 100644 --- a/web/core/components/issues/attachment/attachment-list-item.tsx +++ b/web/core/components/issues/attachment/attachment-list-item.tsx @@ -4,6 +4,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { Trash } from "lucide-react"; import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssueServiceType } from "@plane/types"; // ui import { CustomMenu, Tooltip } from "@plane/ui"; @@ -25,6 +26,7 @@ type TIssueAttachmentsListItem = { }; export const IssueAttachmentsListItem: FC = observer((props) => { + const { t } = useTranslation(); // props const { attachmentId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props; // store hooks @@ -89,7 +91,7 @@ export const IssueAttachmentsListItem: FC = observer( >
- Delete + {t("common.actions.delete")}
diff --git a/web/core/components/issues/attachment/delete-attachment-modal.tsx b/web/core/components/issues/attachment/delete-attachment-modal.tsx index 32c9d961c7f..2b2833fd3a8 100644 --- a/web/core/components/issues/attachment/delete-attachment-modal.tsx +++ b/web/core/components/issues/attachment/delete-attachment-modal.tsx @@ -2,6 +2,8 @@ import { FC, useState } from "react"; import { observer } from "mobx-react"; // constants import { EIssueServiceType } from "@plane/constants"; +// plane-i18n +import { useTranslation } from "@plane/i18n"; // types import { TIssueServiceType } from "@plane/types"; // ui @@ -24,6 +26,7 @@ type Props = { }; export const IssueAttachmentDeleteModal: FC = observer((props) => { + const { t } = useTranslation(); const { isOpen, onClose, attachmentId, attachmentOperations, issueServiceType = EIssueServiceType.ISSUES } = props; // states const [loader, setLoader] = useState(false); @@ -54,9 +57,10 @@ export const IssueAttachmentDeleteModal: FC = observer((props) => { handleSubmit={() => handleDeletion(attachment.id)} isSubmitting={loader} isOpen={isOpen} - title="Delete attachment" + title={t("attachment.delete")} content={ <> + {/* TODO: Translate here */} Are you sure you want to delete attachment-{" "} {getFileName(attachment.attributes.name)}? This attachment will be permanently removed. This action cannot be undone. diff --git a/web/core/components/issues/delete-issue-modal.tsx b/web/core/components/issues/delete-issue-modal.tsx index f24699c460f..b13a06232b9 100644 --- a/web/core/components/issues/delete-issue-modal.tsx +++ b/web/core/components/issues/delete-issue-modal.tsx @@ -104,6 +104,7 @@ export const DeleteIssueModal: React.FC = (props) => { title={t("entity.delete.label", { entity: isEpic ? t("common.epic") : t("common.work_item") })} content={ <> + {/* TODO: Translate here */} {`Are you sure you want to delete ${isEpic ? "epic" : "issue"} `} {projectDetails?.identifier}-{issue?.sequence_id} diff --git a/web/core/components/issues/issue-detail-widgets/attachments/title.tsx b/web/core/components/issues/issue-detail-widgets/attachments/title.tsx index f2d0cd670c7..c96b6ffb8df 100644 --- a/web/core/components/issues/issue-detail-widgets/attachments/title.tsx +++ b/web/core/components/issues/issue-detail-widgets/attachments/title.tsx @@ -2,6 +2,7 @@ import React, { FC, useMemo } from "react"; import { observer } from "mobx-react"; import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssueServiceType } from "@plane/types"; import { CollapsibleButton } from "@plane/ui"; // components @@ -20,6 +21,7 @@ type Props = { export const IssueAttachmentsCollapsibleTitle: FC = observer((props) => { const { isOpen, workspaceSlug, projectId, issueId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props; + const { t } = useTranslation(); // store hooks const { issue: { getIssueById }, @@ -42,7 +44,7 @@ export const IssueAttachmentsCollapsibleTitle: FC = observer((props) => { return ( = observer((props) => { const { isOpen, issueId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props; + const { t } = useTranslation(); // store hooks const { issue: { getIssueById }, @@ -41,7 +43,7 @@ export const IssueLinksCollapsibleTitle: FC = observer((props) => { return ( diff --git a/web/core/components/issues/issue-detail-widgets/relations/helper.tsx b/web/core/components/issues/issue-detail-widgets/relations/helper.tsx index a08fd3be43b..4f905f66429 100644 --- a/web/core/components/issues/issue-detail-widgets/relations/helper.tsx +++ b/web/core/components/issues/issue-detail-widgets/relations/helper.tsx @@ -2,6 +2,7 @@ import { useMemo } from "react"; import { usePathname } from "next/navigation"; import { EIssueServiceType, ISSUE_DELETED, ISSUE_UPDATED } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssue, TIssueServiceType } from "@plane/types"; import { TOAST_TYPE, setToast } from "@plane/ui"; // constants @@ -22,6 +23,7 @@ export const useRelationOperations = ( const { updateIssue, removeIssue } = useIssueDetail(issueServiceType); const { captureIssueEvent } = useEventTracker(); const pathname = usePathname(); + const { t } = useTranslation(); // derived values const entityName = issueServiceType === EIssueServiceType.ISSUES ? "Issue" : "Epic"; @@ -32,8 +34,8 @@ export const useRelationOperations = ( copyTextToClipboard(`${originURL}/${text}`).then(() => { setToast({ type: TOAST_TYPE.SUCCESS, - title: "Link Copied!", - message: `${entityName} link copied to clipboard.`, + title: t("common.link_copied"), + message: t("entity.link_copied_to_clipboard", { entity: entityName }), }); }); }, @@ -50,9 +52,9 @@ export const useRelationOperations = ( path: pathname, }); setToast({ - title: "Success!", + title: t("toast.success"), type: TOAST_TYPE.SUCCESS, - message: `${entityName} updated successfully`, + message: t("entity.update.success", { entity: entityName }), }); } catch (error) { captureIssueEvent({ @@ -65,9 +67,9 @@ export const useRelationOperations = ( path: pathname, }); setToast({ - title: "Error!", + title: t("toast.error"), type: TOAST_TYPE.ERROR, - message: `${entityName} update failed`, + message: t("entity.update.failed", { entity: entityName }), }); } }, diff --git a/web/core/components/issues/issue-detail-widgets/relations/title.tsx b/web/core/components/issues/issue-detail-widgets/relations/title.tsx index 3f91b712f77..f1284eb47d0 100644 --- a/web/core/components/issues/issue-detail-widgets/relations/title.tsx +++ b/web/core/components/issues/issue-detail-widgets/relations/title.tsx @@ -2,6 +2,7 @@ import React, { FC, useMemo } from "react"; import { observer } from "mobx-react"; import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssueServiceType } from "@plane/types"; import { CollapsibleButton } from "@plane/ui"; // components @@ -20,6 +21,7 @@ type Props = { export const RelationsCollapsibleTitle: FC = observer((props) => { const { isOpen, issueId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props; + const { t } = useTranslation(); // store hook const { relation: { getRelationCountByIssueId }, @@ -42,7 +44,7 @@ export const RelationsCollapsibleTitle: FC = observer((props) => { return ( diff --git a/web/core/components/issues/issue-detail-widgets/sub-issues/helper.tsx b/web/core/components/issues/issue-detail-widgets/sub-issues/helper.tsx index ec600c622e7..bcc5cbf8c88 100644 --- a/web/core/components/issues/issue-detail-widgets/sub-issues/helper.tsx +++ b/web/core/components/issues/issue-detail-widgets/sub-issues/helper.tsx @@ -2,6 +2,7 @@ import { useMemo } from "react"; import { useParams, usePathname } from "next/navigation"; import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssue, TIssueServiceType } from "@plane/types"; import { TOAST_TYPE, setToast } from "@plane/ui"; // helper @@ -25,6 +26,7 @@ export const useSubIssueOperations = ( // router const { epicId: epicIdParam } = useParams(); const pathname = usePathname(); + const { t } = useTranslation(); // store hooks const { issue: { getIssueById }, @@ -51,8 +53,13 @@ export const useSubIssueOperations = ( copyTextToClipboard(`${originURL}/${text}`).then(() => { setToast({ type: TOAST_TYPE.SUCCESS, - title: "Link Copied!", - message: `${issueServiceType === EIssueServiceType.ISSUES ? "Issue" : "Epic"} link copied to clipboard`, + title: t("common.link_copied"), + message: t("entity.link_copied_to_clipboard", { + entity: + issueServiceType === EIssueServiceType.ISSUES + ? t("issue.label", { count: 1 }) + : t("epic.label", { count: 1 }), + }), }); }); }, @@ -62,8 +69,13 @@ export const useSubIssueOperations = ( } catch (error) { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: `Error fetching ${issueServiceType === EIssueServiceType.ISSUES ? "sub-issues" : "issues"}`, + title: t("toast.error"), + message: t("entity.fetch.failed", { + entity: + issueServiceType === EIssueServiceType.ISSUES + ? t("common.sub_work_items", { count: 2 }) + : t("issue.label", { count: 2 }), + }), }); } }, @@ -72,14 +84,25 @@ export const useSubIssueOperations = ( await createSubIssues(workspaceSlug, projectId, parentIssueId, issueIds); setToast({ type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: `${issueServiceType === EIssueServiceType.ISSUES ? "Sub-issues" : "Issues"} added successfully`, + title: t("toast.success"), + message: t("entity.add.success", { + entity: + issueServiceType === EIssueServiceType.ISSUES + ? t("common.sub_work_items") + : t("issue.label", { count: 2 }), + }), }); } catch (error) { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: `Error adding ${issueServiceType === EIssueServiceType.ISSUES ? "sub-issues" : "issues"}`, + title: t("toast.error"), + // message: `Error adding ${issueServiceType === EIssueServiceType.ISSUES ? "sub-issues" : "issues"}`, + message: t("entity.add.failed", { + entity: + issueServiceType === EIssueServiceType.ISSUES + ? t("common.sub_work_items") + : t("issue.label", { count: 2 }), + }), }); } }, @@ -130,8 +153,8 @@ export const useSubIssueOperations = ( }); setToast({ type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Sub-issue updated successfully", + title: t("toast.success"), + message: t("common.sub_work_items.update.success"), }); setSubIssueHelpers(parentIssueId, "issue_loader", issueId); } catch (error) { @@ -146,8 +169,8 @@ export const useSubIssueOperations = ( }); setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Error updating sub-issue", + title: t("toast.error"), + message: t("common.sub_work_items.update.error"), }); } }, @@ -167,8 +190,8 @@ export const useSubIssueOperations = ( } setToast({ type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Sub-issue removed successfully", + title: t("toast.success"), + message: t("common.sub_work_items.remove.success"), }); captureIssueEvent({ eventName: "Sub-issue removed", @@ -192,8 +215,8 @@ export const useSubIssueOperations = ( }); setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Error removing sub-issue", + title: t("toast.error"), + message: t("common.sub_work_items.remove.error"), }); } }, @@ -216,8 +239,8 @@ export const useSubIssueOperations = ( }); setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Error deleting issue", + title: t("toast.error"), + message: t("issue.delete.error"), }); } }, diff --git a/web/core/components/issues/issue-detail-widgets/sub-issues/title.tsx b/web/core/components/issues/issue-detail-widgets/sub-issues/title.tsx index e4ff1ea731d..cfe1d1fdef7 100644 --- a/web/core/components/issues/issue-detail-widgets/sub-issues/title.tsx +++ b/web/core/components/issues/issue-detail-widgets/sub-issues/title.tsx @@ -2,6 +2,7 @@ import React, { FC } from "react"; import { observer } from "mobx-react"; import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssueServiceType } from "@plane/types"; import { CircularProgressIndicator, CollapsibleButton } from "@plane/ui"; // components @@ -18,6 +19,7 @@ type Props = { export const SubIssuesCollapsibleTitle: FC = observer((props) => { const { isOpen, parentIssueId, disabled, issueServiceType = EIssueServiceType.ISSUES } = props; + const { t } = useTranslation(); // store hooks const { subIssues: { subIssuesByIssueId, stateDistributionByIssueId }, @@ -38,12 +40,12 @@ export const SubIssuesCollapsibleTitle: FC = observer((props) => { return ( - {completedCount}/{totalCount} Done + {completedCount}/{totalCount} {t("common.done")} } diff --git a/web/core/components/issues/issue-detail/cycle-select.tsx b/web/core/components/issues/issue-detail/cycle-select.tsx index f02320e280a..c82f67102d3 100644 --- a/web/core/components/issues/issue-detail/cycle-select.tsx +++ b/web/core/components/issues/issue-detail/cycle-select.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; // hooks // components import { CycleDropdown } from "@/components/dropdowns"; @@ -21,6 +22,7 @@ type TIssueCycleSelect = { export const IssueCycleSelect: React.FC = observer((props) => { const { className = "", workspaceSlug, projectId, issueId, issueOperations, disabled = false } = props; + const { t } = useTranslation(); // states const [isUpdating, setIsUpdating] = useState(false); // store hooks @@ -50,7 +52,7 @@ export const IssueCycleSelect: React.FC = observer((props) => className="group w-full" buttonContainerClassName="w-full text-left rounded" buttonClassName={`text-sm justify-between ${issue?.cycle_id ? "" : "text-custom-text-400"}`} - placeholder="No cycle" + placeholder={t("cycle.no_cycle")} hideIcon dropdownArrow dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline" diff --git a/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx b/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx index 92a2e08fed3..6ea2cfe8705 100644 --- a/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx +++ b/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx @@ -12,6 +12,7 @@ import { EUserPermissions, EUserPermissionsLevel, } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; // components import { ArchiveIssueModal, DeleteIssueModal, IssueSubscription } from "@/components/issues"; @@ -38,7 +39,7 @@ type Props = { export const IssueDetailQuickActions: FC = observer((props) => { const { workspaceSlug, projectId, issueId } = props; - + const { t } = useTranslation(); // states const [deleteIssueModal, setDeleteIssueModal] = useState(false); const [archiveIssueModal, setArchiveIssueModal] = useState(false); @@ -78,8 +79,8 @@ export const IssueDetailQuickActions: FC = observer((props) => { copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`).then(() => { setToast({ type: TOAST_TYPE.SUCCESS, - title: "Link Copied!", - message: "Issue link copied to clipboard.", + title: t("common.link_copied"), + message: t("common.copied_to_clipboard"), }); }); }; @@ -101,9 +102,9 @@ export const IssueDetailQuickActions: FC = observer((props) => { }); } catch (error) { setToast({ - title: "Error!", + title: t("toast.error "), type: TOAST_TYPE.ERROR, - message: "Issue delete failed", + message: t("entity.delete.failed", { entity: t("issue.label", { count: 1 }) }), }); captureIssueEvent({ eventName: ISSUE_DELETED, @@ -141,16 +142,16 @@ export const IssueDetailQuickActions: FC = observer((props) => { .then(() => { setToast({ type: TOAST_TYPE.SUCCESS, - title: "Restore success", - message: "Your issue can be found in project issues.", + title: t("issue.restore.success.title"), + message: t("issue.restore.success.message"), }); router.push(`/${workspaceSlug}/projects/${projectId}/issues/${issueId}`); }) .catch(() => { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Issue could not be restored. Please try again.", + title: t("toast.error"), + message: t("issue.restore.failed.message"), }); }) .finally(() => setIsRestoring(false)); @@ -185,7 +186,7 @@ export const IssueDetailQuickActions: FC = observer((props) => { )}
- +
); @@ -147,7 +149,7 @@ export const IssueLabelSelect: React.FC = observer((props) => className="w-full bg-transparent px-2 py-1 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none" value={query} onChange={(e) => setQuery(e.target.value)} - placeholder="Search" + placeholder={t("common.search.label")} displayValue={(assigned: any) => assigned?.name} onKeyDown={searchInputKeyDown} tabIndex={baseTabIndex} @@ -156,7 +158,7 @@ export const IssueLabelSelect: React.FC = observer((props) =>
{isLoading ? ( -

Loading...

+

{t("common.loading")}

) : filteredOptions.length > 0 ? ( filteredOptions.map((option) => ( = observer((props) => > {query.length ? ( <> - + Add "{query}" to labels + {/* TODO: Translate here */}+ Add{" "} + "{query}" to labels ) : ( - "Type to add a new label" + t("label.create.type") )} ) : ( -

No matching results.

+

{t("common.search.no_matching_results")}

)}
diff --git a/web/core/components/issues/issue-detail/links/create-update-link-modal.tsx b/web/core/components/issues/issue-detail/links/create-update-link-modal.tsx index 5ac0e8e8e9a..04d167763af 100644 --- a/web/core/components/issues/issue-detail/links/create-update-link-modal.tsx +++ b/web/core/components/issues/issue-detail/links/create-update-link-modal.tsx @@ -79,7 +79,7 @@ export const IssueLinkCreateUpdateModal: FC = observe
= observer((props) => { // props const { linkId, linkOperations, isNotAllowed, issueServiceType = EIssueServiceType.ISSUES } = props; // hooks + const { t } = useTranslation(); const { toggleIssueLinkModal: toggleIssueLinkModalStore, setIssueLinkData, @@ -67,8 +69,8 @@ export const IssueLinkItem: FC = observer((props) => { copyTextToClipboard(linkDetail.url); setToast({ type: TOAST_TYPE.SUCCESS, - title: "Link copied!", - message: "Link copied to clipboard", + title: t("common.link_copied"), + message: t("common.link_copied_to_clipboard"), }); }} className="relative grid place-items-center rounded p-1 text-custom-text-400 outline-none group-hover:text-custom-text-200 cursor-pointer hover:bg-custom-background-80" @@ -91,7 +93,7 @@ export const IssueLinkItem: FC = observer((props) => { }} > - Edit + {t("common.actions.edit")} = observer((props) => { }} > - Delete + {t("common.actions.delete")}
diff --git a/web/core/components/issues/issue-detail/module-select.tsx b/web/core/components/issues/issue-detail/module-select.tsx index c51e8848191..d021958e89d 100644 --- a/web/core/components/issues/issue-detail/module-select.tsx +++ b/web/core/components/issues/issue-detail/module-select.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import xor from "lodash/xor"; import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; // hooks // components import { ModuleDropdown } from "@/components/dropdowns"; @@ -22,6 +23,7 @@ type TIssueModuleSelect = { export const IssueModuleSelect: React.FC = observer((props) => { const { className = "", workspaceSlug, projectId, issueId, issueOperations, disabled = false } = props; + const { t } = useTranslation(); // states const [isUpdating, setIsUpdating] = useState(false); // store hooks @@ -59,7 +61,7 @@ export const IssueModuleSelect: React.FC = observer((props) projectId={projectId} value={issue?.module_ids ?? []} onChange={handleIssueModuleChange} - placeholder="No module" + placeholder={t("module.no_module")} disabled={disableSelect} className="group h-full w-full" buttonContainerClassName="w-full rounded" diff --git a/web/core/components/issues/issue-detail/subscription.tsx b/web/core/components/issues/issue-detail/subscription.tsx index ccafdacd394..a895c34c900 100644 --- a/web/core/components/issues/issue-detail/subscription.tsx +++ b/web/core/components/issues/issue-detail/subscription.tsx @@ -4,6 +4,8 @@ import { FC, useState } from "react"; import isNil from "lodash/isNil"; import { observer } from "mobx-react"; import { Bell, BellOff } from "lucide-react"; +// plane-i18n +import { useTranslation } from "@plane/i18n"; // UI import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui"; @@ -18,6 +20,7 @@ export type TIssueSubscription = { export const IssueSubscription: FC = observer((props) => { const { workspaceSlug, projectId, issueId } = props; + const { t } = useTranslation(); // hooks const { subscription: { getSubscriptionByIssueId }, @@ -44,16 +47,18 @@ export const IssueSubscription: FC = observer((props) => { else await createSubscription(workspaceSlug, projectId, issueId); setToast({ type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: `Issue ${isSubscribed ? `unsubscribed` : `subscribed`} successfully.!`, + title: t("toast.success"), + message: isSubscribed + ? t("issue.subscription.actions.unsubscribed") + : t("issue.subscription.actions.subscribed"), }); setLoading(false); } catch (error) { setLoading(false); setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Something went wrong. Please try again later.", + title: t("toast.error"), + message: t("commons.error.message"), }); } }; @@ -77,12 +82,12 @@ export const IssueSubscription: FC = observer((props) => { > {loading ? ( - Loading... + {t("common.loading")} ) : isSubscribed ? ( -
Unsubscribe
+
{t("common.actions.unsubscribe")}
) : ( -
Subscribe
+
{t("common.actions.subscribe")}
)}
diff --git a/web/core/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx b/web/core/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx index 9b5b13ce462..35c2c281cc0 100644 --- a/web/core/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx +++ b/web/core/components/issues/issue-layouts/calendar/dropdowns/options-dropdown.tsx @@ -10,6 +10,7 @@ import { Popover, Transition } from "@headlessui/react"; // ui // icons import { EIssueFilterType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, @@ -46,6 +47,8 @@ interface ICalendarHeader { export const CalendarOptionsDropdown: React.FC = observer((props) => { const { issuesFilterStore, updateFilters } = props; + const { t } = useTranslation(); + const { projectId } = useParams(); const issueCalendarView = useCalendarView(); @@ -111,7 +114,7 @@ export const CalendarOptionsDropdown: React.FC = observer((prop open ? "text-custom-text-100" : "text-custom-text-200" }`} > -
Options
+
{t("common.options")}
@@ -156,7 +159,7 @@ export const CalendarOptionsDropdown: React.FC = observer((prop className="flex w-full items-center justify-between gap-2 rounded px-1 py-1.5 text-left text-xs hover:bg-custom-background-80" onClick={handleToggleWeekends} > - Show weekends + {t("common.actions.show_weekends")} { diff --git a/web/core/components/issues/issue-layouts/calendar/header.tsx b/web/core/components/issues/issue-layouts/calendar/header.tsx index a441ff8c165..e5943f33b09 100644 --- a/web/core/components/issues/issue-layouts/calendar/header.tsx +++ b/web/core/components/issues/issue-layouts/calendar/header.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react"; // components import { ChevronLeft, ChevronRight } from "lucide-react"; import { EIssueFilterType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, @@ -37,6 +38,8 @@ interface ICalendarHeader { export const CalendarHeader: React.FC = observer((props) => { const { issuesFilterStore, updateFilters, setSelectedDate } = props; + const { t } = useTranslation(); + const issueCalendarView = useCalendarView(); const calendarLayout = issuesFilterStore.issueFilters?.displayFilters?.calendar?.layout ?? "month"; @@ -119,7 +122,7 @@ export const CalendarHeader: React.FC = observer((props) => { className="rounded bg-custom-background-80 px-2.5 py-1 text-xs font-medium text-custom-text-200 hover:text-custom-text-100" onClick={handleToday} > - Today + {t("common.today")}
diff --git a/web/core/components/issues/issue-layouts/calendar/issue-blocks.tsx b/web/core/components/issues/issue-layouts/calendar/issue-blocks.tsx index 5eaae6d57df..9e7109fa33d 100644 --- a/web/core/components/issues/issue-layouts/calendar/issue-blocks.tsx +++ b/web/core/components/issues/issue-layouts/calendar/issue-blocks.tsx @@ -1,4 +1,5 @@ import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; import { TIssue, TPaginationData } from "@plane/types"; // components import { CalendarQuickAddIssueActions, CalendarIssueBlockRoot } from "@/components/issues"; @@ -43,6 +44,7 @@ export const CalendarIssueBlocks: React.FC = observer((props) => { isEpic = false, } = props; const formattedDatePayload = renderFormattedPayloadDate(date); + const { t } = useTranslation(); const { issues: { getGroupIssueCount, getPaginationData, getIssueLoader }, @@ -99,7 +101,7 @@ export const CalendarIssueBlocks: React.FC = observer((props) => { className="w-min whitespace-nowrap rounded text-xs px-1.5 py-1 font-medium hover:bg-custom-background-80 text-custom-primary-100 hover:text-custom-primary-200" onClick={() => loadMoreIssues(formattedDatePayload)} > - Load More + {t("common.load_more")} )} diff --git a/web/core/components/issues/issue-layouts/calendar/quick-add-issue-actions.tsx b/web/core/components/issues/issue-layouts/calendar/quick-add-issue-actions.tsx index 75afad975ef..88d7d237daf 100644 --- a/web/core/components/issues/issue-layouts/calendar/quick-add-issue-actions.tsx +++ b/web/core/components/issues/issue-layouts/calendar/quick-add-issue-actions.tsx @@ -7,6 +7,8 @@ import { useParams } from "next/navigation"; import { PlusIcon } from "lucide-react"; // plane constants import { EIssueLayoutTypes } from "@plane/constants"; +// i18n +import { useTranslation } from "@plane/i18n"; // types import { ISearchIssueResponse, TIssue } from "@plane/types"; // ui @@ -29,6 +31,7 @@ type TCalendarQuickAddIssueActions = { export const CalendarQuickAddIssueActions: FC = observer((props) => { const { prePopulatedData, quickAddCallback, addIssuesToView, onOpen, isEpic = false } = props; + const { t } = useTranslation(); // router const { workspaceSlug, projectId, moduleId } = useParams(); // states @@ -52,14 +55,14 @@ export const CalendarQuickAddIssueActions: FC = o ).then(() => addIssuesToView?.(issueIds)); setPromiseToast(addExistingIssuesPromise, { - loading: `Adding ${issueIds.length > 1 ? "issues" : "issue"} to cycle...`, + loading: t("toast.add.cycle.loading", { count: issueIds.length }), success: { - title: "Success!", - message: () => `${issueIds.length > 1 ? "Issues" : "Issue"} added to cycle successfully.`, + title: t("toast.success"), + message: () => t("toast.add.cycle.success", { count: issueIds.length }), }, error: { - title: "Error!", - message: (err) => err?.message || "Something went wrong. Please try again.", + title: t("toast.error"), + message: (err) => err?.message || t("common.errors.default.message"), }, }); }; @@ -119,12 +122,18 @@ export const CalendarQuickAddIssueActions: FC = o customButton={
- {`New ${isEpic ? "Epic" : "Issue"}`} + + {isEpic ? t("epic.add.label") : t("issue.add.label")} +
} > - {`New ${isEpic ? "Epic" : "Issue"}`} - {!isEpic && Add existing issue} + + {isEpic ? t("epic.add.label") : t("issue.add.label")} + + {!isEpic && ( + {t("issue.add.existing")} + )} } diff --git a/web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx b/web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx index 22eb03c9bde..4cd41e21165 100644 --- a/web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx +++ b/web/core/components/issues/issue-layouts/gantt/base-gantt-root.tsx @@ -9,6 +9,7 @@ import { EUserPermissions, EUserPermissionsLevel, } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssue } from "@plane/types"; import { setToast, TOAST_TYPE } from "@plane/ui"; // hooks @@ -43,6 +44,7 @@ export type GanttStoreType = export const BaseGanttRoot: React.FC = observer((props: IBaseGanttRoot) => { const { viewId, isCompletedCycle = false, isEpic = false } = props; + const { t } = useTranslation(); // router const { workspaceSlug, projectId } = useParams(); @@ -98,7 +100,7 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan issues.updateIssueDates(workspaceSlug.toString(), projectId.toString(), updates).catch(() => { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", + title: t("toast.error"), message: "Error while updating Issue Dates, Please try again Later", }); }), @@ -126,8 +128,8 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan
} diff --git a/web/core/components/issues/issue-layouts/properties/label-dropdown.tsx b/web/core/components/issues/issue-layouts/properties/label-dropdown.tsx index d74434f2bc4..68fe34ebf66 100644 --- a/web/core/components/issues/issue-layouts/properties/label-dropdown.tsx +++ b/web/core/components/issues/issue-layouts/properties/label-dropdown.tsx @@ -7,6 +7,7 @@ import { Combobox } from "@headlessui/react"; // plane imports import { EUserPermissionsLevel, EUserProjectRoles, getRandomLabelColor } from "@plane/constants"; import { useOutsideClickDetector } from "@plane/hooks"; +import { useTranslation } from "@plane/i18n"; // types import { IIssueLabel } from "@plane/types"; // components @@ -54,6 +55,7 @@ export const LabelDropdown = (props: ILabelDropdownProps) => { fullHeight = false, label, } = props; + const { t } = useTranslation(); //router const { workspaceSlug: routerWorkspaceSlug } = useParams(); @@ -235,14 +237,14 @@ export const LabelDropdown = (props: ILabelDropdownProps) => { className="w-full bg-transparent px-2 py-1 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none" value={query} onChange={(e) => setQuery(e.target.value)} - placeholder="Search" + placeholder={t("common.search.label")} displayValue={(assigned: any) => assigned?.name || ""} onKeyDown={searchInputKeyDown} />
{isLoading ? ( -

Loading...

+

{t("common.loading")}

) : filteredOptions.length > 0 ? ( filteredOptions.map((option) => ( { }} className={`text-left text-custom-text-200 ${query.length ? "cursor-pointer" : "cursor-default"}`} > + {/* TODO: translate here */} {query.length ? ( <> + Add "{query}" to labels ) : ( - "Type to add a new label" + t("label.create.type") )}

) : ( -

No matching results.

+

{t("common.search.no_matching_results")}

)}
diff --git a/web/core/components/issues/issue-layouts/quick-add/button/gantt.tsx b/web/core/components/issues/issue-layouts/quick-add/button/gantt.tsx index e9297abc9ce..af96c0a91c3 100644 --- a/web/core/components/issues/issue-layouts/quick-add/button/gantt.tsx +++ b/web/core/components/issues/issue-layouts/quick-add/button/gantt.tsx @@ -1,12 +1,13 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { PlusIcon } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; import { Row } from "@plane/ui"; import { TQuickAddIssueButton } from "../root"; export const GanttQuickAddIssueButton: FC = observer((props) => { const { onClick, isEpic = false } = props; - + const { t } = useTranslation(); return ( ); diff --git a/web/core/components/issues/issue-layouts/quick-add/button/kanban.tsx b/web/core/components/issues/issue-layouts/quick-add/button/kanban.tsx index 918ef33120e..deadd9e4cc4 100644 --- a/web/core/components/issues/issue-layouts/quick-add/button/kanban.tsx +++ b/web/core/components/issues/issue-layouts/quick-add/button/kanban.tsx @@ -1,18 +1,19 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { PlusIcon } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; import { TQuickAddIssueButton } from "../root"; export const KanbanQuickAddIssueButton: FC = observer((props) => { const { onClick, isEpic = false } = props; - + const { t } = useTranslation(); return (
- {`New ${isEpic ? "Epic" : "Issue"}`} + {isEpic ? t("epic.new") : t("issue.new")}
); }); diff --git a/web/core/components/issues/issue-layouts/quick-add/button/list.tsx b/web/core/components/issues/issue-layouts/quick-add/button/list.tsx index 3dcbf5990a8..1b09e8e3c54 100644 --- a/web/core/components/issues/issue-layouts/quick-add/button/list.tsx +++ b/web/core/components/issues/issue-layouts/quick-add/button/list.tsx @@ -1,19 +1,20 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { PlusIcon } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; import { Row } from "@plane/ui"; import { TQuickAddIssueButton } from "../root"; export const ListQuickAddIssueButton: FC = observer((props) => { const { onClick, isEpic = false } = props; - + const { t } = useTranslation(); return ( - {`New ${isEpic ? "Epic" : "Issue"}`} + {isEpic ? t("epic.new") : t("issue.new")} ); }); diff --git a/web/core/components/issues/issue-layouts/quick-add/button/spreadsheet.tsx b/web/core/components/issues/issue-layouts/quick-add/button/spreadsheet.tsx index 170f891909c..1a0bb7d465d 100644 --- a/web/core/components/issues/issue-layouts/quick-add/button/spreadsheet.tsx +++ b/web/core/components/issues/issue-layouts/quick-add/button/spreadsheet.tsx @@ -1,11 +1,12 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { PlusIcon } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; import { TQuickAddIssueButton } from "../root"; export const SpreadsheetAddIssueButton: FC = observer((props) => { const { onClick, isEpic = false } = props; - + const { t } = useTranslation(); return (
); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/gantt.tsx b/web/core/components/issues/issue-layouts/quick-add/form/gantt.tsx index e9b7bb38d0c..19f0476762e 100644 --- a/web/core/components/issues/issue-layouts/quick-add/form/gantt.tsx +++ b/web/core/components/issues/issue-layouts/quick-add/form/gantt.tsx @@ -1,11 +1,12 @@ import { FC } from "react"; import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; import { cn } from "@/helpers/common.helper"; import { TQuickAddIssueForm } from "../root"; export const GanttQuickAddIssueForm: FC = observer((props) => { const { ref, projectDetail, hasError, register, onSubmit, isEpic } = props; - + const { t } = useTranslation(); return (
= observer((props) =
-
{`Press 'Enter' to add another ${isEpic ? "epic" : "issue"}`}
+
+ {isEpic ? t("epic.add.press_enter") : t("issue.add.press_enter")} +
); }); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/kanban.tsx b/web/core/components/issues/issue-layouts/quick-add/form/kanban.tsx index 1f25be6e942..92e1811b3ba 100644 --- a/web/core/components/issues/issue-layouts/quick-add/form/kanban.tsx +++ b/web/core/components/issues/issue-layouts/quick-add/form/kanban.tsx @@ -1,10 +1,11 @@ import { FC } from "react"; import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; import { TQuickAddIssueForm } from "../root"; export const KanbanQuickAddIssueForm: FC = observer((props) => { const { ref, projectDetail, register, onSubmit, isEpic } = props; - + const { t } = useTranslation(); return (
@@ -12,15 +13,17 @@ export const KanbanQuickAddIssueForm: FC = observer((props)

{projectDetail?.identifier ?? "..."}

-
{`Press 'Enter' to add another ${isEpic ? "epic" : "issue"}`}
+
+ {isEpic ? t("epic.add.press_enter") : t("issue.add.press_enter")} +
); }); diff --git a/web/core/components/issues/issue-layouts/quick-add/form/spreadsheet.tsx b/web/core/components/issues/issue-layouts/quick-add/form/spreadsheet.tsx index 4918157c74f..494f2c63609 100644 --- a/web/core/components/issues/issue-layouts/quick-add/form/spreadsheet.tsx +++ b/web/core/components/issues/issue-layouts/quick-add/form/spreadsheet.tsx @@ -1,10 +1,11 @@ import { FC } from "react"; import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; import { TQuickAddIssueForm } from "../root"; export const SpreadsheetQuickAddIssueForm: FC = observer((props) => { const { ref, projectDetail, register, onSubmit, isEpic } = props; - + const { t } = useTranslation(); return (
= observer((pr

- {`Press Enter to add another ${isEpic ? "epic" : "issue"}`} + {isEpic ? t("epic.add.press_enter") : t("issue.add.press_enter")}

); diff --git a/web/core/components/issues/issue-layouts/quick-add/root.tsx b/web/core/components/issues/issue-layouts/quick-add/root.tsx index 2dd6ef9cf44..6bc6997a4cd 100644 --- a/web/core/components/issues/issue-layouts/quick-add/root.tsx +++ b/web/core/components/issues/issue-layouts/quick-add/root.tsx @@ -120,6 +120,7 @@ export const QuickAddIssueRoot: FC = observer((props) => { title: t("common.success"), message: () => `${isEpic ? t("epic.create.success") : t("issue.create.success")}`, actionItems: (data) => ( + // TODO: Translate here { >
- Clear sorting + {t("common.actions.clear_sorting")}
)} diff --git a/web/core/components/issues/peek-overview/header.tsx b/web/core/components/issues/peek-overview/header.tsx index d80643b514e..801c3783af5 100644 --- a/web/core/components/issues/peek-overview/header.tsx +++ b/web/core/components/issues/peek-overview/header.tsx @@ -6,6 +6,8 @@ import Link from "next/link"; import { ArchiveRestoreIcon, Link2, MoveDiagonal, MoveRight, Trash2 } from "lucide-react"; // plane imports import { ARCHIVABLE_STATE_GROUPS } from "@plane/constants"; +// i18n +import { useTranslation } from "@plane/i18n"; // types import { TNameDescriptionLoader } from "@plane/types"; // ui @@ -30,21 +32,21 @@ import { useIssueDetail, useProjectState, useUser } from "@/hooks/store"; import { usePlatformOS } from "@/hooks/use-platform-os"; export type TPeekModes = "side-peek" | "modal" | "full-screen"; -const PEEK_OPTIONS: { key: TPeekModes; icon: any; title: string }[] = [ +const PEEK_OPTIONS: { key: TPeekModes; icon: any; i18n_title: string }[] = [ { key: "side-peek", icon: SidePanelIcon, - title: "Side Peek", + i18n_title: "common.side_peek ", }, { key: "modal", icon: CenterPanelIcon, - title: "Modal", + i18n_title: "common.modal", }, { key: "full-screen", icon: FullScreenPanelIcon, - title: "Full Screen", + i18n_title: "common.full_screen", }, ]; @@ -80,6 +82,7 @@ export const IssuePeekOverviewHeader: FC = observer((pr handleRestoreIssue, isSubmitting, } = props; + const { t } = useTranslation(); // store hooks const { data: currentUser } = useUser(); const { @@ -100,8 +103,8 @@ export const IssuePeekOverviewHeader: FC = observer((pr copyUrlToClipboard(issueLink).then(() => { setToast({ type: TOAST_TYPE.SUCCESS, - title: "Link Copied!", - message: "Issue link copied to clipboard.", + title: t("common.link_copied"), + message: t("common.copied_to_clipboard"), }); }); }; @@ -117,13 +120,13 @@ export const IssuePeekOverviewHeader: FC = observer((pr }`} >
- + - + removeRoutePeekId()}> @@ -134,7 +137,7 @@ export const IssuePeekOverviewHeader: FC = observer((pr value={currentMode} onChange={(val: any) => setPeekMode(val)} customButton={ - + @@ -151,7 +154,7 @@ export const IssuePeekOverviewHeader: FC = observer((pr }`} > - {mode.title} + {t(mode.i18n_title)}
))} @@ -165,7 +168,7 @@ export const IssuePeekOverviewHeader: FC = observer((pr {currentUser && !isArchived && ( )} - + @@ -173,7 +176,7 @@ export const IssuePeekOverviewHeader: FC = observer((pr {isArchivingAllowed && ( )} {!disabled && ( - + diff --git a/web/core/components/issues/peek-overview/properties.tsx b/web/core/components/issues/peek-overview/properties.tsx index d000c9b6fb7..7d80f5a2312 100644 --- a/web/core/components/issues/peek-overview/properties.tsx +++ b/web/core/components/issues/peek-overview/properties.tsx @@ -3,7 +3,8 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { Signal, Tag, Triangle, LayoutPanelTop, CalendarClock, CalendarCheck2, Users, UserCircle2 } from "lucide-react"; -// hooks +// i18n +import { useTranslation } from "@plane/i18n"; // ui icons import { DiceIcon, DoubleCircleIcon, ContrastIcon } from "@plane/ui"; // components @@ -41,6 +42,7 @@ interface IPeekOverviewProperties { export const PeekOverviewProperties: FC = observer((props) => { const { workspaceSlug, projectId, issueId, issueOperations, disabled } = props; + const { t } = useTranslation(); // store hooks const { getProjectById } = useProject(); const { @@ -64,14 +66,14 @@ export const PeekOverviewProperties: FC = observer((pro return (
-
Properties
+
{t("common.properties")}
{/* TODO: render properties using a common component */}
{/* state */}
- State + {t("common.state")}
= observer((pro
- Assignees + {t("common.assignees")}
issueOperations.update(workspaceSlug, projectId, issueId, { assignee_ids: val })} disabled={disabled} projectId={projectId} - placeholder="Add assignees" + placeholder={t("issue.add.assignee")} multiple buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"} className="w-3/4 flex-grow group" @@ -114,7 +116,7 @@ export const PeekOverviewProperties: FC = observer((pro
- Priority + {t("common.priority")}
= observer((pro
- Created by + {t("common.created_by")}
= observer((pro
- Start date + {t("common.order_by.start_date")}
= observer((pro start_date: val ? renderFormattedPayloadDate(val) : null, }) } - placeholder="Add start date" + placeholder={t("issue.add.start_date")} buttonVariant="transparent-with-text" maxDate={maxDate ?? undefined} disabled={disabled} @@ -177,7 +179,7 @@ export const PeekOverviewProperties: FC = observer((pro
- Due date + {t("common.order_by.due_date")}
= observer((pro target_date: val ? renderFormattedPayloadDate(val) : null, }) } - placeholder="Add due date" + placeholder={t("issue.add.due_date")} buttonVariant="transparent-with-text" minDate={minDate ?? undefined} disabled={disabled} @@ -208,7 +210,7 @@ export const PeekOverviewProperties: FC = observer((pro
- Estimate + {t("common.estimate")}
= observer((pro
- Modules + {t("common.modules")}
= observer((pro
- Cycle + {t("common.cycle")}
= observer((pro
-

Parent

+

{t("common.parent")}

= observer((pro
- Labels + {t("common.labels")}
diff --git a/web/core/components/issues/peek-overview/root.tsx b/web/core/components/issues/peek-overview/root.tsx index 6687230ac90..5ed6403bcb9 100644 --- a/web/core/components/issues/peek-overview/root.tsx +++ b/web/core/components/issues/peek-overview/root.tsx @@ -4,7 +4,16 @@ import { FC, useEffect, useState, useMemo, useCallback } from "react"; import { observer } from "mobx-react"; import { usePathname } from "next/navigation"; // plane types -import { EIssuesStoreType, ISSUE_UPDATED, ISSUE_DELETED, ISSUE_ARCHIVED, ISSUE_RESTORED , EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { + EIssuesStoreType, + ISSUE_UPDATED, + ISSUE_DELETED, + ISSUE_ARCHIVED, + ISSUE_RESTORED, + EUserPermissions, + EUserPermissionsLevel, +} from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssue } from "@plane/types"; // plane ui import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui"; @@ -29,6 +38,7 @@ export const IssuePeekOverview: FC = observer((props) => { is_draft = false, storeType: issueStoreFromProps, } = props; + const { t } = useTranslation(); // router const pathname = usePathname(); // store hook @@ -89,9 +99,9 @@ export const IssuePeekOverview: FC = observer((props) => { path: pathname, }); setToast({ - title: "Error!", + title: t("toast.error"), type: TOAST_TYPE.ERROR, - message: "Issue update failed", + message: t("entity.update.failed", { entity: t("issue.label", { count: 1 }) }), }); }); } @@ -108,9 +118,9 @@ export const IssuePeekOverview: FC = observer((props) => { }); } catch { setToast({ - title: "Error!", + title: t("toast.error"), type: TOAST_TYPE.ERROR, - message: "Issue delete failed", + message: t("entity.delete.failed", { entity: t("issue.label", { count: 1 }) }), }); captureIssueEvent({ eventName: ISSUE_DELETED, @@ -141,8 +151,8 @@ export const IssuePeekOverview: FC = observer((props) => { await restoreIssue(workspaceSlug, projectId, issueId); setToast({ type: TOAST_TYPE.SUCCESS, - title: "Restore success", - message: "Your issue can be found in project issues.", + title: t("issue.restore.success.title"), + message: t("issue.restore.success.message"), }); captureIssueEvent({ eventName: ISSUE_RESTORED, @@ -152,8 +162,8 @@ export const IssuePeekOverview: FC = observer((props) => { } catch { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Issue could not be restored. Please try again.", + title: t("toast.error"), + message: t("issue.restore.failed.message"), }); captureIssueEvent({ eventName: ISSUE_RESTORED, @@ -178,8 +188,8 @@ export const IssuePeekOverview: FC = observer((props) => { } catch { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Issue could not be added to the cycle. Please try again.", + title: t("toast.error"), + message: t("issue.add.cycle.failed"), }); captureIssueEvent({ eventName: ISSUE_UPDATED, @@ -207,8 +217,8 @@ export const IssuePeekOverview: FC = observer((props) => { } catch { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Issue could not be added to the cycle. Please try again.", + title: t("toast.error"), + message: t("issue.add.cycle.failed"), }); captureIssueEvent({ eventName: ISSUE_UPDATED, @@ -225,14 +235,14 @@ export const IssuePeekOverview: FC = observer((props) => { try { const removeFromCyclePromise = issues.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId); setPromiseToast(removeFromCyclePromise, { - loading: "Removing issue from the cycle...", + loading: t("issue.remove.cycle.loading"), success: { - title: "Success!", - message: () => "Issue removed from the cycle successfully.", + title: t("toast.success"), + message: () => t("issue.remove.cycle.success"), }, error: { - title: "Error!", - message: () => "Issue could not be removed from the cycle. Please try again.", + title: t("toast.error"), + message: () => t("issue.remove.cycle.failed"), }, }); await removeFromCyclePromise; @@ -288,14 +298,14 @@ export const IssuePeekOverview: FC = observer((props) => { try { const removeFromModulePromise = issues.removeIssuesFromModule(workspaceSlug, projectId, moduleId, [issueId]); setPromiseToast(removeFromModulePromise, { - loading: "Removing issue from the module...", + loading: t("issue.remove.module.loading"), success: { - title: "Success!", - message: () => "Issue removed from the module successfully.", + title: t("toast.success"), + message: () => t("issue.remove.module.success"), }, error: { - title: "Error!", - message: () => "Issue could not be removed from the module. Please try again.", + title: t("toast.error"), + message: () => t("issue.remove.module.failed"), }, }); await removeFromModulePromise; diff --git a/web/core/components/issues/relations/issue-list-item.tsx b/web/core/components/issues/relations/issue-list-item.tsx index bb669762ae6..4860e315dbc 100644 --- a/web/core/components/issues/relations/issue-list-item.tsx +++ b/web/core/components/issues/relations/issue-list-item.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react"; import { X, Pencil, Trash, Link as LinkIcon } from "lucide-react"; // Plane import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssue, TIssueServiceType } from "@plane/types"; import { ControlLink, CustomMenu, Tooltip } from "@plane/ui"; // components @@ -40,6 +41,8 @@ export const RelationIssueListItem: FC = observer((props) => { issueServiceType = EIssueServiceType.ISSUES, } = props; + const { t } = useTranslation(); + // store hooks const { issue: { getIssueById }, @@ -153,7 +156,7 @@ export const RelationIssueListItem: FC = observer((props) => {
- Edit + {t("common.actions.edit")}
)} @@ -161,7 +164,7 @@ export const RelationIssueListItem: FC = observer((props) => {
- Copy link + {t("common.actions.copy_link")}
@@ -169,7 +172,7 @@ export const RelationIssueListItem: FC = observer((props) => {
- Remove relation + {t("common.actions.remove_relation")}
)} @@ -178,7 +181,7 @@ export const RelationIssueListItem: FC = observer((props) => {
- Delete + {t("common.actions.delete")}
)} diff --git a/web/core/components/issues/sub-issues/issue-list-item.tsx b/web/core/components/issues/sub-issues/issue-list-item.tsx index 28f6f47f31d..4e8e759dfad 100644 --- a/web/core/components/issues/sub-issues/issue-list-item.tsx +++ b/web/core/components/issues/sub-issues/issue-list-item.tsx @@ -4,6 +4,7 @@ import React from "react"; import { observer } from "mobx-react"; import { ChevronRight, X, Pencil, Trash, Link as LinkIcon, Loader } from "lucide-react"; import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssue, TIssueServiceType } from "@plane/types"; // ui import { ControlLink, CustomMenu, Tooltip } from "@plane/ui"; @@ -53,7 +54,7 @@ export const IssueListItem: React.FC = observer((props) => { subIssueOperations, issueServiceType = EIssueServiceType.ISSUES, } = props; - + const { t } = useTranslation(); const { issue: { getIssueById }, } = useIssueDetail(issueServiceType); @@ -184,7 +185,7 @@ export const IssueListItem: React.FC = observer((props) => { >
- Edit issue + {t("issue.edit")}
)} @@ -198,7 +199,7 @@ export const IssueListItem: React.FC = observer((props) => { >
- Copy issue link + {t("issue.copy_link")}
@@ -213,7 +214,9 @@ export const IssueListItem: React.FC = observer((props) => { >
- {`Remove ${issueServiceType === EIssueServiceType.ISSUES ? "parent" : ""} issue`} + {issueServiceType === EIssueServiceType.ISSUES + ? t("issue.remove.parent.label") + : t("issue.remove.label")}
)} @@ -229,7 +232,7 @@ export const IssueListItem: React.FC = observer((props) => { >
- Delete issue + {t("issue.delete.label")}
)} diff --git a/web/helpers/issue.helper.ts b/web/helpers/issue.helper.ts index dc7aa5a5b95..69fb2d8c79e 100644 --- a/web/helpers/issue.helper.ts +++ b/web/helpers/issue.helper.ts @@ -213,6 +213,7 @@ export const formatTextList = (TextArray: string[]): string => { export const getDescriptionPlaceholder = (isFocused: boolean, description: string | undefined): string => { const isDescriptionEmpty = !description || description === "

" || description.trim() === ""; + // TODO: Translate here if (!isDescriptionEmpty || isFocused) return "Press '/' for commands..."; else return "Click to add description"; };