diff --git a/packages/constants/src/analytics.ts b/packages/constants/src/analytics.ts
index 710ac412b74..69ac2dbe8df 100644
--- a/packages/constants/src/analytics.ts
+++ b/packages/constants/src/analytics.ts
@@ -2,8 +2,11 @@
import { TXAxisValues, TYAxisValues } from "@plane/types";
export const ANALYTICS_TABS = [
- { key: "scope_and_demand", title: "Scope and Demand" },
- { key: "custom", title: "Custom Analytics" },
+ {
+ key: "scope_and_demand",
+ i18n_title: "workspace_analytics.tabs.scope_and_demand",
+ },
+ { key: "custom", i18n_title: "workspace_analytics.tabs.custom" },
];
export const ANALYTICS_X_AXIS_VALUES: { value: TXAxisValues; label: string }[] =
diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json
index 1f813f3e563..cb4bf290050 100644
--- a/packages/i18n/src/locales/en/translations.json
+++ b/packages/i18n/src/locales/en/translations.json
@@ -328,9 +328,6 @@
"deleting": "Deleting",
"make_a_copy": "Make a copy",
"move_to_project": "Move to project",
- "deleting": "Deleting",
- "make_a_copy": "Make a copy",
- "move_to_project": "Move to project",
"good": "Good",
"morning": "morning",
"afternoon": "afternoon",
@@ -338,6 +335,8 @@
"show_all": "Show all",
"show_less": "Show less",
"no_data_yet": "No Data yet",
+ "unassigned": "Unassigned",
+ "work_items": "Work items",
"add_link": "Add link",
"points": "Points",
"no_assignee": "No assignee",
@@ -677,6 +676,35 @@
},
"workspace_analytics": {
+ "label": "Analytics",
+ "page_label": "{workspace} - Analytics",
+ "open_tasks": "Total open tasks",
+ "error": "There was some error in fetching the data.",
+ "work_items_closed_in": "Work items closed in",
+ "selected_projects": "Selected projects",
+ "total_members": "Total members",
+ "total_cycles": "Total cycles",
+ "total_modules": "Total modules",
+ "pending_work_items":{
+ "title": "Pending work items",
+ "empty_state": "Analysis of pending work items by co-workers appears here."
+ },
+ "work_items_closed_in_a_year": {
+ "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":{
+ "title": "Most work items created",
+ "empty_state": "Co-workers and the number of work items created by them appears here."
+ },
+ "most_work_items_closed":{
+ "title": "Most work items closed",
+ "empty_state": "Co-workers and the number of work items closed by them appears here."
+ },
+ "tabs": {
+ "scope_and_demand": "Scope and Demand",
+ "custom": "Custom Analytics"
+ },
"empty_state": {
"general": {
"title": "Track progress, workloads, and allocations. Spot trends, remove blockers, and move work faster",
@@ -1382,7 +1410,8 @@
"exporter": {
"csv": {
"title": "CSV",
- "description": "Export issues to a CSV file."
+ "description": "Export issues to a CSV file.",
+ "short_description": "Export as csv"
},
"xlsx": {
"title": "Excel",
diff --git a/web/app/[workspaceSlug]/(projects)/analytics/header.tsx b/web/app/[workspaceSlug]/(projects)/analytics/header.tsx
index fe55f8cbdc9..2c3247bd7f6 100644
--- a/web/app/[workspaceSlug]/(projects)/analytics/header.tsx
+++ b/web/app/[workspaceSlug]/(projects)/analytics/header.tsx
@@ -41,7 +41,12 @@ export const WorkspaceAnalyticsHeader = observer(() => {
} />}
+ link={
+ }
+ />
+ }
/>
{analytics_tab === "custom" ? (
diff --git a/web/app/[workspaceSlug]/(projects)/analytics/page.tsx b/web/app/[workspaceSlug]/(projects)/analytics/page.tsx
index f6094276328..bd89f62372a 100644
--- a/web/app/[workspaceSlug]/(projects)/analytics/page.tsx
+++ b/web/app/[workspaceSlug]/(projects)/analytics/page.tsx
@@ -32,7 +32,9 @@ const AnalyticsPage = observer(() => {
// helper hooks
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/analytics" });
// derived values
- const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Analytics` : undefined;
+ const pageTitle = currentWorkspace?.name
+ ? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name })
+ : undefined;
// permissions
const canPerformEmptyStateActions = allowPermissions(
@@ -59,7 +61,7 @@ const AnalyticsPage = observer(() => {
selected ? "text-custom-primary-100 " : "hover:text-custom-text-200"
}`}
>
- {tab.title}
+ {t(tab.i18n_title)}
diff --git a/web/core/components/analytics/custom-analytics/sidebar/projects-list.tsx b/web/core/components/analytics/custom-analytics/sidebar/projects-list.tsx
index d7080746763..86ffbe19c9c 100644
--- a/web/core/components/analytics/custom-analytics/sidebar/projects-list.tsx
+++ b/web/core/components/analytics/custom-analytics/sidebar/projects-list.tsx
@@ -2,6 +2,7 @@ import { observer } from "mobx-react";
// icons
import { Contrast, LayoutGrid, Users } from "lucide-react";
// components
+import { useTranslation } from "@plane/i18n";
import { Logo } from "@/components/common";
// helpers
import { truncateText } from "@/helpers/string.helper";
@@ -16,10 +17,11 @@ export const CustomAnalyticsSidebarProjectsList: React.FC = observer((pro
const { projectIds } = props;
const { getProjectById } = useProject();
+ const { t } = useTranslation();
return (
-
Selected Projects
+
{t("workspace_analytics.selected_projects")}
{projectIds.map((projectId) => {
const project = getProjectById(projectId);
@@ -41,21 +43,21 @@ export const CustomAnalyticsSidebarProjectsList: React.FC
= observer((pro
-
Total members
+ {t("workspace_analytics.total_members")}
{project.total_members}
-
Total cycles
+ {t("workspace_analytics.total_cycles")}
{project.total_cycles}
-
Total modules
+ {t("workspace_analytics.total_modules")}
{project.total_modules}
diff --git a/web/core/components/analytics/custom-analytics/sidebar/sidebar.tsx b/web/core/components/analytics/custom-analytics/sidebar/sidebar.tsx
index 639a830cd5e..e19b3658683 100644
--- a/web/core/components/analytics/custom-analytics/sidebar/sidebar.tsx
+++ b/web/core/components/analytics/custom-analytics/sidebar/sidebar.tsx
@@ -7,6 +7,7 @@ import { mutate } from "swr";
// icons
import { CalendarDays, Download, RefreshCw } from "lucide-react";
// types
+import { useTranslation } from "@plane/i18n";
import { IAnalyticsParams, IAnalyticsResponse, IExportAnalyticsFormData, IWorkspace } from "@plane/types";
// ui
import { Button, LayersIcon, TOAST_TYPE, setToast } from "@plane/ui";
@@ -38,11 +39,12 @@ export const CustomAnalyticsSidebar: React.FC = observer((props) => {
const { data: currentUser } = useUser();
const { workspaceProjectIds, getProjectById } = useProject();
const { getWorkspaceById } = useWorkspace();
+ const { t } = useTranslation();
const { fetchCycleDetails, getCycleById } = useCycle();
const { fetchModuleDetails, getModuleById } = useModule();
- const projectDetails = projectId ? getProjectById(projectId.toString()) ?? undefined : undefined;
+ const projectDetails = projectId ? (getProjectById(projectId.toString()) ?? undefined) : undefined;
const trackExportAnalytics = () => {
if (!currentUser) return;
@@ -152,7 +154,7 @@ export const CustomAnalyticsSidebar: React.FC = observer((props) => {
{analytics ? analytics.total : "..."}
-
Issues
+
{t("work_items")}
{isProjectLevel && (
@@ -187,10 +189,10 @@ export const CustomAnalyticsSidebar: React.FC
= observer((props) => {
mutate(ANALYTICS(workspaceSlug.toString(), params));
}}
>
- Refresh
+ {t("refresh")}
} onClick={exportAnalytics}>
- Export as CSV
+ {t("exporter.csv.short_description")}
diff --git a/web/core/components/analytics/project-modal/main-content.tsx b/web/core/components/analytics/project-modal/main-content.tsx
index b572efdcb0c..cd3813f80cf 100644
--- a/web/core/components/analytics/project-modal/main-content.tsx
+++ b/web/core/components/analytics/project-modal/main-content.tsx
@@ -3,6 +3,7 @@ import { observer } from "mobx-react";
import { Tab } from "@headlessui/react";
// plane package imports
import { ANALYTICS_TABS } from "@plane/constants";
+import { useTranslation } from "@plane/i18n";
import { ICycle, IModule, IProject } from "@plane/types";
// components
import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics";
@@ -16,7 +17,7 @@ type Props = {
export const ProjectAnalyticsModalMainContent: React.FC
= observer((props) => {
const { fullScreen, cycleDetails, moduleDetails } = props;
-
+ const { t } = useTranslation();
return (
@@ -28,7 +29,7 @@ export const ProjectAnalyticsModalMainContent: React.FC = observer((props
selected ? "text-custom-primary-100 " : "hover:text-custom-text-200"
}`}
>
- {tab.title}
+ {t(tab.i18n_title)}
diff --git a/web/core/components/analytics/scope-and-demand/demand.tsx b/web/core/components/analytics/scope-and-demand/demand.tsx
index 7b1077af0cf..fefdc8fc6ee 100644
--- a/web/core/components/analytics/scope-and-demand/demand.tsx
+++ b/web/core/components/analytics/scope-and-demand/demand.tsx
@@ -1,5 +1,6 @@
// plane imports
import { STATE_GROUPS } from "@plane/constants";
+import { useTranslation } from "@plane/i18n";
// types
import { IDefaultAnalyticsResponse, TStateGroups } from "@plane/types";
// constants
@@ -9,45 +10,49 @@ type Props = {
defaultAnalytics: IDefaultAnalyticsResponse;
};
-export const AnalyticsDemand: React.FC = ({ defaultAnalytics }) => (
-
-
-
Total open tasks
- {defaultAnalytics.open_issues}
-
-
- {defaultAnalytics?.open_issues_classified.map((group) => {
- const percentage = ((group.state_count / defaultAnalytics.total_issues) * 100).toFixed(0);
+export const AnalyticsDemand: React.FC
= ({ defaultAnalytics }) => {
+ const { t } = useTranslation();
- return (
-
-
-
-
+
+
{t("workspace_analytics.open_tasks")}
+ {defaultAnalytics.open_issues}
+
+
+ {defaultAnalytics?.open_issues_classified.map((group) => {
+ const percentage = ((group.state_count / defaultAnalytics.total_issues) * 100).toFixed(0);
+
+ return (
+
+
+
+
+
{group.state_group}
+
+ {group.state_count}
+
+
+
{percentage}%
+
+
+
-
{group.state_group}
-
- {group.state_count}
-
-
{percentage}%
-
-
- );
- })}
-
-
-);
+ );
+ })}
+
+
+ );
+};
diff --git a/web/core/components/analytics/scope-and-demand/scope-and-demand.tsx b/web/core/components/analytics/scope-and-demand/scope-and-demand.tsx
index 6aac07c6a29..ae51727aa01 100644
--- a/web/core/components/analytics/scope-and-demand/scope-and-demand.tsx
+++ b/web/core/components/analytics/scope-and-demand/scope-and-demand.tsx
@@ -2,6 +2,7 @@
import { useParams } from "next/navigation";
import useSWR from "swr";
// ui
+import { useTranslation } from "@plane/i18n";
import { Button, ContentWrapper, Loader } from "@plane/ui";
// components
import { AnalyticsDemand, AnalyticsLeaderBoard, AnalyticsScope, AnalyticsYearWiseIssues } from "@/components/analytics";
@@ -21,6 +22,7 @@ export const ScopeAndDemand: React.FC
= (props) => {
const { fullScreen = true } = props;
const { workspaceSlug, projectId, cycleId, moduleId } = useParams();
+ const { t } = useTranslation();
const isProjectLevel = projectId ? true : false;
@@ -66,8 +68,8 @@ export const ScopeAndDemand: React.FC = (props) => {
count: user?.count,
id: user?.created_by__id,
}))}
- title="Most issues created"
- emptyStateMessage="Co-workers and the number of issues created by them appears here."
+ title={t("workspace_analytics.most_work_items_created.title")}
+ emptyStateMessage={t("workspace_analytics.most_work_items_created.empty_state")}
workspaceSlug={workspaceSlug?.toString() ?? ""}
/>
= (props) => {
count: user?.count,
id: user?.assignees__id,
}))}
- title="Most issues closed"
- emptyStateMessage="Co-workers and the number of issues closed by them appears here."
+ title={t("workspace_analytics.most_work_items_closed.title")}
+ emptyStateMessage={t("workspace_analytics.most_work_items_closed.empty_state")}
workspaceSlug={workspaceSlug?.toString() ?? ""}
/>
@@ -99,10 +101,10 @@ export const ScopeAndDemand: React.FC
= (props) => {
) : (
-
There was some error in fetching the data.
+
{t("workspace_analytics.error")}
diff --git a/web/core/components/analytics/scope-and-demand/scope.tsx b/web/core/components/analytics/scope-and-demand/scope.tsx
index 6711150975f..13cd60d5661 100644
--- a/web/core/components/analytics/scope-and-demand/scope.tsx
+++ b/web/core/components/analytics/scope-and-demand/scope.tsx
@@ -1,4 +1,5 @@
// plane types
+import { useTranslation } from "@plane/i18n";
import { IDefaultAnalyticsUser } from "@plane/types";
// plane ui
import { Card } from "@plane/ui";
@@ -14,82 +15,85 @@ type Props = {
pendingAssignedIssues: IDefaultAnalyticsUser[];
};
-export const AnalyticsScope: React.FC
= ({ pendingUnAssignedIssuesUser, pendingAssignedIssues }) => (
-
-
-
-
-
Pending issues
- {pendingUnAssignedIssuesUser && (
-
- Unassigned: {pendingUnAssignedIssuesUser.count}
-
- )}
-
+export const AnalyticsScope: React.FC
= ({ pendingUnAssignedIssuesUser, pendingAssignedIssues }) => {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+
{t("workspace_analytics.pending_work_items.title")}
+ {pendingUnAssignedIssuesUser && (
+
+ {t("unassigned")}: {pendingUnAssignedIssuesUser.count}
+
+ )}
+
- {pendingAssignedIssues && pendingAssignedIssues.length > 0 ? (
-
`#f97316`}
- customYAxisTickValues={pendingAssignedIssues.map((d) => (d.count > 0 ? d.count : 50))}
- tooltip={(datum) => {
- const assignee = pendingAssignedIssues.find((a) => a.assignees__id === `${datum.indexValue}`);
+ {pendingAssignedIssues && pendingAssignedIssues.length > 0 ? (
+ `#f97316`}
+ customYAxisTickValues={pendingAssignedIssues.map((d) => (d.count > 0 ? d.count : 50))}
+ tooltip={(datum) => {
+ const assignee = pendingAssignedIssues.find((a) => a.assignees__id === `${datum.indexValue}`);
- return (
-
-
- {assignee ? assignee.assignees__display_name : "No assignee"}:{" "}
-
- {datum.value}
-
- );
- }}
- axisBottom={{
- renderTick: (datum) => {
- const assignee = pendingAssignedIssues[datum.tickIndex] ?? "";
+ return (
+
+
+ {assignee ? assignee.assignees__display_name : "No assignee"}:{" "}
+
+ {datum.value}
+
+ );
+ }}
+ axisBottom={{
+ renderTick: (datum) => {
+ const assignee = pendingAssignedIssues[datum.tickIndex] ?? "";
- if (assignee && assignee?.assignees__avatar_url && assignee?.assignees__avatar_url !== "")
- return (
-
-
-
- );
- else
- return (
-
-
-
- {datum.value ? `${assignee.assignees__display_name}`.toUpperCase()[0] : "?"}
-
-
- );
- },
- }}
- margin={{ top: 20 }}
- theme={{
- axis: {},
- }}
- />
- ) : (
-
-
+
+
+ );
+ else
+ return (
+
+
+
+ {datum.value ? `${assignee.assignees__display_name}`.toUpperCase()[0] : "?"}
+
+
+ );
+ },
+ }}
+ margin={{ top: 20 }}
+ theme={{
+ axis: {},
+ }}
/>
-
- )}
+ ) : (
+
+ )}
+
-
-
-);
+
+ );
+};
diff --git a/web/core/components/analytics/scope-and-demand/year-wise-issues.tsx b/web/core/components/analytics/scope-and-demand/year-wise-issues.tsx
index b2c5805dd7a..0f469db7022 100644
--- a/web/core/components/analytics/scope-and-demand/year-wise-issues.tsx
+++ b/web/core/components/analytics/scope-and-demand/year-wise-issues.tsx
@@ -1,4 +1,5 @@
// ui
+import { useTranslation } from "@plane/i18n";
import { IDefaultAnalyticsResponse } from "@plane/types";
import { Card } from "@plane/ui";
import { LineGraph, ProfileEmptyState } from "@/components/ui";
@@ -12,49 +13,52 @@ type Props = {
defaultAnalytics: IDefaultAnalyticsResponse;
};
-export const AnalyticsYearWiseIssues: React.FC
= ({ defaultAnalytics }) => (
-
- Issues closed in a year
- {defaultAnalytics.issue_completed_month_wise.length > 0 ? (
- ({
- x: month.shortTitle,
- y:
- defaultAnalytics.issue_completed_month_wise.find((data) => data.month === parseInt(index, 10))?.count ||
- 0,
- })),
- },
- ]}
- customYAxisTickValues={defaultAnalytics.issue_completed_month_wise.map((data) => data.count)}
- height="300px"
- colors={(datum) => datum.color}
- curve="monotoneX"
- margin={{ top: 20 }}
- enableSlices="x"
- sliceTooltip={(datum) => (
-
- {datum.slice.points[0].data.yFormatted}
- issues closed in
- {datum.slice.points[0].data.xFormatted}
-
- )}
- theme={{
- background: "rgb(var(--color-background-100))",
- }}
- enableArea
- />
- ) : (
-
-
= ({ defaultAnalytics }) => {
+ const { t } = useTranslation();
+ return (
+
+ {t("workspace_analytics.work_items_closed_in_a_year.title")}
+ {defaultAnalytics.issue_completed_month_wise.length > 0 ? (
+ ({
+ x: t(month.shortTitle),
+ y:
+ defaultAnalytics.issue_completed_month_wise.find((data) => data.month === parseInt(index, 10))
+ ?.count || 0,
+ })),
+ },
+ ]}
+ customYAxisTickValues={defaultAnalytics.issue_completed_month_wise.map((data) => data.count)}
+ height="300px"
+ colors={(datum) => datum.color}
+ curve="monotoneX"
+ margin={{ top: 20 }}
+ enableSlices="x"
+ sliceTooltip={(datum) => (
+
+ {datum.slice.points[0].data.yFormatted}
+ {t("workspace_analytics.work_items_closed_in")}
+ {datum.slice.points[0].data.xFormatted}
+
+ )}
+ theme={{
+ background: "rgb(var(--color-background-100))",
+ }}
+ enableArea
/>
-
- )}
-
-);
+ ) : (
+
+ )}
+
+ );
+};