diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index cab40f4fec0..6a7ad1bd689 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -308,9 +308,6 @@ "create_new_label": "Create new label", "start_date": "Start date", "due_date": "Due date", - "end_date": "End date", - "target_date": "Target date", - "cycle": "Cycle", "estimate": "Estimate", "change_parent_issue": "Change parent issue", "remove_parent_issue": "Remove parent issue", @@ -366,6 +363,35 @@ "error": "Error!" }, + "links": { + "toasts": { + "created": { + "title": "Link created", + "message": "The link has been successfully created" + }, + "not_created": { + "title": "Link not created", + "message": "The link could not be created" + }, + "updated": { + "title": "Link updated", + "message": "The link has been successfully updated" + }, + "not_updated": { + "title": "Link not updated", + "message": "The link could not be updated" + }, + "removed": { + "title": "Link removed", + "message": "The link has been successfully removed" + }, + "not_removed": { + "title": "Link not removed", + "message": "The link could not be removed" + } + } + }, + "home": { "empty": { "create_project": { @@ -400,33 +426,7 @@ "empty": "Save links to work things that you'd like handy.", "add": "Add quick Link", "title": "Quicklink", - "title_plural": "Quicklinks", - "toasts": { - "created": { - "title": "Link created", - "message": "The link has been successfully created" - }, - "not_created": { - "title": "Link not created", - "message": "The link could not be created" - }, - "updated": { - "title": "Link updated", - "message": "The link has been successfully updated" - }, - "not_updated": { - "title": "Link not updated", - "message": "The link could not be updated" - }, - "removed": { - "title": "Link removed", - "message": "The link has been successfully removed" - }, - "not_removed": { - "title": "Link not removed", - "message": "The link could not be removed" - } - } + "title_plural": "Quicklinks" }, "recents": { "title": "Recents", @@ -495,27 +495,41 @@ "filters": "Filters", "display": "Display", "load_more": "Load more", - "no_matches_found": "No matches found", "activity": "Activity", "analytics": "Analytics", - "success": "Success", - "error": "Error", + "success": "Success!", + "something_went_wrong": "Something went wrong", + "error": { + "label": "Error!", + "message": "Some error occurred. Please try again." + }, "group_by": "Group by", - "search": "Search", "epic": "Epic", - "issue": "Issue", + "work_item": "Work item", + "add": "Add", "warning": "Warning", "updating": "Updating", + "adding": "Adding", "update": "Update", "creating": "Creating", + "create": "Create", "cancel": "Cancel", "description": "Description", "title": "Title", "attachment": "Attachment", + "loading": "Loading...", + "attachments": "Attachments", + "links": "Links", + "properties": "Properties", + "parent": "Parent", + "remove": "Remove", + "archiving": "Archiving", + "archive": "Archive", "access": { "public": "Public", "private": "Private" }, + "sub_work_items": "Sub-work items", "order_by": { "label": "Order by", "manual": "Manual", @@ -541,10 +555,32 @@ "is_copied_to_clipboard": "Work item is copied to clipboard", "no_links_added_yet": "No links added yet", "add_link": "Add link", - "links": "Links", - "progress": "Progress" + "update_link": "Update link", + "progress": "Progress", + "attach": "Attach", + "create_new": "Create new", + "add_existing": "Add existing", + "type_or_paste_a_url": "Type or paste a URL", + "url_is_invalid": "URL is invalid", + "display_title": "Display title", + "link_title_placeholder": "What you'd like to see this link as", + "optional": "Optional", + "search": { + "label": "Search", + "placeholder": "Type to search...", + "no_matches_found": "No matches found", + "no_matching_results": "No matching results" + }, + "actions": { + "edit": "Edit", + "make_a_copy": "Make a copy", + "open_in_new_tab": "Open in new tab", + "copy_link": "Copy link", + "archive": "Archive", + "delete": "Delete" + } }, - + "form": { "title": { "required": "Title is required", @@ -556,18 +592,75 @@ "grouping_title": "{entity} Grouping", "priority": "{entity} ", "all": "All {entity}", - "drop_here_to_move": "Drop here to move the {entity}" + "drop_here_to_move": "Drop here to move the {entity}", + "delete": { + "label": "Delete {entity}", + "success": "{entity} deleted successfully", + "failed": "{entity} delete failed" + }, + "update": { + "failed": "{entity} update failed" + } }, "epic": { "all": "All Epics", - "label": "{count, plural, one {Epic} other {Epics}}" + "label": "{count, plural, one {Epic} other {Epics}}", + "new": "New Epic", + "adding": "Adding epic...", + "create": { + "success": "Epic created successfully" + }, + "add": { + "press_enter": "Press 'Enter' to add another epic" + }, + "title": { + "label": "Epic Title", + "required": "Epic title is required." + } }, "issue": { - "label": "{count, plural, one {Issue} other {Issues}}", - "all": "All Issues", - "add": "Add Issue", + "label": "{count, plural, one {Work item} other {Work items}}", + "all": "All Work items", + "title": { + "label": "Work item title", + "required": "Work item title is required." + }, + "add": { + "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." + }, + "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" + }, + "remove": { + "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." + }, + "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." + }, + "parent": { + "label": "Remove parent work item" + } + }, + "new": "New Work item", + "adding": "Adding work item...", + "create": { + "success": "Work item created successfully" + }, "priority": { "urgent": "Urgent", "high": "High", @@ -578,14 +671,14 @@ "properties": { "label": "Display Properties", "id": "ID", - "issue_type": "Issue Type", - "sub_issue_count": "Sub issue count", + "issue_type": "Work item Type", + "sub_issue_count": "Sub-work item count", "attachment_count": "Attachment count", "created_on": "Created on", - "sub_issue": "Sub-issue" + "sub_issue": "Sub-work item" }, "extra": { - "show_sub_issues": "Show sub-issues", + "show_sub_issues": "Show sub-work items", "show_empty_groups": "Show empty groups" } }, @@ -624,6 +717,36 @@ "upload": { "error": "Asset upload failed. Please try again later." } + }, + "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.", + "primary_button": { + "text": "View other work items" + } + } + }, + "sibling": { + "label": "Sibling work items" + }, + "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.", + "success": { + "label": "Archive success", + "message": "Your archives can be found in project archives." + }, + "failed": { + "message": "Work item could not be archived. Please try again." + } + }, + "relation": { + "relates_to": "Relates to", + "duplicate": "Duplicate of", + "blocked_by": "Blocked by", + "blocking": "Blocking" } }, @@ -749,7 +872,7 @@ "total_members": "Total members", "total_cycles": "Total cycles", "total_modules": "Total modules", - "pending_work_items":{ + "pending_work_items": { "title": "Pending work items", "empty_state": "Analysis of pending work items by co-workers appears here." }, @@ -757,11 +880,11 @@ "title": "Work items closed in a year", "empty_state": "Close work items to view analysis of the same in the form of a graph." }, - "most_work_items_created":{ + "most_work_items_created": { "title": "Most work items created", "empty_state": "Co-workers and the number of work items created by them appears here." }, - "most_work_items_closed":{ + "most_work_items_closed": { "title": "Most work items closed", "empty_state": "Co-workers and the number of work items closed by them appears here." }, @@ -1514,5 +1637,13 @@ "created_at": "Created date", "manual": "Manual" } + }, + "cycle": { + "label": "{count, plural, one {Cycle} other {Cycles}}", + "no_cycle": "No cycle" + }, + "module": { + "label": "{count, plural, one {Module} other {Modules}}", + "no_module": "No module" } } diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx index 80e1731666c..2cffdfe84dd 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx @@ -289,7 +289,7 @@ export const CycleIssuesHeader: React.FC = observer(() => { }} size="sm" > - {t("issue.add")} + {t("issue.add.label")} )} diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx index 5b68ae688ed..3dbcaccb1de 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx @@ -5,6 +5,8 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { useTheme } from "next-themes"; import useSWR from "swr"; +// i18n +import { useTranslation } from "@plane/i18n"; // ui import { Loader } from "@plane/ui"; // components @@ -19,6 +21,8 @@ import emptyIssueDark from "@/public/empty-state/search/issues-dark.webp"; import emptyIssueLight from "@/public/empty-state/search/issues-light.webp"; const IssueDetailsPage = observer(() => { + // i18n + const { t } = useTranslation(); // router const router = useAppRouter(); const { workspaceSlug, projectId, issueId } = useParams(); @@ -64,10 +68,10 @@ const IssueDetailsPage = observer(() => { {error ? ( router.push(`/${workspaceSlug}/projects/${projectId}/issues`), }} /> diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx index 7ea73bb1c94..6b83f367b1e 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx @@ -3,6 +3,8 @@ import { observer } from "mobx-react"; import Head from "next/head"; import { useParams } from "next/navigation"; +// i18n +import { useTranslation } from "@plane/i18n"; // components import { PageHead } from "@/components/core"; import { ProjectLayoutRoot } from "@/components/issues"; @@ -11,6 +13,8 @@ import { useProject } from "@/hooks/store"; const ProjectIssuesPage = observer(() => { const { projectId } = useParams(); + // i18n + const { t } = useTranslation(); // store const { getProjectById } = useProject(); @@ -20,13 +24,15 @@ const ProjectIssuesPage = observer(() => { // derived values const project = getProjectById(projectId.toString()); - const pageTitle = project?.name ? `${project?.name} - Issues` : undefined; + const pageTitle = project?.name ? `${project?.name} - ${t("issue.label", { count: 2 })}` : undefined; // Count is for pluralization return ( <> - {project?.name} - Issues + + {project?.name} - {t("issue.label", { count: 2 })} +
diff --git a/web/ce/components/issues/header.tsx b/web/ce/components/issues/header.tsx index f9e82b095ae..d98661e924e 100644 --- a/web/ce/components/issues/header.tsx +++ b/web/ce/components/issues/header.tsx @@ -6,6 +6,8 @@ import { useParams } from "next/navigation"; import { Circle, ExternalLink } from "lucide-react"; // plane constants import { EIssuesStoreType } from "@plane/constants"; +// i18n +import { useTranslation } from "@plane/i18n"; // ui import { Breadcrumbs, Button, LayersIcon, Tooltip, Header } from "@plane/ui"; // components @@ -31,6 +33,8 @@ export const IssuesHeader = observer(() => { const { issues: { getGroupIssueCount }, } = useIssues(EIssuesStoreType.PROJECT); + // i18n + const { t } = useTranslation(); const { currentProjectDetails, loader } = useProject(); @@ -78,7 +82,7 @@ export const IssuesHeader = observer(() => { rel="noopener noreferrer" > - Public + {t("workspace_projects.network.public.title")} ) : ( @@ -102,7 +106,8 @@ export const IssuesHeader = observer(() => { }} size="sm" > -
Add
Issue +
{t("issue.label", { count: 1 })}
+
{t("issue.add.label")}
) : ( <> diff --git a/web/ce/components/relations/index.tsx b/web/ce/components/relations/index.tsx index 5517ac06359..cb0ef3feefb 100644 --- a/web/ce/components/relations/index.tsx +++ b/web/ce/components/relations/index.tsx @@ -8,28 +8,28 @@ export * from "./activity"; export const ISSUE_RELATION_OPTIONS: Record = { relates_to: { key: "relates_to", - label: "Relates to", + i18n_label: "issue.relation.relates_to", className: "bg-custom-background-80 text-custom-text-200", icon: (size) => , placeholder: "Add related issues", }, duplicate: { key: "duplicate", - label: "Duplicate of", + i18n_label: "issue.relation.duplicate", className: "bg-custom-background-80 text-custom-text-200", icon: (size) => , placeholder: "None", }, blocked_by: { key: "blocked_by", - label: "Blocked by", + i18n_label: "issue.relation.blocked_by", className: "bg-red-500/20 text-red-700", icon: (size) => , placeholder: "None", }, blocking: { key: "blocking", - label: "Blocking", + i18n_label: "issue.relation.blocking", className: "bg-yellow-500/20 text-yellow-700", icon: (size) => , placeholder: "None", diff --git a/web/core/components/dropdowns/cycle/cycle-options.tsx b/web/core/components/dropdowns/cycle/cycle-options.tsx index e720fb78c9f..f3c55d8a707 100644 --- a/web/core/components/dropdowns/cycle/cycle-options.tsx +++ b/web/core/components/dropdowns/cycle/cycle-options.tsx @@ -8,6 +8,8 @@ import { usePopper } from "react-popper"; // components import { Check, Search } from "lucide-react"; import { Combobox } from "@headlessui/react"; +// i18n +import { useTranslation } from "@plane/i18n"; // icon import { TCycleGroups } from "@plane/types"; // ui @@ -36,6 +38,8 @@ type CycleOptionsProps = { export const CycleOptions: FC = observer((props) => { const { projectId, isOpen, referenceElement, placement, canRemoveCycle, currentCycleId } = props; + // i18n + const { t } = useTranslation(); //state hooks const [query, setQuery] = useState(""); const [popperElement, setPopperElement] = useState(null); @@ -103,11 +107,11 @@ export const CycleOptions: FC = observer((props) => { if (canRemoveCycle) { options?.unshift({ value: null, - query: "No cycle", + query: t("cycle.no_cycle"), content: (
- No cycle + {t("cycle.no_cycle")}
), }); @@ -132,7 +136,7 @@ export const CycleOptions: 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} /> @@ -159,10 +163,10 @@ export const CycleOptions: FC = observer((props) => { )) ) : ( -

No matches found

+

{t("common.no_matches_found")}

) ) : ( -

Loading...

+

{t("common.loading")}

)}
diff --git a/web/core/components/dropdowns/cycle/index.tsx b/web/core/components/dropdowns/cycle/index.tsx index 98124a0a19d..051c863e8bd 100644 --- a/web/core/components/dropdowns/cycle/index.tsx +++ b/web/core/components/dropdowns/cycle/index.tsx @@ -3,6 +3,7 @@ import { ReactNode, useRef, useState } from "react"; import { observer } from "mobx-react"; import { ChevronDown } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; // ui import { ComboDropDown, ContrastIcon } from "@plane/ui"; // helpers @@ -52,6 +53,8 @@ export const CycleDropdown: React.FC = observer((props) => { renderByDefault = true, currentCycleId, } = props; + // i18n + const { t } = useTranslation(); // states const [isOpen, setIsOpen] = useState(false); @@ -108,7 +111,7 @@ export const CycleDropdown: React.FC = observer((props) => { = observer((props) => { value, renderByDefault = true, } = props; + // i18n + const { t } = useTranslation(); // states const [isOpen, setIsOpen] = useState(false); // refs @@ -256,7 +260,7 @@ export const ModuleDropdown: React.FC = observer((props) => { { const { projectId, isOpen, referenceElement, placement, multiple } = props; + // i18n + const { t } = useTranslation(); // states const [query, setQuery] = useState(""); const [popperElement, setPopperElement] = useState(null); @@ -97,11 +101,11 @@ export const ModuleOptions = observer((props: Props) => { if (!multiple) options?.unshift({ value: null, - query: "No module", + query: t("module.no_module"), content: (
- No module + {t("module.no_module")}
), }); @@ -125,7 +129,7 @@ export const ModuleOptions = observer((props: 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} /> @@ -157,10 +161,10 @@ export const ModuleOptions = observer((props: Props) => { )) ) : ( -

No matching results

+

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

) ) : ( -

Loading...

+

{t("common.loading")}

)} diff --git a/web/core/components/home/widgets/links/use-links.tsx b/web/core/components/home/widgets/links/use-links.tsx index 3607f5b6abf..0cb1f6ac52a 100644 --- a/web/core/components/home/widgets/links/use-links.tsx +++ b/web/core/components/home/widgets/links/use-links.tsx @@ -36,17 +36,17 @@ export const useLinks = (workspaceSlug: string) => { if (!workspaceSlug) throw new Error("Missing required fields"); await createLink(workspaceSlug, data); setToast({ - message: t("home.quick_links.toasts.created.message"), + message: t("links.toasts.created.message"), type: TOAST_TYPE.SUCCESS, - title: t("home.quick_links.toasts.created.title"), + title: t("links.toasts.created.title"), }); toggleLinkModal(false); } catch (error: any) { console.error("error", error); setToast({ - message: error?.data?.error ?? t("home.quick_links.toasts.not_created.message"), + message: error?.data?.error ?? t("links.toasts.not_created.message"), type: TOAST_TYPE.ERROR, - title: t("home.quick_links.toasts.not_created.title"), + title: t("links.toasts.not_created.title"), }); throw error; } @@ -56,16 +56,16 @@ export const useLinks = (workspaceSlug: string) => { if (!workspaceSlug) throw new Error("Missing required fields"); await updateLink(workspaceSlug, linkId, data); setToast({ - message: t("home.quick_links.toasts.updated.message"), + message: t("links.toasts.updated.message"), type: TOAST_TYPE.SUCCESS, - title: t("home.quick_links.toasts.updated.title"), + title: t("links.toasts.updated.title"), }); toggleLinkModal(false); } catch (error: any) { setToast({ - message: error?.data?.error ?? t("home.quick_links.toasts.not_updated.message"), + message: error?.data?.error ?? t("links.toasts.not_updated.message"), type: TOAST_TYPE.ERROR, - title: t("home.quick_links.toasts.not_updated.title"), + title: t("links.toasts.not_updated.title"), }); throw error; } @@ -75,15 +75,15 @@ export const useLinks = (workspaceSlug: string) => { if (!workspaceSlug) throw new Error("Missing required fields"); await removeLink(workspaceSlug, linkId); setToast({ - message: t("home.quick_links.toasts.removed.message"), + message: t("links.toasts.removed.message"), type: TOAST_TYPE.SUCCESS, - title: t("home.quick_links.toasts.removed.message"), + title: t("links.toasts.removed.message"), }); } catch (error: any) { setToast({ - message: error?.data?.error ?? t("home.quick_links.toasts.not_removed.message"), + message: error?.data?.error ?? t("links.toasts.not_removed.message"), type: TOAST_TYPE.ERROR, - title: t("home.quick_links.toasts.not_removed.title"), + title: t("links.toasts.not_removed.title"), }); } }, diff --git a/web/core/components/issues/archive-issue-modal.tsx b/web/core/components/issues/archive-issue-modal.tsx index 4f944e26a0d..b3a8a9b9a63 100644 --- a/web/core/components/issues/archive-issue-modal.tsx +++ b/web/core/components/issues/archive-issue-modal.tsx @@ -2,6 +2,8 @@ import { useState, Fragment } from "react"; import { Dialog, Transition } from "@headlessui/react"; +// i18n +import { useTranslation } from "@plane/i18n"; // types import { TDeDupeIssue, TIssue } from "@plane/types"; // ui @@ -20,6 +22,7 @@ type Props = { export const ArchiveIssueModal: React.FC = (props) => { const { dataId, data, isOpen, handleClose, onSubmit } = props; + const { t } = useTranslation(); // states const [isArchiving, setIsArchiving] = useState(false); // store hooks @@ -44,16 +47,16 @@ export const ArchiveIssueModal: React.FC = (props) => { .then(() => { setToast({ type: TOAST_TYPE.SUCCESS, - title: "Archive success", - message: "Your archives can be found in project archives.", + title: t("common.archive.success.label"), + message: t("common.archive.success.message"), }); onClose(); }) .catch(() => setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Issue could not be archived. Please try again.", + title: t("common.error.label"), + message: t("common.archive.failed.message"), }) ) .finally(() => setIsArchiving(false)); @@ -88,17 +91,15 @@ export const ArchiveIssueModal: React.FC = (props) => {

- Archive issue {projectDetails?.identifier} {issue.sequence_id} + {t("issue.archive.label")} {projectDetails?.identifier} {issue.sequence_id}

-

- Are you sure you want to archive the issue? All your archived issues can be restored later. -

+

{t("issue.archive.confirm_message")}

diff --git a/web/core/components/issues/delete-issue-modal.tsx b/web/core/components/issues/delete-issue-modal.tsx index 9f84c2ea8d3..95d96bd1e9e 100644 --- a/web/core/components/issues/delete-issue-modal.tsx +++ b/web/core/components/issues/delete-issue-modal.tsx @@ -73,8 +73,10 @@ export const DeleteIssueModal: React.FC = (props) => { .then(() => { setToast({ type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: `${isSubIssue ? "Sub-issue" : isEpic ? "Epic" : "Issue"} deleted successfully`, + title: t("common.success"), + message: t("entity.delete.success", { + entity: isSubIssue ? t("common.sub_issue") : isEpic ? t("common.epic") : t("common.work_item"), + }), }); onClose(); }) @@ -100,7 +102,7 @@ export const DeleteIssueModal: React.FC = (props) => { handleSubmit={handleIssueDelete} isSubmitting={isDeleting} isOpen={isOpen} - title={`Delete ${isEpic ? "epic" : "issue"}`} + title={t("entity.delete.label", { entity: isEpic ? t("common.epic") : t("common.work_item") })} content={ <> {`Are you sure you want to delete ${isEpic ? "epic" : "issue"} `} diff --git a/web/core/components/issues/issue-detail-widgets/action-buttons.tsx b/web/core/components/issues/issue-detail-widgets/action-buttons.tsx index 18b6cc024ec..7b6dff67b6d 100644 --- a/web/core/components/issues/issue-detail-widgets/action-buttons.tsx +++ b/web/core/components/issues/issue-detail-widgets/action-buttons.tsx @@ -1,6 +1,8 @@ "use client"; import React, { FC } from "react"; import { Layers, Link, Paperclip, Waypoints } from "lucide-react"; +//i18n +import { useTranslation } from "@plane/i18n"; // components import { IssueAttachmentActionButton, @@ -19,13 +21,14 @@ type Props = { export const IssueDetailWidgetActionButtons: FC = (props) => { const { workspaceSlug, projectId, issueId, disabled } = props; + const { t } = useTranslation(); return (
} disabled={disabled} /> @@ -36,7 +39,7 @@ export const IssueDetailWidgetActionButtons: FC = (props) => { issueId={issueId} customButton={ } disabled={disabled} /> @@ -46,7 +49,7 @@ export const IssueDetailWidgetActionButtons: FC = (props) => { } disabled={disabled} /> @@ -59,7 +62,7 @@ export const IssueDetailWidgetActionButtons: FC = (props) => { issueId={issueId} customButton={ } disabled={disabled} /> diff --git a/web/core/components/issues/issue-detail-widgets/links/helper.tsx b/web/core/components/issues/issue-detail-widgets/links/helper.tsx index ae915beb898..71ad2c1e7a0 100644 --- a/web/core/components/issues/issue-detail-widgets/links/helper.tsx +++ b/web/core/components/issues/issue-detail-widgets/links/helper.tsx @@ -1,6 +1,7 @@ "use client"; import { useMemo } from "react"; import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssueLink, TIssueServiceType } from "@plane/types"; import { TOAST_TYPE, setToast } from "@plane/ui"; // hooks @@ -15,6 +16,8 @@ export const useLinkOperations = ( issueServiceType: TIssueServiceType = EIssueServiceType.ISSUES ): TLinkOperations => { const { createLink, updateLink, removeLink } = useIssueDetail(issueServiceType); + // i18n + const { t } = useTranslation(); const handleLinkOperations: TLinkOperations = useMemo( () => ({ @@ -23,15 +26,15 @@ export const useLinkOperations = ( if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields"); await createLink(workspaceSlug, projectId, issueId, data); setToast({ - message: "The link has been successfully created", + message: t("links.toasts.created.message"), type: TOAST_TYPE.SUCCESS, - title: "Link created", + title: t("links.toasts.created.title"), }); } catch (error: any) { setToast({ - message: error?.data?.error ?? "The link could not be created", + message: error?.data?.error ?? t("links.toasts.not_created.message"), type: TOAST_TYPE.ERROR, - title: "Link not created", + title: t("links.toasts.not_created.title"), }); throw error; } @@ -41,15 +44,15 @@ export const useLinkOperations = ( if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields"); await updateLink(workspaceSlug, projectId, issueId, linkId, data); setToast({ - message: "The link has been successfully updated", + message: t("links.toasts.updated.message"), type: TOAST_TYPE.SUCCESS, - title: "Link updated", + title: t("links.toasts.updated.title"), }); } catch (error) { setToast({ - message: "The link could not be updated", + message: t("links.toasts.not_updated.message"), type: TOAST_TYPE.ERROR, - title: "Link not updated", + title: t("links.toasts.not_updated.title"), }); throw error; } @@ -59,15 +62,15 @@ export const useLinkOperations = ( if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields"); await removeLink(workspaceSlug, projectId, issueId, linkId); setToast({ - message: "The link has been successfully removed", + message: t("links.toasts.removed.message"), type: TOAST_TYPE.SUCCESS, - title: "Link removed", + title: t("links.toasts.removed.title"), }); } catch (error) { setToast({ - message: "The link could not be removed", + message: t("links.toasts.not_removed.message"), type: TOAST_TYPE.ERROR, - title: "Link not removed", + title: t("links.toasts.not_removed.title"), }); } }, diff --git a/web/core/components/issues/issue-detail-widgets/relations/content.tsx b/web/core/components/issues/issue-detail-widgets/relations/content.tsx index 54517dcc769..e6243da099b 100644 --- a/web/core/components/issues/issue-detail-widgets/relations/content.tsx +++ b/web/core/components/issues/issue-detail-widgets/relations/content.tsx @@ -3,6 +3,7 @@ import { FC, useState } from "react"; import { observer } from "mobx-react"; // plane imports import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssue, TIssueServiceType } from "@plane/types"; import { Collapsible } from "@plane/ui"; // components @@ -30,7 +31,7 @@ type TIssueCrudState = { toggle: boolean; issueId: string | undefined; issue: TI export type TRelationObject = { key: TIssueRelationTypes; - label: string; + i18n_label: string; className: string; icon: (size: number) => React.ReactElement; placeholder: string; @@ -38,6 +39,7 @@ export type TRelationObject = { export const RelationsCollapsibleContent: FC = observer((props) => { const { workspaceSlug, projectId, issueId, disabled = false, issueServiceType = EIssueServiceType.ISSUES } = props; + const { t } = useTranslation(); // state const [issueCrudState, setIssueCrudState] = useState<{ update: TIssueCrudState; @@ -94,7 +96,7 @@ export const RelationsCollapsibleContent: FC = observer((props) => { relationKey: relationKey, issueIds: issueIds, icon: issueRelationOption?.icon, - label: issueRelationOption?.label, + label: t(issueRelationOption?.i18n_label), className: issueRelationOption?.className, }; }); diff --git a/web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx b/web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx index b1ff260f6f5..2d7e5a2bab3 100644 --- a/web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx +++ b/web/core/components/issues/issue-detail-widgets/relations/quick-action-button.tsx @@ -3,6 +3,7 @@ import React, { FC } from "react"; import { observer } from "mobx-react"; import { Plus } from "lucide-react"; import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssueServiceType } from "@plane/types"; import { CustomMenu } from "@plane/ui"; // hooks @@ -20,6 +21,7 @@ type Props = { export const RelationActionButton: FC = observer((props) => { const { customButton, issueId, disabled = false, issueServiceType = EIssueServiceType.ISSUES } = props; + const { t } = useTranslation(); // store hooks const { toggleRelationModal, setRelationKey } = useIssueDetail(issueServiceType); @@ -56,7 +58,7 @@ export const RelationActionButton: FC = observer((props) => { >
{item.icon(12)} - {item.label} + {t(item.i18n_label)}
); diff --git a/web/core/components/issues/issue-detail-widgets/sub-issues/quick-action-button.tsx b/web/core/components/issues/issue-detail-widgets/sub-issues/quick-action-button.tsx index 73770bb130c..d200afc5f7a 100644 --- a/web/core/components/issues/issue-detail-widgets/sub-issues/quick-action-button.tsx +++ b/web/core/components/issues/issue-detail-widgets/sub-issues/quick-action-button.tsx @@ -3,6 +3,7 @@ import React, { FC } from "react"; import { observer } from "mobx-react"; import { LayersIcon, Plus } from "lucide-react"; import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssue, TIssueServiceType } from "@plane/types"; import { CustomMenu } from "@plane/ui"; // hooks @@ -17,6 +18,7 @@ type Props = { export const SubIssuesActionButton: FC = observer((props) => { const { issueId, customButton, disabled = false, issueServiceType = EIssueServiceType.ISSUES } = props; + const { t } = useTranslation(); // store hooks const { issue: { getIssueById }, @@ -63,12 +65,12 @@ export const SubIssuesActionButton: FC = observer((props) => { // options const optionItems = [ { - label: "Create new", + i18n_label: "common.create_new", icon: , onClick: handleCreateNew, }, { - label: "Add existing", + i18n_label: "common.add_existing", icon: , onClick: handleAddExisting, }, @@ -90,7 +92,7 @@ export const SubIssuesActionButton: FC = observer((props) => { >
{item.icon} - {item.label} + {t(item.i18n_label)}
))} diff --git a/web/core/components/issues/issue-detail/issue-activity/root.tsx b/web/core/components/issues/issue-detail/issue-activity/root.tsx index da54174ccc6..f3682440a01 100644 --- a/web/core/components/issues/issue-detail/issue-activity/root.tsx +++ b/web/core/components/issues/issue-detail/issue-activity/root.tsx @@ -97,7 +97,7 @@ export const IssueActivity: FC = observer((props) => { return comment; } catch (error) { setToast({ - title: t("common.error"), + title: t("common.error.label"), type: TOAST_TYPE.ERROR, message: t("issue.comments.create.error"), }); @@ -114,7 +114,7 @@ export const IssueActivity: FC = observer((props) => { }); } catch (error) { setToast({ - title: t("common.error"), + title: t("common.error.label"), type: TOAST_TYPE.ERROR, message: t("issue.comments.update.error"), }); @@ -131,7 +131,7 @@ export const IssueActivity: FC = observer((props) => { }); } catch (error) { setToast({ - title: t("common.error"), + title: t("common.error.label"), type: TOAST_TYPE.ERROR, message: t("issue.comments.remove.error"), }); 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 2da5d0f7955..5ac0e8e8e9a 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 @@ -4,6 +4,7 @@ import { FC, useEffect } from "react"; import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; import { EIssueServiceType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // plane types import type { TIssueLinkEditableFields, TIssueServiceType } from "@plane/types"; // plane ui @@ -32,6 +33,8 @@ const defaultValues: TIssueLinkCreateFormFieldOptions = { }; export const IssueLinkCreateUpdateModal: FC = observer((props) => { + // i18n + const { t } = useTranslation(); // props const { isModalOpen, handleOnClose, linkOperations, issueServiceType = EIssueServiceType.ISSUES } = props; // react hook form @@ -70,7 +73,9 @@ export const IssueLinkCreateUpdateModal: FC = observe
-

{preloadedData?.id ? "Update" : "Add"} link

+

+ {preloadedData?.id ? t("common.update_link") : t("common.add_link")} +

= observe onChange={onChange} ref={ref} hasError={Boolean(errors.title)} - placeholder="What you'd like to see this link as" + placeholder={t("common.link_title_placeholder")} className="w-full" /> )} @@ -123,10 +128,17 @@ export const IssueLinkCreateUpdateModal: FC = observe
diff --git a/web/core/components/issues/issue-detail/parent-select.tsx b/web/core/components/issues/issue-detail/parent-select.tsx index d83bc635b47..72c0de41287 100644 --- a/web/core/components/issues/issue-detail/parent-select.tsx +++ b/web/core/components/issues/issue-detail/parent-select.tsx @@ -4,6 +4,7 @@ import React from "react"; import { observer } from "mobx-react"; import Link from "next/link"; import { Pencil, X } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; // ui import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; // components @@ -29,6 +30,7 @@ type TIssueParentSelect = { export const IssueParentSelect: React.FC = observer((props) => { const { className = "", disabled = false, issueId, issueOperations, projectId, workspaceSlug } = props; + const { t } = useTranslation(); // store hooks const { getProjectById } = useProject(); const { @@ -72,8 +74,8 @@ export const IssueParentSelect: React.FC = observer((props) } catch (error) { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Something went wrong", + title: t("common.error.label"), + message: t("common.something_went_wrong"), }); } }; @@ -126,7 +128,7 @@ export const IssueParentSelect: React.FC = observer((props) {!disabled && ( - + { e.preventDefault(); @@ -140,7 +142,7 @@ export const IssueParentSelect: React.FC = observer((props) )}
) : ( - Add parent issue + {t("issue.add.parent")} )} {!disabled && ( = observer((props) => { const { workspaceSlug, projectId, issueId, issue, issueOperations } = props; + const { t } = useTranslation(); // hooks const { issueMap } = useIssues(); const { getProjectStates } = useProjectState(); @@ -66,7 +68,7 @@ export const IssueParentDetail: FC = observer((props) => {
- Sibling issues + {t("issue.sibling.label")}
@@ -76,7 +78,7 @@ export const IssueParentDetail: FC = observer((props) => { className="flex items-center gap-2 py-2 text-red-500" > - Remove Parent Issue + {t("issue.remove.parent.label")}
diff --git a/web/core/components/issues/issue-detail/root.tsx b/web/core/components/issues/issue-detail/root.tsx index 6fd5b21663a..357d2678bd4 100644 --- a/web/core/components/issues/issue-detail/root.tsx +++ b/web/core/components/issues/issue-detail/root.tsx @@ -4,7 +4,8 @@ import { FC, useMemo } from "react"; import { observer } from "mobx-react"; import { usePathname } from "next/navigation"; // types -import { EIssuesStoreType ,ISSUE_UPDATED, ISSUE_DELETED, ISSUE_ARCHIVED } from "@plane/constants"; +import { EIssuesStoreType, ISSUE_UPDATED, ISSUE_DELETED, ISSUE_ARCHIVED } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TIssue } from "@plane/types"; // ui import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui"; @@ -53,6 +54,7 @@ export type TIssueDetailRoot = { }; export const IssueDetailRoot: FC = observer((props) => { + const { t } = useTranslation(); const { workspaceSlug, projectId, issueId, is_archived = false } = props; // router const router = useAppRouter(); @@ -110,9 +112,9 @@ export const IssueDetailRoot: FC = observer((props) => { path: pathname, }); setToast({ - title: "Error!", + title: t("common.error.label"), type: TOAST_TYPE.ERROR, - message: "Issue update failed", + message: t("entity.update.failed", { entity: t("issue.label") }), }); } }, @@ -121,9 +123,9 @@ export const IssueDetailRoot: FC = observer((props) => { if (is_archived) await removeArchivedIssue(workspaceSlug, projectId, issueId); else await removeIssue(workspaceSlug, projectId, issueId); setToast({ - title: "Success!", + title: t("common.success"), type: TOAST_TYPE.SUCCESS, - message: "Issue deleted successfully", + message: t("entity.delete.success", { entity: t("issue.label") }), }); captureIssueEvent({ eventName: ISSUE_DELETED, @@ -133,9 +135,9 @@ export const IssueDetailRoot: FC = observer((props) => { } catch (error) { console.log("Error in deleting issue:", error); setToast({ - title: "Error!", + title: t("common.error.label"), type: TOAST_TYPE.ERROR, - message: "Issue delete failed", + message: t("entity.delete.failed", { entity: t("issue.label") }), }); captureIssueEvent({ eventName: ISSUE_DELETED, @@ -176,8 +178,8 @@ export const IssueDetailRoot: FC = observer((props) => { } catch (error) { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Issue could not be added to the cycle. Please try again.", + title: t("common.error.label"), + message: t("issue.add.cycle.failed"), }); captureIssueEvent({ eventName: ISSUE_UPDATED, @@ -205,8 +207,8 @@ export const IssueDetailRoot: FC = observer((props) => { } catch (error) { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Issue could not be added to the cycle. Please try again.", + title: t("common.error.label"), + message: t("issue.add.cycle.failed"), }); captureIssueEvent({ eventName: ISSUE_UPDATED, @@ -223,14 +225,14 @@ export const IssueDetailRoot: FC = observer((props) => { try { const removeFromCyclePromise = 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("common.success"), + message: () => t("issue.remove.cycle.success"), }, error: { - title: "Error!", - message: () => "Issue could not be removed from the cycle. Please try again.", + title: t("common.error.label"), + message: () => t("issue.remove.cycle.failed"), }, }); await removeFromCyclePromise; @@ -259,14 +261,14 @@ export const IssueDetailRoot: FC = observer((props) => { try { const removeFromModulePromise = removeIssueFromModule(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("common.success"), + message: () => t("issue.remove.module.success"), }, error: { - title: "Error!", - message: () => "Issue could not be removed from the module. Please try again.", + title: t("common.error.label"), + message: () => t("issue.remove.module.failed"), }, }); await removeFromModulePromise; @@ -338,10 +340,10 @@ export const IssueDetailRoot: FC = observer((props) => { {!issue ? ( router.push(`/${workspaceSlug}/projects/${projectId}/issues`), }} /> diff --git a/web/core/components/issues/issue-detail/sidebar.tsx b/web/core/components/issues/issue-detail/sidebar.tsx index 102dc6e5a33..b91c6fb3f2c 100644 --- a/web/core/components/issues/issue-detail/sidebar.tsx +++ b/web/core/components/issues/issue-detail/sidebar.tsx @@ -3,6 +3,8 @@ import React from "react"; import { observer } from "mobx-react"; import { CalendarCheck2, CalendarClock, LayoutPanelTop, Signal, Tag, Triangle, UserCircle2, Users } from "lucide-react"; +// i18n +import { useTranslation } from "@plane/i18n"; // ui import { ContrastIcon, DiceIcon, DoubleCircleIcon } from "@plane/ui"; // components @@ -36,6 +38,7 @@ type Props = { }; export const IssueDetailsSidebar: React.FC = observer((props) => { + const { t } = useTranslation(); const { workspaceSlug, projectId, issueId, issueOperations, isEditable } = props; // store hooks const { getProjectById } = useProject(); @@ -64,13 +67,13 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { <>
-
Properties
+
{t("common.properties")}
{/* TODO: render properties using a common component */}
- State + {t("common.state")}
= observer((props) => {
- Assignees + {t("common.assignees")}
issueOperations.update(workspaceSlug, projectId, issueId, { assignee_ids: val })} disabled={!isEditable} projectId={projectId?.toString() ?? ""} - placeholder="Add assignees" + placeholder={t("issue.add.assignee")} multiple buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"} className="group w-3/5 flex-grow" @@ -113,7 +116,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => {
- Priority + {t("common.priority")}
= observer((props) => {
- Created by + {t("common.created_by")}
@@ -142,10 +145,10 @@ export const IssueDetailsSidebar: React.FC = observer((props) => {
- Start date + {t("common.order_by.start_date")}
issueOperations.update(workspaceSlug, projectId, issueId, { @@ -168,10 +171,10 @@ export const IssueDetailsSidebar: React.FC = observer((props) => {
- Due date + {t("common.order_by.due_date")}
issueOperations.update(workspaceSlug, projectId, issueId, { @@ -198,7 +201,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => {
- Estimate + {t("common.estimate")}
= observer((props) => { className="group w-3/5 flex-grow" buttonContainerClassName="w-full text-left" buttonClassName={`text-sm ${issue?.estimate_point !== null ? "" : "text-custom-text-400"}`} - placeholder="None" + placeholder={t("common.none")} hideIcon dropdownArrow dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline" @@ -223,7 +226,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => {
- Modules + {t("common.modules")}
= observer((props) => {
- Cycle + {t("common.cycle")}
= observer((props) => {
- Parent + {t("common.parent")}
= observer((props) => {
- Labels + {t("common.labels")}
= observer((props) => { @@ -29,15 +27,10 @@ export const FilterIssueGrouping: React.FC = observer((props) => { const activeIssueType = selectedIssueType ?? null; - // hooks - const { t } = useTranslation(); - return ( <> setPreviewEnabled(!previewEnabled)} /> @@ -48,7 +41,7 @@ export const FilterIssueGrouping: React.FC = observer((props) => { key={issueType?.key} isChecked={activeIssueType === issueType?.key ? true : false} onClick={() => handleUpdate(issueType?.key)} - title={`${t(issueType.i18n_title)} ${isEpic ? t("epic.label", { count: 2 }) : t("issue.label", { count: 2 })}`} + title={`${issueType.title} ${isEpic ? "Epics" : "Issues"}`} multiple={false} /> ))} diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx b/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx index c9f463222b1..84911f0405b 100644 --- a/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx +++ b/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx @@ -51,7 +51,6 @@ type Props = { }; export const FilterSelection: React.FC = observer((props) => { - const { filters, displayFilters, @@ -101,7 +100,7 @@ export const FilterSelection: React.FC = observer((props) => { setFiltersSearchQuery(e.target.value)} autoFocus={!isMobile} diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/priority.tsx b/web/core/components/issues/issue-layouts/filters/header/filters/priority.tsx index 8d763899590..1d3240bcfa7 100644 --- a/web/core/components/issues/issue-layouts/filters/header/filters/priority.tsx +++ b/web/core/components/issues/issue-layouts/filters/header/filters/priority.tsx @@ -28,7 +28,7 @@ export const FilterPriority: React.FC = observer((props) => { return ( <> 0 ? ` (${appliedFiltersCount})` : ""}`} + title={`Priority ${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`} isPreviewEnabled={previewEnabled} handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)} /> diff --git a/web/core/components/issues/issue-layouts/properties/all-properties.tsx b/web/core/components/issues/issue-layouts/properties/all-properties.tsx index 1eb72c840c3..f3759c9308c 100644 --- a/web/core/components/issues/issue-layouts/properties/all-properties.tsx +++ b/web/core/components/issues/issue-layouts/properties/all-properties.tsx @@ -8,6 +8,8 @@ import { useParams, usePathname } from "next/navigation"; import { CalendarCheck2, CalendarClock, Layers, Link, Paperclip } from "lucide-react"; // types import { ISSUE_UPDATED } from "@plane/constants"; +// i18n +import { useTranslation } from "@plane/i18n"; import { TIssue, IIssueDisplayProperties, TIssuePriorities } from "@plane/types"; // ui import { Tooltip } from "@plane/ui"; @@ -47,6 +49,8 @@ export interface IIssueProperties { export const IssueProperties: React.FC = observer((props) => { const { issue, updateIssue, displayProperties, activeLayout, isReadOnly, className, isEpic = false } = props; + // i18n + const { t } = useTranslation(); // store hooks const { getProjectById } = useProject(); const { labelMap } = useLabel(); @@ -311,7 +315,7 @@ export const IssueProperties: React.FC = observer((props) => { value={issue.start_date ?? null} onChange={handleStartDate} maxDate={maxDate} - placeholder="Start date" + placeholder={t("common.order_by.start_date")} icon={} buttonVariant={issue.start_date ? "border-with-text" : "border-without-text"} optionsClassName="z-10" @@ -329,7 +333,7 @@ export const IssueProperties: React.FC = observer((props) => { value={issue?.target_date ?? null} onChange={handleTargetDate} minDate={minDate} - placeholder="Due date" + placeholder={t("common.order_by.due_date")} icon={} buttonVariant={issue.target_date ? "border-with-text" : "border-without-text"} buttonClassName={shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group) ? "text-red-500" : ""} @@ -354,7 +358,7 @@ export const IssueProperties: React.FC = observer((props) => { buttonVariant={issue.assignee_ids?.length > 0 ? "transparent-without-text" : "border-without-text"} buttonClassName={issue.assignee_ids?.length > 0 ? "hover:bg-transparent px-0" : ""} showTooltip={issue?.assignee_ids?.length === 0} - placeholder="Assignees" + placeholder={t("common.assignees")} optionsClassName="z-10" tooltipContent="" renderByDefault={isMobile} @@ -431,7 +435,7 @@ export const IssueProperties: React.FC = observer((props) => { shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!subIssueCount} > = observer((props) => { shouldRenderProperty={(properties) => !!properties.attachment_count && !!issue.attachment_count} > = observer((props) => { shouldRenderProperty={(properties) => !!properties.link && !!issue.link_count} > = observer((pro fullWidth = false, fullHeight = false, } = props; + // i18n + const { t } = useTranslation(); // states const [isOpen, setIsOpen] = useState(false); // refs @@ -85,7 +89,13 @@ export const IssuePropertyLabels: React.FC = observer((pro const NoLabel = useMemo( () => ( - +
= observer((pro value.includes(l?.id)) .map((l) => l?.name) @@ -136,7 +146,7 @@ export const IssuePropertyLabels: React.FC = observer((pro = observer((p placements = "bottom-end", parentRef, } = props; + // i18n + const { t } = useTranslation(); // router const { workspaceSlug } = useParams(); const pathname = usePathname(); @@ -84,62 +88,65 @@ export const ProjectIssueQuickActions: React.FC = observer((p ["id"] ); - const MENU_ITEMS: TContextMenuItem[] = [ - { - key: "edit", - title: "Edit", - icon: Pencil, - action: () => { - setTrackElement(activeLayout); - setIssueToEdit(issue); - setCreateUpdateIssueModal(true); + const MENU_ITEMS: TContextMenuItem[] = useMemo( + () => [ + { + key: "edit", + title: t("common.actions.edit"), + icon: Pencil, + action: () => { + setTrackElement(activeLayout); + setIssueToEdit(issue); + setCreateUpdateIssueModal(true); + }, + shouldRender: isEditingAllowed, }, - shouldRender: isEditingAllowed, - }, - { - key: "make-a-copy", - title: "Make a copy", - icon: Copy, - action: () => { - setTrackElement(activeLayout); - setCreateUpdateIssueModal(true); + { + key: "make-a-copy", + title: t("common.actions.make_a_copy"), + icon: Copy, + action: () => { + setTrackElement(activeLayout); + setCreateUpdateIssueModal(true); + }, + shouldRender: isEditingAllowed, }, - shouldRender: isEditingAllowed, - }, - { - key: "open-in-new-tab", - title: "Open in new tab", - icon: ExternalLink, - action: handleOpenInNewTab, - }, - { - key: "copy-link", - title: "Copy link", - icon: Link, - action: handleCopyIssueLink, - }, - { - key: "archive", - title: "Archive", - description: isInArchivableGroup ? undefined : "Only completed or canceled\nissues can be archived", - icon: ArchiveIcon, - className: "items-start", - iconClassName: "mt-1", - action: () => setArchiveIssueModal(true), - disabled: !isInArchivableGroup, - shouldRender: isArchivingAllowed, - }, - { - key: "delete", - title: "Delete", - icon: Trash2, - action: () => { - setTrackElement(activeLayout); - setDeleteIssueModal(true); + { + key: "open-in-new-tab", + title: t("common.actions.open_in_new_tab"), + icon: ExternalLink, + action: handleOpenInNewTab, }, - shouldRender: isDeletingAllowed, - }, - ]; + { + key: "copy-link", + title: t("common.actions.copy_link"), + icon: Link, + action: handleCopyIssueLink, + }, + { + key: "archive", + title: t("common.actions.archive"), + description: isInArchivableGroup ? undefined : t("issue.archive.description"), + icon: ArchiveIcon, + className: "items-start", + iconClassName: "mt-1", + action: () => setArchiveIssueModal(true), + disabled: !isInArchivableGroup, + shouldRender: isArchivingAllowed, + }, + { + key: "delete", + title: t("common.actions.delete"), + icon: Trash2, + action: () => { + setTrackElement(activeLayout); + setDeleteIssueModal(true); + }, + shouldRender: isDeletingAllowed, + }, + ], + [t] + ); return ( <> diff --git a/web/core/components/issues/issue-layouts/quick-add/form/list.tsx b/web/core/components/issues/issue-layouts/quick-add/form/list.tsx index 75e2c8d3a9a..a977a2918ad 100644 --- a/web/core/components/issues/issue-layouts/quick-add/form/list.tsx +++ b/web/core/components/issues/issue-layouts/quick-add/form/list.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 ListQuickAddIssueForm: FC = observer((props) => { const { ref, projectDetail, 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/root.tsx b/web/core/components/issues/issue-layouts/quick-add/root.tsx index ed68405e33b..2dd6ef9cf44 100644 --- a/web/core/components/issues/issue-layouts/quick-add/root.tsx +++ b/web/core/components/issues/issue-layouts/quick-add/root.tsx @@ -7,6 +7,8 @@ import { useForm, UseFormRegister } from "react-hook-form"; import { PlusIcon } from "lucide-react"; // plane constants import { EIssueLayoutTypes, EIssueServiceType, ISSUE_CREATED } from "@plane/constants"; +// i18n +import { useTranslation } from "@plane/i18n"; import { IProject, TIssue } from "@plane/types"; // ui import { setPromiseToast } from "@plane/ui"; @@ -64,6 +66,8 @@ export const QuickAddIssueRoot: FC = observer((props) => { quickAddCallback, isEpic = false, } = props; + // i18n + const { t } = useTranslation(); // router const { workspaceSlug, projectId } = useParams(); const pathname = usePathname(); @@ -111,10 +115,10 @@ export const QuickAddIssueRoot: FC = observer((props) => { if (quickAddCallback) { const quickAddPromise = quickAddCallback(projectId.toString(), { ...payload }); setPromiseToast(quickAddPromise, { - loading: `Adding ${isEpic ? "epic" : "issue"}...`, + loading: isEpic ? t("epic.adding") : t("issue.adding"), success: { - title: "Success!", - message: () => `${isEpic ? "Epic" : "Issue"} created successfully.`, + title: t("common.success"), + message: () => `${isEpic ? t("epic.create.success") : t("issue.create.success")}`, actionItems: (data) => ( = observer((props) => { ), }, error: { - title: "Error!", - message: (err) => err?.message || "Some error occurred. Please try again.", + title: t("common.error.label"), + message: (err) => err?.message || t("common.error.message"), }, }); @@ -180,7 +184,7 @@ export const QuickAddIssueRoot: FC = observer((props) => { onClick={() => handleIsOpen(true)} > - {`New ${isEpic ? "Epic" : "Issue"}`} + {t(`${isEpic ? "epic.new" : "issue.new"}`)}
)} diff --git a/web/core/components/issues/issue-modal/components/default-properties.tsx b/web/core/components/issues/issue-modal/components/default-properties.tsx index 631b20f593e..63f0c12b299 100644 --- a/web/core/components/issues/issue-modal/components/default-properties.tsx +++ b/web/core/components/issues/issue-modal/components/default-properties.tsx @@ -211,7 +211,7 @@ export const IssueDefaultProperties: React.FC = ob onChange(cycleId); handleFormChange(); }} - placeholder={t("cycle")} + placeholder={t("cycle.label", { count: 1 })} value={value} buttonVariant="border-with-text" tabIndex={getIndex("cycle_id")} diff --git a/web/core/components/issues/parent-issues-list-modal.tsx b/web/core/components/issues/parent-issues-list-modal.tsx index 5d2d46caf68..30a10df6bae 100644 --- a/web/core/components/issues/parent-issues-list-modal.tsx +++ b/web/core/components/issues/parent-issues-list-modal.tsx @@ -6,6 +6,8 @@ import { useParams } from "next/navigation"; import { Rocket, Search } from "lucide-react"; // headless ui import { Combobox, Dialog, Transition } from "@headlessui/react"; +// i18n +import { useTranslation } from "@plane/i18n"; // types import { ISearchIssueResponse } from "@plane/types"; // ui @@ -44,6 +46,9 @@ export const ParentIssuesListModal: React.FC = ({ issueId, searchEpic = false, }) => { + // i18n + const { t } = useTranslation(); + const [isLoading, setIsLoading] = useState(false); const [searchTerm, setSearchTerm] = useState(""); const [issues, setIssues] = useState([]); @@ -122,7 +127,7 @@ export const ParentIssuesListModal: React.FC = ({ /> setSearchTerm(e.target.value)} displayValue={() => ""} diff --git a/web/core/components/issues/title-input.tsx b/web/core/components/issues/title-input.tsx index e1c1548dd50..463b81a7910 100644 --- a/web/core/components/issues/title-input.tsx +++ b/web/core/components/issues/title-input.tsx @@ -2,6 +2,7 @@ import { FC, useState, useEffect, useCallback } from "react"; import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; import { TNameDescriptionLoader } from "@plane/types"; // components import { TextArea } from "@plane/ui"; @@ -37,6 +38,7 @@ export const IssueTitleInput: FC = observer((props) => { className, containerClassName, } = props; + const { t } = useTranslation(); // states const [title, setTitle] = useState(""); const [isLengthVisible, setIsLengthVisible] = useState(false); @@ -119,7 +121,7 @@ export const IssueTitleInput: FC = observer((props) => { value={title} onChange={handleTitleChange} maxLength={255} - placeholder="Issue title" + placeholder={t("issue.title.label")} onFocus={() => setIsLengthVisible(true)} onBlur={() => setIsLengthVisible(false)} /> @@ -135,7 +137,7 @@ export const IssueTitleInput: FC = observer((props) => { /255
- {title?.length === 0 && Title is required} + {title?.length === 0 && {t("form.title.required")}}
); });