diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json
index bff3397ecce..b3877581ddd 100644
--- a/packages/i18n/src/locales/en/translations.json
+++ b/packages/i18n/src/locales/en/translations.json
@@ -1484,6 +1484,32 @@
},
"project_cycles": {
+ "add_cycle": "Add cycle",
+ "more_details": "More details",
+ "cycle": "Cycle",
+ "update_cycle": "Update cycle",
+ "create_cycle": "Create cycle",
+ "no_matching_cycles": "No matching cycles",
+ "remove_filters_to_see_all_cycles": "Remove the filters to see all cycles",
+ "remove_search_criteria_to_see_all_cycles": "Remove the search criteria to see all cycles",
+ "only_completed_cycles_can_be_archived": "Only completed cycles can be archived",
+ "active_cycle": {
+ "label" :"Active cycle",
+ "progress": "Progress",
+ "chart": "Burndown chart",
+ "priority_issue": "Priority issues",
+ "assignees": "Assignees",
+ "issue_burndown": "Issue burndown",
+ "ideal": "Ideal",
+ "current": "Current",
+ "labels": "Labels"
+ },
+ "upcoming_cycle": {
+ "label": "Upcoming cycle"
+ },
+ "completed_cycle": {
+ "label": "Completed cycle"
+ },
"status": {
"days_left": "Days left",
"completed": "Completed",
@@ -1491,6 +1517,55 @@
"in_progress": "In progress",
"draft": "Draft"
},
+ "action": {
+ "restore": {
+ "title": "Restore cycle",
+ "success": {
+ "title": "Cycle restored",
+ "description": "The cycle has been restored."
+ },
+ "failed": {
+ "title": "Cycle restore failed",
+ "description": "The cycle could not be restored. Please try again."
+ }
+ },
+ "favorite": {
+ "loading": "Adding cycle to favorites...",
+ "success": {
+ "description": "Cycle added to favorites.",
+ "title": "Success!"
+ },
+ "failed": {
+ "description": "Couldn't add the cycle to favorites. Please try again.",
+ "title": "Error!"
+ }
+ },
+ "unfavorite": {
+ "loading": "Removing cycle from favorites...",
+ "success": {
+ "description": "Cycle removed from favorites.",
+ "title": "Success!"
+ },
+ "failed": {
+ "description": "Couldn't remove the cycle from favorites. Please try again.",
+ "title": "Error!"
+ }
+ },
+ "update": {
+ "loading": "Updating cycle...",
+ "success": {
+ "description": "Cycle updated successfully.",
+ "title": "Success!"
+ },
+ "failed": {
+ "description": "Error updating the cycle. Please try again.",
+ "title": "Error!"
+ },
+ "error": {
+ "already_exists": "You already have a cycle on the given dates, if you want to create a draft cycle, you can do that by removing both the dates."
+ }
+ }
+ },
"empty_state": {
"general": {
"title": "Group and timebox your work in Cycles.",
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx
index b067c873298..28a99a2b966 100644
--- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx
+++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx
@@ -4,6 +4,7 @@ import { FC } from "react";
import { observer } from "mobx-react";
// ui
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
+import { useTranslation } from "@plane/i18n";
import { Breadcrumbs, Button, ContrastIcon, Header } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
@@ -23,6 +24,7 @@ export const CyclesListHeader: FC = observer(() => {
const { setTrackElement } = useEventTracker();
const { allowPermissions } = useUserPermissions();
const { currentProjectDetails, loader } = useProject();
+ const { t } = useTranslation();
const canUserCreateCycle = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
@@ -36,7 +38,12 @@ export const CyclesListHeader: FC = observer(() => {
} />}
+ link={
+ }
+ />
+ }
/>
@@ -51,7 +58,8 @@ export const CyclesListHeader: FC = observer(() => {
toggleCreateCycleModal(true);
}}
>
-
Add
Cycle
+ {t("add")}
+ {t("add_cycle")}
) : (
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx
index b72b46dbeaf..d6b4eea15bc 100644
--- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx
+++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx
@@ -38,7 +38,7 @@ const ProjectCyclesPage = observer(() => {
// derived values
const totalCycles = currentProjectCycleIds?.length ?? 0;
const project = projectId ? getProjectById(projectId?.toString()) : undefined;
- const pageTitle = project?.name ? `${project?.name} - Cycles` : undefined;
+ const pageTitle = project?.name ? `${project?.name} - ${t("cycles.label", { count: 2 })}` : undefined;
const hasAdminLevelPermission = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT);
const hasMemberLevelPermission = allowPermissions(
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
diff --git a/web/ce/components/cycles/active-cycle/root.tsx b/web/ce/components/cycles/active-cycle/root.tsx
index a13b7f64cfa..6b2ecb928be 100644
--- a/web/ce/components/cycles/active-cycle/root.tsx
+++ b/web/ce/components/cycles/active-cycle/root.tsx
@@ -97,7 +97,7 @@ export const ActiveCycleRoot: React.FC = observer((props) =
{({ open }) => (
<>
-
+
{ActiveCyclesComponent}
>
diff --git a/web/ce/components/cycles/analytics-sidebar/base.tsx b/web/ce/components/cycles/analytics-sidebar/base.tsx
index 551d0ad53ac..c259a4a81d2 100644
--- a/web/ce/components/cycles/analytics-sidebar/base.tsx
+++ b/web/ce/components/cycles/analytics-sidebar/base.tsx
@@ -2,6 +2,7 @@
import { FC, Fragment } from "react";
import { observer } from "mobx-react";
// plane ui
+import { useTranslation } from "@plane/i18n";
import { TCycleEstimateType } from "@plane/types";
import { Loader } from "@plane/ui";
// components
@@ -23,6 +24,7 @@ export const SidebarChart: FC = observer((props) => {
// hooks
const { getEstimateTypeByCycleId, getCycleById, fetchCycleDetails, fetchArchivedCycleDetails, setEstimateType } =
useCycle();
+ const { t } = useTranslation();
// derived data
const cycleDetails = validateCycleSnapshot(getCycleById(cycleId));
@@ -66,11 +68,11 @@ export const SidebarChart: FC = observer((props) => {
- Ideal
+ {t("ideal")}
- Current
+ {t("current")}
{cycleStartDate && cycleEndDate && completionChartDistributionData ? (
@@ -80,7 +82,7 @@ export const SidebarChart: FC = observer((props) => {
startDate={cycleStartDate}
endDate={cycleEndDate}
totalIssues={estimateType === "points" ? totalEstimatePoints : totalIssues}
- plotTitle={estimateType === "points" ? "points" : "work items"}
+ plotTitle={estimateType === "points" ? t("points") : t("work_items")}
/>
) : (
diff --git a/web/core/components/cycles/active-cycle/cycle-stats.tsx b/web/core/components/cycles/active-cycle/cycle-stats.tsx
index b9e96d559f3..79343d65c30 100644
--- a/web/core/components/cycles/active-cycle/cycle-stats.tsx
+++ b/web/core/components/cycles/active-cycle/cycle-stats.tsx
@@ -126,7 +126,7 @@ export const ActiveCycleStats: FC = observer((props) => {
)
}
>
- Priority work items
+ {t("project_cycles.active_cycle.priority_issue")}
@@ -139,7 +139,7 @@ export const ActiveCycleStats: FC = observer((props) => {
)
}
>
- Assignees
+ {t("project_cycles.active_cycle.assignees")}
@@ -152,7 +152,7 @@ export const ActiveCycleStats: FC = observer((props) => {
)
}
>
- Labels
+ {t("project_cycles.active_cycle.labels")}
@@ -289,7 +289,7 @@ export const ActiveCycleStats: FC = observer((props) => {
- No assignee
+ {t("no_assignee")}
}
completed={assignee.completed_issues}
diff --git a/web/core/components/cycles/active-cycle/productivity.tsx b/web/core/components/cycles/active-cycle/productivity.tsx
index 2f2ef55101e..613355c7eba 100644
--- a/web/core/components/cycles/active-cycle/productivity.tsx
+++ b/web/core/components/cycles/active-cycle/productivity.tsx
@@ -43,7 +43,9 @@ export const ActiveCycleProductivity: FC = observe
-
Issue burndown
+
+ {t("project_cycles.active_cycle.issue_burndown")}
+
@@ -56,11 +58,11 @@ export const ActiveCycleProductivity: FC
= observe
- Ideal
+ {t("project_cycles.active_cycle.ideal")}
- Current
+ {t("project_cycles.active_cycle.current")}
{estimateType === "points" ? (
diff --git a/web/core/components/cycles/active-cycle/progress.tsx b/web/core/components/cycles/active-cycle/progress.tsx
index 73babfc4843..c2f0174f624 100644
--- a/web/core/components/cycles/active-cycle/progress.tsx
+++ b/web/core/components/cycles/active-cycle/progress.tsx
@@ -47,7 +47,7 @@ export const ActiveCycleProgress: FC = observer((props
-
Progress
+
{t("project_cycles.active_cycle.progress")}
{cycle.total_issues > 0 && (
{`${cycle.completed_issues + cycle.cancelled_issues}/${cycle.total_issues - cycle.cancelled_issues} ${
diff --git a/web/core/components/cycles/analytics-sidebar/issue-progress.tsx b/web/core/components/cycles/analytics-sidebar/issue-progress.tsx
index f231693ce66..47f9df5e5d3 100644
--- a/web/core/components/cycles/analytics-sidebar/issue-progress.tsx
+++ b/web/core/components/cycles/analytics-sidebar/issue-progress.tsx
@@ -8,6 +8,7 @@ import { useSearchParams } from "next/navigation";
import { ChevronUp, ChevronDown } from "lucide-react";
import { Disclosure, Transition } from "@headlessui/react";
import { EIssueFilterType, EIssuesStoreType } from "@plane/constants";
+import { useTranslation } from "@plane/i18n";
import { ICycle, IIssueFilterOptions, TCyclePlotType, TProgressSnapshot } from "@plane/types";
// components
import { CycleProgressStats } from "@/components/cycles";
@@ -63,6 +64,7 @@ export const CycleAnalyticsProgress: FC = observer((pro
const {
issuesFilter: { issueFilters, updateFilters },
} = useIssues(EIssuesStoreType.CYCLE);
+ const { t } = useTranslation();
// derived values
const cycleDetails = validateCycleSnapshot(getCycleById(cycleId));
@@ -138,7 +140,9 @@ export const CycleAnalyticsProgress: FC = observer((pro
{isCycleDateValid ? (
- Progress
+
+ {t("project_cycles.active_cycle.progress")}
+
{open ? (
@@ -150,7 +154,9 @@ export const CycleAnalyticsProgress: FC = observer((pro
) : (
-
Progress
+
+ {t("project_cycles.active_cycle.progress")}
+
)}
diff --git a/web/core/components/cycles/analytics-sidebar/progress-stats.tsx b/web/core/components/cycles/analytics-sidebar/progress-stats.tsx
index 069ccb2fff5..8336430ccab 100644
--- a/web/core/components/cycles/analytics-sidebar/progress-stats.tsx
+++ b/web/core/components/cycles/analytics-sidebar/progress-stats.tsx
@@ -4,6 +4,7 @@ import { FC } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import { Tab } from "@headlessui/react";
+import { useTranslation } from "@plane/i18n";
import {
IIssueFilterOptions,
IIssueFilters,
@@ -73,6 +74,7 @@ type TStateStatComponent = {
export const AssigneeStatComponent = observer((props: TAssigneeStatComponent) => {
const { distribution, isEditable, filters, handleFiltersUpdate } = props;
+ const { t } = useTranslation();
return (
{distribution && distribution.length > 0 ? (
@@ -104,7 +106,7 @@ export const AssigneeStatComponent = observer((props: TAssigneeStatComponent) =>
-
No assignee
+
{t("no_assignee")}
}
completed={assignee?.completed ?? 0}
@@ -117,7 +119,7 @@ export const AssigneeStatComponent = observer((props: TAssigneeStatComponent) =>
- No assignees yet
+ {t("no_assignee")}
)}
@@ -126,6 +128,7 @@ export const AssigneeStatComponent = observer((props: TAssigneeStatComponent) =>
export const LabelStatComponent = observer((props: TLabelStatComponent) => {
const { distribution, isEditable, filters, handleFiltersUpdate } = props;
+ const { t } = useTranslation();
return (
{distribution && distribution.length > 0 ? (
@@ -142,7 +145,7 @@ export const LabelStatComponent = observer((props: TLabelStatComponent) => {
backgroundColor: label.color ?? "transparent",
}}
/>
- {label.title ?? "No labels"}
+ {label.title ?? t("no_labels_yet")}
}
completed={label.completed}
@@ -165,7 +168,7 @@ export const LabelStatComponent = observer((props: TLabelStatComponent) => {
backgroundColor: label.color ?? "transparent",
}}
/>
-
{label.title ?? "No labels"}
+
{label.title ?? t("no_labels_yet")}
}
completed={label.completed}
@@ -179,7 +182,7 @@ export const LabelStatComponent = observer((props: TLabelStatComponent) => {
- No labels yet
+ {t("no_labels_yet")}
)}
@@ -222,15 +225,15 @@ export const StateStatComponent = observer((props: TStateStatComponent) => {
const progressStats = [
{
key: "stat-states",
- title: "States",
+ i18n_title: "common.states",
},
{
key: "stat-assignees",
- title: "Assignees",
+ i18n_title: "common.assignees",
},
{
key: "stat-labels",
- title: "Labels",
+ i18n_title: "common.labels",
},
];
@@ -267,6 +270,7 @@ export const CycleProgressStats: FC = observer((props) => {
`cycle-analytics-tab-${cycleId}`,
"stat-assignees"
);
+ const { t } = useTranslation();
// derived values
const currentTabIndex = (tab: string): number => progressStats.findIndex((stat) => stat.key === tab);
@@ -337,7 +341,7 @@ export const CycleProgressStats: FC = observer((props) => {
key={stat.key}
onClick={() => setCycleTab(stat.key)}
>
- {stat.title}
+ {t(stat.i18n_title)}
))}
diff --git a/web/core/components/cycles/analytics-sidebar/sidebar-details.tsx b/web/core/components/cycles/analytics-sidebar/sidebar-details.tsx
index a4529882e2b..eaa460f7abe 100644
--- a/web/core/components/cycles/analytics-sidebar/sidebar-details.tsx
+++ b/web/core/components/cycles/analytics-sidebar/sidebar-details.tsx
@@ -4,6 +4,7 @@ import isEmpty from "lodash/isEmpty";
import { observer } from "mobx-react";
import { LayersIcon, SquareUser, Users } from "lucide-react";
// plane types
+import { useTranslation } from "@plane/i18n";
import { ICycle } from "@plane/types";
// plane ui
import { Avatar, AvatarGroup, TextArea } from "@plane/ui";
@@ -24,6 +25,7 @@ export const CycleSidebarDetails: FC = observer((props) => {
// hooks
const { getUserDetails } = useMember();
const { areEstimateEnabledByProjectId, currentActiveEstimateId, estimateById } = useProjectEstimates();
+ const { t } = useTranslation();
const areEstimateEnabled = projectId && areEstimateEnabledByProjectId(projectId.toString());
const cycleStatus = cycleDetails?.status?.toLocaleLowerCase();
@@ -32,10 +34,10 @@ export const CycleSidebarDetails: FC = observer((props) => {
const issueCount =
isCompleted && !isEmpty(cycleDetails?.progress_snapshot)
? cycleDetails?.progress_snapshot?.total_issues === 0
- ? "0 Work item"
+ ? `0 ${t("common.work_item")}`
: `${cycleDetails?.progress_snapshot?.completed_issues}/${cycleDetails?.progress_snapshot?.total_issues}`
: cycleDetails?.total_issues === 0
- ? "0 Work item"
+ ? `0 ${t("common.work_item")}`
: `${cycleDetails?.completed_issues}/${cycleDetails?.total_issues}`;
const estimateType = areEstimateEnabled && currentActiveEstimateId && estimateById(currentActiveEstimateId);
const cycleOwnerDetails = cycleDetails ? getUserDetails(cycleDetails.owned_by_id) : undefined;
@@ -51,10 +53,10 @@ export const CycleSidebarDetails: FC = observer((props) => {
const issueEstimatePointCount =
isCompleted && !isEmpty(cycleDetails?.progress_snapshot)
? cycleDetails?.progress_snapshot.total_issues === 0
- ? "0 Work item"
+ ? `0 ${t("common.work_item")}`
: `${cycleDetails?.progress_snapshot.completed_estimate_points}/${cycleDetails?.progress_snapshot.total_estimate_points}`
: cycleDetails?.total_issues === 0
- ? "0 Work item"
+ ? `0 ${t("common.work_item")}`
: `${cycleDetails?.completed_estimate_points}/${cycleDetails?.total_estimate_points}`;
return (
@@ -70,7 +72,7 @@ export const CycleSidebarDetails: FC
= observer((props) => {
- Lead
+ {t("lead")}
@@ -83,7 +85,7 @@ export const CycleSidebarDetails: FC
= observer((props) => {
- Members
+ {t("members")}
@@ -104,7 +106,7 @@ export const CycleSidebarDetails: FC
= observer((props) => {
>
) : (
- No assignees
+ {t("no_assignee")}
)}
@@ -113,7 +115,7 @@ export const CycleSidebarDetails: FC
= observer((props) => {
- Work items
+ {t("work_items")}
{issueCount}
@@ -127,7 +129,7 @@ export const CycleSidebarDetails: FC
= observer((props) => {
- Points
+ {t("points")}
{issueEstimatePointCount}
diff --git a/web/core/components/cycles/analytics-sidebar/sidebar-header.tsx b/web/core/components/cycles/analytics-sidebar/sidebar-header.tsx
index 2c4037d7db9..c8d4cc487b4 100644
--- a/web/core/components/cycles/analytics-sidebar/sidebar-header.tsx
+++ b/web/core/components/cycles/analytics-sidebar/sidebar-header.tsx
@@ -5,7 +5,7 @@ import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form";
import { ArchiveIcon, ArchiveRestoreIcon, ChevronRight, EllipsisIcon, LinkIcon, Trash2 } from "lucide-react";
// types
-import { CYCLE_STATUS, CYCLE_UPDATED ,EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
+import { CYCLE_STATUS, CYCLE_UPDATED, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { ICycle } from "@plane/types";
// ui
@@ -70,16 +70,16 @@ export const CycleSidebarHeader: FC
= observer((props) => {
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
- title: "Restore success",
- message: "Your cycle can be found in project cycles.",
+ title: t("project_cycles.action.restore.success.title"),
+ message: t("project_cycles.action.restore.success.description"),
});
router.push(`/${workspaceSlug.toString()}/projects/${projectId.toString()}/archives/cycles`);
})
.catch(() =>
setToast({
type: TOAST_TYPE.ERROR,
- title: "Error!",
- message: "Cycle could not be restored. Please try again.",
+ title: t("project_cycles.action.restore.failed.title"),
+ message: t("project_cycles.action.restore.failed.description"),
})
);
};
@@ -89,14 +89,14 @@ export const CycleSidebarHeader: FC = observer((props) => {
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
- title: "Link Copied!",
- message: "Cycle link copied to clipboard.",
+ title: t("common.link_copied"),
+ message: t("common.link_copied_to_clipboard"),
});
})
.catch(() => {
setToast({
type: TOAST_TYPE.ERROR,
- title: "Some error occurred",
+ title: t("common.errors.default.message"),
});
});
};
@@ -166,15 +166,14 @@ export const CycleSidebarHeader: FC = observer((props) => {
submitChanges(payload, "date_range");
setToast({
type: TOAST_TYPE.SUCCESS,
- title: "Success!",
- message: "Cycle updated successfully.",
+ title: t("project_cycles.action.update.success.title"),
+ message: t("project_cycles.action.update.success.description"),
});
} else {
setToast({
type: TOAST_TYPE.ERROR,
- title: "Error!",
- message:
- "You already have a cycle on the given dates, if you want to create a draft cycle, you can do that by removing both the dates.",
+ title: t("project_cycles.action.update.failed.title"),
+ message: t("project_cycles.action.update.error.already_exists"),
});
reset({ ...cycleDetails });
}
@@ -231,15 +230,15 @@ export const CycleSidebarHeader: FC = observer((props) => {
{isCompleted ? (
- Archive cycle
+ {t("common.archive")}
) : (
-
Archive cycle
+
{t("common.archive")}
- Only completed cycles can be archived.
+ {t("project_cycles.only_completed_cycles_can_be_archived")}
@@ -250,7 +249,7 @@ export const CycleSidebarHeader: FC = observer((props) => {
- Restore cycle
+ {t("project_cycles.action.restore.title")}
)}
@@ -263,7 +262,7 @@ export const CycleSidebarHeader: FC = observer((props) => {
>
- Delete cycle
+ {t("delete")}
)}
diff --git a/web/core/components/cycles/cycles-view-header.tsx b/web/core/components/cycles/cycles-view-header.tsx
index c3139bc8732..fe5b942dbf2 100644
--- a/web/core/components/cycles/cycles-view-header.tsx
+++ b/web/core/components/cycles/cycles-view-header.tsx
@@ -5,6 +5,7 @@ import { ListFilter, Search, X } from "lucide-react";
// plane helpers
import { useOutsideClickDetector } from "@plane/hooks";
// types
+import { useTranslation } from "@plane/i18n";
import { TCycleFilters } from "@plane/types";
// components
import { CycleFiltersSelection } from "@/components/cycles";
@@ -25,6 +26,7 @@ export const CyclesViewHeader: React.FC = observer((props) => {
const inputRef = useRef(null);
// hooks
const { currentProjectFilters, searchQuery, updateFilters, updateSearchQuery } = useCycleFilter();
+ const { t } = useTranslation();
// states
const [isSearchOpen, setIsSearchOpen] = useState(searchQuery !== "" ? true : false);
// outside click detector hook
@@ -114,7 +116,7 @@ export const CyclesViewHeader: React.FC = observer((props) => {
}
- title="Filters"
+ title={t("common.filters")}
placement="bottom-end"
isFiltersApplied={isFiltersApplied}
>
diff --git a/web/core/components/cycles/cycles-view.tsx b/web/core/components/cycles/cycles-view.tsx
index dea4554129f..ef6c9d136a2 100644
--- a/web/core/components/cycles/cycles-view.tsx
+++ b/web/core/components/cycles/cycles-view.tsx
@@ -2,6 +2,7 @@ import { FC } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
// components
+import { useTranslation } from "@plane/i18n";
import { CyclesList } from "@/components/cycles";
// ui
import { CycleModuleListLayout } from "@/components/ui";
@@ -21,6 +22,7 @@ export const CyclesView: FC
= observer((props) => {
// store hooks
const { getFilteredCycleIds, getFilteredCompletedCycleIds, loader, currentProjectActiveCycleId } = useCycle();
const { searchQuery } = useCycleFilter();
+ const { t } = useTranslation();
// derived values
const filteredCycleIds = getFilteredCycleIds(projectId, false);
const filteredCompletedCycleIds = getFilteredCompletedCycleIds(projectId);
@@ -39,11 +41,11 @@ export const CyclesView: FC = observer((props) => {
className="mx-auto h-36 w-36 sm:h-48 sm:w-48"
alt="No matching cycles"
/>
- No matching cycles
+ {t("project_cycles.no_matching_cycles")}
{searchQuery.trim() === ""
- ? "Remove the filters to see all cycles"
- : "Remove the search criteria to see all cycles"}
+ ? t("project_cycles.remove_filters_to_see_all_cycles")
+ : t("project_cycles.remove_search_criteria_to_see_all_cycles")}
diff --git a/web/core/components/cycles/form.tsx b/web/core/components/cycles/form.tsx
index e11cbef1355..c5c9c918697 100644
--- a/web/core/components/cycles/form.tsx
+++ b/web/core/components/cycles/form.tsx
@@ -5,6 +5,7 @@ import { Controller, useForm } from "react-hook-form";
// plane imports
import { ETabIndices } from "@plane/constants";
// types
+import { useTranslation } from "@plane/i18n";
import { ICycle } from "@plane/types";
// ui
import { Button, Input, TextArea } from "@plane/ui";
@@ -35,6 +36,7 @@ const defaultValues: Partial
= {
export const CycleForm: React.FC = (props) => {
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data, isMobile = false } = props;
+ const { t } = useTranslation();
// form data
const {
formState: { errors, isSubmitting, dirtyFields },
@@ -85,7 +87,9 @@ export const CycleForm: React.FC = (props) => {
)}
/>
)}
- {status ? "Update" : "Create"} cycle
+
+ {status ? t("project_cycles.update_cycle") : t("project_cycles.create_cycle")}
+
@@ -93,17 +97,17 @@ export const CycleForm: React.FC
= (props) => {
name="name"
control={control}
rules={{
- required: "Title is required",
+ required: t("title_is_required"),
maxLength: {
value: 255,
- message: "Title should be less than 255 characters",
+ message: t("title_should_be_less_than_255_characters"),
},
}}
render={({ field: { value, onChange } }) => (
= (props) => {
render={({ field: { value, onChange } }) => (
- Cancel
+ {t("common.cancel")}
- {data ? (isSubmitting ? "Updating" : "Update Cycle") : isSubmitting ? "Creating" : "Create Cycle"}
+ {data
+ ? isSubmitting
+ ? t("common.updating")
+ : t("project_cycles.update_cycle")
+ : isSubmitting
+ ? t("common.creating")
+ : t("project_cycles.create_cycle")}
diff --git a/web/core/components/cycles/list/cycle-list-item-action.tsx b/web/core/components/cycles/list/cycle-list-item-action.tsx
index dfcd6b4b309..a5b98ca5d56 100644
--- a/web/core/components/cycles/list/cycle-list-item-action.tsx
+++ b/web/core/components/cycles/list/cycle-list-item-action.tsx
@@ -7,6 +7,7 @@ import { Controller, useForm } from "react-hook-form";
import { Eye, Users } from "lucide-react";
// types
import { CYCLE_FAVORITED, CYCLE_UNFAVORITED, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
+import { useTranslation } from "@plane/i18n";
import { ICycle, TCycleGroups } from "@plane/types";
// ui
import {
@@ -63,6 +64,7 @@ export const CycleListItemAction: FC
= observer((props) => {
const [transferIssuesModal, setTransferIssuesModal] = useState(false);
// hooks
const { isMobile } = usePlatformOS();
+ const { t } = useTranslation();
// router
const router = useAppRouter();
const searchParams = useSearchParams();
@@ -110,14 +112,14 @@ export const CycleListItemAction: FC = observer((props) => {
);
setPromiseToast(addToFavoritePromise, {
- loading: "Adding cycle to favorites...",
+ loading: t("project_cycles.action.favorite.loading"),
success: {
- title: "Success!",
- message: () => "Cycle added to favorites.",
+ title: t("project_cycles.action.favorite.success.title"),
+ message: () => t("project_cycles.action.favorite.success.description"),
},
error: {
- title: "Error!",
- message: () => "Couldn't add the cycle to favorites. Please try again.",
+ title: t("project_cycles.action.favorite.failed.title"),
+ message: () => t("project_cycles.action.favorite.failed.description"),
},
});
};
@@ -139,14 +141,14 @@ export const CycleListItemAction: FC = observer((props) => {
});
setPromiseToast(removeFromFavoritePromise, {
- loading: "Removing cycle from favorites...",
+ loading: t("project_cycles.action.unfavorite.loading"),
success: {
- title: "Success!",
- message: () => "Cycle removed from favorites.",
+ title: t("project_cycles.action.unfavorite.success.title"),
+ message: () => t("project_cycles.action.unfavorite.success.description"),
},
error: {
- title: "Error!",
- message: () => "Couldn't remove the cycle from favorites. Please try again.",
+ title: t("project_cycles.action.unfavorite.failed.title"),
+ message: () => t("project_cycles.action.unfavorite.failed.description"),
},
});
};
@@ -186,15 +188,14 @@ export const CycleListItemAction: FC = observer((props) => {
submitChanges(payload);
setToast({
type: TOAST_TYPE.SUCCESS,
- title: "Success!",
- message: "Cycle updated successfully.",
+ title: t("project_cycles.action.update.success.title"),
+ message: t("project_cycles.action.update.success.description"),
});
} else {
setToast({
type: TOAST_TYPE.ERROR,
- title: "Error!",
- message:
- "You already have a cycle on the given dates, if you want to create a draft cycle, you can do that by removing both the dates.",
+ title: t("project_cycles.action.update.failed.title"),
+ message: t("project_cycles.action.update.error.already_exists"),
});
reset({ ...cycleDetails });
}
@@ -238,7 +239,7 @@ export const CycleListItemAction: FC = observer((props) => {
className={`z-[1] flex text-custom-primary-200 text-xs gap-1 flex-shrink-0 ${isMobile || (isActive && !searchParams.has("peekCycle")) ? "flex" : "hidden group-hover:flex"}`}
>
- More details
+ {t("project_cycles.more_details")}
{showIssueCount && (
diff --git a/web/core/components/cycles/list/root.tsx b/web/core/components/cycles/list/root.tsx
index 1531b88aa80..49e3fcbe3e4 100644
--- a/web/core/components/cycles/list/root.tsx
+++ b/web/core/components/cycles/list/root.tsx
@@ -2,6 +2,7 @@ import React, { FC } from "react";
import { observer } from "mobx-react";
import { Disclosure } from "@headlessui/react";
// components
+import { useTranslation } from "@plane/i18n";
import { ContentWrapper, ERowVariant } from "@plane/ui";
import { ListLayout } from "@/components/core/list";
import { CycleListGroupHeader, CyclePeekOverview, CyclesListMap } from "@/components/cycles";
@@ -18,6 +19,7 @@ export interface ICyclesList {
export const CyclesList: FC = observer((props) => {
const { completedCycleIds, upcomingCycleIds, cycleIds, workspaceSlug, projectId, isArchived = false } = props;
+ const { t } = useTranslation();
return (
@@ -36,7 +38,7 @@ export const CyclesList: FC = observer((props) => {
<>
= observer((props) => {
<>
= observer((props) => {
const { setTrackElement } = useEventTracker();
const { allowPermissions } = useUserPermissions();
const { getCycleById, restoreCycle } = useCycle();
+ const { t } = useTranslation();
// derived values
const cycleDetails = getCycleById(cycleId);
const isArchived = !!cycleDetails?.archived_at;
@@ -57,8 +59,8 @@ export const CycleQuickActions: React.FC = observer((props) => {
copyUrlToClipboard(cycleLink).then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
- title: "Link Copied!",
- message: "Cycle link copied to clipboard.",
+ title: t("common.link_copied"),
+ message: t("common.link_copied_to_clipboard"),
});
});
const handleOpenInNewTab = () => window.open(`/${cycleLink}`, "_blank");
@@ -75,16 +77,16 @@ export const CycleQuickActions: React.FC = observer((props) => {
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
- title: "Restore success",
- message: "Your cycle can be found in project cycles.",
+ title: t("project_cycles.action.restore.success.title"),
+ message: t("project_cycles.action.restore.success.description"),
});
router.push(`/${workspaceSlug}/projects/${projectId}/archives/cycles`);
})
.catch(() =>
setToast({
type: TOAST_TYPE.ERROR,
- title: "Error!",
- message: "Cycle could not be restored. Please try again.",
+ title: t("project_cycles.action.restore.failed.title"),
+ message: t("project_cycles.action.restore.failed.description"),
})
);
@@ -96,7 +98,7 @@ export const CycleQuickActions: React.FC = observer((props) => {
const MENU_ITEMS: TContextMenuItem[] = [
{
key: "edit",
- title: "Edit",
+ title: t("edit"),
icon: Pencil,
action: handleEditCycle,
shouldRender: isEditingAllowed && !isCompleted && !isArchived,
@@ -104,22 +106,22 @@ export const CycleQuickActions: React.FC = observer((props) => {
{
key: "open-new-tab",
action: handleOpenInNewTab,
- title: "Open in new tab",
+ title: t("open_in_new_tab"),
icon: ExternalLink,
shouldRender: !isArchived,
},
{
key: "copy-link",
action: handleCopyText,
- title: "Copy link",
+ title: t("copy_link"),
icon: LinkIcon,
shouldRender: !isArchived,
},
{
key: "archive",
action: handleArchiveCycle,
- title: "Archive",
- description: isCompleted ? undefined : "Only completed cycles can\nbe archived.",
+ title: t("archive"),
+ description: isCompleted ? undefined : t("project_cycles.only_completed_cycles_can_be_archived"),
icon: ArchiveIcon,
className: "items-start",
iconClassName: "mt-1",
@@ -129,14 +131,14 @@ export const CycleQuickActions: React.FC = observer((props) => {
{
key: "restore",
action: handleRestoreCycle,
- title: "Restore",
+ title: t("restor"),
icon: ArchiveRestoreIcon,
shouldRender: isEditingAllowed && isArchived,
},
{
key: "delete",
action: handleDeleteCycle,
- title: "Delete",
+ title: t("delete"),
icon: Trash2,
shouldRender: isEditingAllowed && !isCompleted && !isArchived,
},
diff --git a/web/core/components/issues/issue-detail/subscription.tsx b/web/core/components/issues/issue-detail/subscription.tsx
index a895c34c900..e19e787f398 100644
--- a/web/core/components/issues/issue-detail/subscription.tsx
+++ b/web/core/components/issues/issue-detail/subscription.tsx
@@ -5,9 +5,9 @@ import isNil from "lodash/isNil";
import { observer } from "mobx-react";
import { Bell, BellOff } from "lucide-react";
// plane-i18n
+import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// UI
-import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui";
// hooks
import { useIssueDetail, useUserPermissions } from "@/hooks/store";
diff --git a/web/core/components/project/search-projects.tsx b/web/core/components/project/search-projects.tsx
index 8c0dcbc9385..67e4a3ebb31 100644
--- a/web/core/components/project/search-projects.tsx
+++ b/web/core/components/project/search-projects.tsx
@@ -59,7 +59,7 @@ export const ProjectSearch: FC = observer(() => {
updateSearchQuery(e.target.value)}
onKeyDown={handleInputKeyDown}