From 40f089d017183ab8908e73244cafe261be8a8c67 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Tue, 28 Jan 2025 20:25:38 +0530 Subject: [PATCH 1/2] chore: module language support added --- .../i18n/src/locales/en/translations.json | 42 ++++++++++++++++++- .../[projectId]/modules/(list)/header.tsx | 15 +++++-- .../analytics-sidebar/issue-progress.tsx | 23 ++++++---- .../analytics-sidebar/progress-stats.tsx | 6 ++- .../modules/analytics-sidebar/root.tsx | 42 +++++++++++-------- web/core/components/modules/form.tsx | 33 ++++++++++----- .../modules/module-list-item-action.tsx | 7 +++- web/core/components/modules/quick-actions.tsx | 18 ++++---- 8 files changed, 133 insertions(+), 53 deletions(-) diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index bf1da0356b9..283fd722ec9 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -199,6 +199,7 @@ "project_id_is_required": "Project ID is required", "select_network": "Select network", "lead": "Lead", + "date_range": "Date range", "private": "Private", "public": "Public", "accessible_only_by_invite": "Accessible only by invite", @@ -307,6 +308,8 @@ "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", @@ -318,6 +321,8 @@ "optional": "Optional", "Cancel": "Cancel", "edit": "Edit", + "archive": "Archive", + "restor": "Restore", "open_in_new_tab": "Open in new tab", "delete": "Delete", "deleting": "Deleting", @@ -333,6 +338,13 @@ "show_all": "Show all", "show_less": "Show less", "no_data_yet": "No Data yet", + "add_link": "Add link", + "points": "Points", + "no_assignee": "No assignee", + "no_assignees_yet": "No assignees yet", + "No labels yet": "No labels yet", + "ideal": "Ideal", + "current": "Current", "project_view": { "sort_by": { @@ -516,7 +528,11 @@ }, "comments": "Comments", "updates": "Updates", - "clear_all": "Clear all" + "clear_all": "Clear all", + "no_links_added_yet": "No links added yet", + "add_link": "Add link", + "links": "Links", + "progress": "Progress" }, "form": { @@ -968,6 +984,12 @@ }, "project_module": { + "add_module": "Add Module", + "update_module": "Update Module", + "create_module": "Create Module", + "archive_module": "Archive Module", + "restore_module": "Restore Module", + "delete_module": "Delete module", "empty_state": { "general": { "title": "Map your project milestones to Modules and track aggregated work easily.", @@ -993,6 +1015,24 @@ "archived": { "title": "No archived Modules yet", "description": "To tidy up your project, archive completed or cancelled modules. Find them here once archived." + }, + "sidebar": { + "in_active": "This module isn't active yet.", + "invalid_date": "Invalid date. Please enter valid date." + } + }, + "quick_actions": { + "archive_module": "Archive module", + "archive_module_description": "Only completed or canceled\nmodule can be archived.", + "delete_module": "Delete module" + }, + "toast": { + "copy": { + "success": "Module link copied to clipboard" + }, + "delete": { + "success": "Module deleted successfully", + "error": "Failed to delete module" } } }, diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx index 4d320b24c81..e0b330dde16 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx @@ -1,6 +1,8 @@ "use client"; import { observer } from "mobx-react"; +// plane imports +import { useTranslation } from "@plane/i18n"; // ui import { Breadcrumbs, Button, DiceIcon, Header } from "@plane/ui"; // components @@ -22,7 +24,9 @@ export const ModulesListHeader: React.FC = observer(() => { const { setTrackElement } = useEventTracker(); const { allowPermissions } = useUserPermissions(); - const { loader } = useProject(); + const { loader } = useProject(); + + const { t } = useTranslation(); // auth const canUserCreateModule = allowPermissions( @@ -35,10 +39,12 @@ export const ModulesListHeader: React.FC = observer(() => {
- + } />} + link={ + } /> + } />
@@ -54,7 +60,8 @@ export const ModulesListHeader: React.FC = observer(() => { toggleCreateModuleModal(true); }} > -
Add
Module +
{t("add")}
+
{t("project_module.add_module")}
) : ( <> diff --git a/web/core/components/modules/analytics-sidebar/issue-progress.tsx b/web/core/components/modules/analytics-sidebar/issue-progress.tsx index 29bac20c29b..d72ff00fbef 100644 --- a/web/core/components/modules/analytics-sidebar/issue-progress.tsx +++ b/web/core/components/modules/analytics-sidebar/issue-progress.tsx @@ -7,6 +7,7 @@ import { useSearchParams } from "next/navigation"; import { AlertCircle, ChevronUp, ChevronDown } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; import { EIssueFilterType, EIssuesStoreType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IIssueFilterOptions, TModulePlotType } from "@plane/types"; import { CustomSelect, Spinner } from "@plane/ui"; // components @@ -27,8 +28,8 @@ type TModuleAnalyticsProgress = { }; const moduleBurnDownChartOptions = [ - { value: "burndown", label: "Issues" }, - { value: "points", label: "Points" }, + { value: "burndown", i18n_label: "issues" }, + { value: "points", i18n_label: "points" }, ]; export const ModuleAnalyticsProgress: FC = observer((props) => { @@ -47,6 +48,8 @@ export const ModuleAnalyticsProgress: FC = observer((p // state const [loader, setLoader] = useState(false); + const { t } = useTranslation(); + // derived values const moduleDetails = getModuleById(moduleId); const plotType: TModulePlotType = getPlotTypeByModuleId(moduleId); @@ -154,7 +157,7 @@ export const ModuleAnalyticsProgress: FC = observer((p {isModuleDateValid ? (
-
Progress
+
{t("progress")}
{progressHeaderPercentage > 0 && (
{`${progressHeaderPercentage}%`}
)} @@ -165,14 +168,16 @@ export const ModuleAnalyticsProgress: FC = observer((p {moduleBurnDownChartOptions.find((v) => v.value === plotType)?.label ?? "None"} + + {t(moduleBurnDownChartOptions.find((v) => v.value === plotType)?.i18n_label || "none")} + } onChange={onChange} maxHeight="lg" > {moduleBurnDownChartOptions.map((item) => ( - {item.label} + {t(item.i18n_label)} ))} @@ -195,8 +200,8 @@ export const ModuleAnalyticsProgress: FC = observer((p {moduleDetails?.start_date && moduleDetails?.target_date - ? "This module isn't active yet." - : "Invalid date. Please enter valid date."} + ? t("project_module.empty_state.sidebar.in_active") + : t("project_module.empty_state.sidebar.invalid_date")}
@@ -209,11 +214,11 @@ export const ModuleAnalyticsProgress: FC = observer((p
- Ideal + {t("ideal")}
- Current + {t("current")}
{moduleStartDate && moduleEndDate && completionChartDistributionData && ( diff --git a/web/core/components/modules/analytics-sidebar/progress-stats.tsx b/web/core/components/modules/analytics-sidebar/progress-stats.tsx index 34145424748..5c92786d77f 100644 --- a/web/core/components/modules/analytics-sidebar/progress-stats.tsx +++ b/web/core/components/modules/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) =>
User
- No assignee + {t("no_assignee")}
} completed={assignee?.completed ?? 0} @@ -117,7 +119,7 @@ export const AssigneeStatComponent = observer((props: TAssigneeStatComponent) =>
empty members
-
No assignees yet
+
{t("no_assignees_yet")}
)} diff --git a/web/core/components/modules/analytics-sidebar/root.tsx b/web/core/components/modules/analytics-sidebar/root.tsx index e0f22b73b26..d45c3358bbf 100644 --- a/web/core/components/modules/analytics-sidebar/root.tsx +++ b/web/core/components/modules/analytics-sidebar/root.tsx @@ -18,7 +18,13 @@ import { } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; // plane types -import { MODULE_STATUS ,MODULE_LINK_CREATED, MODULE_LINK_DELETED, MODULE_LINK_UPDATED, MODULE_UPDATED } from "@plane/constants"; +import { + MODULE_STATUS, + MODULE_LINK_CREATED, + MODULE_LINK_DELETED, + MODULE_LINK_UPDATED, + MODULE_UPDATED, +} from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { ILinkDetails, IModule, ModuleLink } from "@plane/types"; // plane ui @@ -314,7 +320,7 @@ export const ModuleAnalyticsSidebar: React.FC = observer((props) => { {isInArchivableGroup ? (
- Archive module + {t("archive_module")}
) : (
@@ -322,7 +328,7 @@ export const ModuleAnalyticsSidebar: React.FC = observer((props) => {

Archive module

- Only completed or cancelled
module can be archived. + {t("project_module.quick_actions.archive_module_description")}

@@ -333,7 +339,7 @@ export const ModuleAnalyticsSidebar: React.FC = observer((props) => { - Restore module + {t("restore_module")} )} @@ -345,7 +351,7 @@ export const ModuleAnalyticsSidebar: React.FC = observer((props) => { > - Delete module + {t("delete_module")} @@ -405,7 +411,7 @@ export const ModuleAnalyticsSidebar: React.FC = observer((props) => {
- Date range + {t("date_range")}
= observer((props) => { handleDateChange(val?.from, val?.to); }} placeholder={{ - from: "Start date", - to: "Target date", + from: t("start_date"), + to: t("target_date"), }} disabled={!isEditingAllowed || isArchived} /> @@ -449,7 +455,7 @@ export const ModuleAnalyticsSidebar: React.FC = observer((props) => {
- Lead + {t("lead")}
= observer((props) => { projectId={projectId?.toString() ?? ""} multiple={false} buttonVariant="background-with-text" - placeholder="Lead" + placeholder={t("lead")} disabled={!isEditingAllowed || isArchived} icon={SquareUser} /> @@ -475,7 +481,7 @@ export const ModuleAnalyticsSidebar: React.FC = observer((props) => {
- Members + {t("members")}
= observer((props) => {
- Issues + {t("issues")}
{issueCount} @@ -514,7 +520,7 @@ export const ModuleAnalyticsSidebar: React.FC = observer((props) => {
- Points + {t("points")}
{issueEstimatePointCount} @@ -539,7 +545,7 @@ export const ModuleAnalyticsSidebar: React.FC = observer((props) => {
- Links + {t("links")}
@@ -558,7 +564,7 @@ export const ModuleAnalyticsSidebar: React.FC = observer((props) => { onClick={() => setModuleLinkModal(true)} > - Add link + {t("add_link")}
)} @@ -576,7 +582,9 @@ export const ModuleAnalyticsSidebar: React.FC = observer((props) => {
- No links added yet + + {t("common.no_links_added_yet")} +
{isEditingAllowed && !isArchived && ( )}
diff --git a/web/core/components/modules/form.tsx b/web/core/components/modules/form.tsx index 031651dd200..34f3d9755a7 100644 --- a/web/core/components/modules/form.tsx +++ b/web/core/components/modules/form.tsx @@ -4,6 +4,7 @@ import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; // plane imports import { ETabIndices } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IModule } from "@plane/types"; // ui import { Button, Input, TextArea } from "@plane/ui"; @@ -55,6 +56,8 @@ export const ModuleForm: React.FC = (props) => { const { getIndex } = getTabIndex(ETabIndices.PROJECT_MODULE, isMobile); + const { t } = useTranslation(); + const handleCreateUpdateModule = async (formData: Partial) => { await handleFormSubmit(formData, dirtyFields); @@ -95,7 +98,9 @@ export const ModuleForm: React.FC = (props) => { )} /> )} -

{status ? "Update" : "Create"} module

+

+ {status ? t("update") : t("create")} {t("module").toLowerCase()} +

@@ -103,10 +108,10 @@ export const ModuleForm: React.FC = (props) => { control={control} name="name" 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 } }) => ( @@ -117,7 +122,7 @@ export const ModuleForm: React.FC = (props) => { value={value} onChange={onChange} hasError={Boolean(errors?.name)} - placeholder="Title" + placeholder={t("title")} className="w-full text-base" tabIndex={getIndex("name")} autoFocus @@ -136,7 +141,7 @@ export const ModuleForm: React.FC = (props) => { name="description" value={value} onChange={onChange} - placeholder="Description" + placeholder={t("description")} className="w-full text-base resize-none min-h-24" hasError={Boolean(errors?.description)} tabIndex={getIndex("description")} @@ -165,8 +170,8 @@ export const ModuleForm: React.FC = (props) => { onChangeEndDate(val?.to ? renderFormattedPayloadDate(val.to) : null); }} placeholder={{ - from: "Start date", - to: "End date", + from: t("start_date"), + to: t("end_date"), }} hideIcon={{ to: true, @@ -191,7 +196,7 @@ export const ModuleForm: React.FC = (props) => { projectId={projectId} multiple={false} buttonVariant="border-with-text" - placeholder="Lead" + placeholder={t("lead")} tabIndex={getIndex("lead")} />
@@ -209,7 +214,7 @@ export const ModuleForm: React.FC = (props) => { multiple buttonVariant={value && value.length > 0 ? "transparent-without-text" : "border-with-text"} buttonClassName={value && value.length > 0 ? "hover:bg-transparent px-0" : ""} - placeholder="Members" + placeholder={t("members")} tabIndex={getIndex("member_ids")} />
@@ -220,10 +225,16 @@ export const ModuleForm: React.FC = (props) => {
diff --git a/web/core/components/modules/module-list-item-action.tsx b/web/core/components/modules/module-list-item-action.tsx index 3b864db3776..e6264b5ed59 100644 --- a/web/core/components/modules/module-list-item-action.tsx +++ b/web/core/components/modules/module-list-item-action.tsx @@ -7,6 +7,7 @@ import { useParams } from "next/navigation"; import { SquareUser } from "lucide-react"; // types import { MODULE_STATUS, MODULE_FAVORITED, MODULE_UNFAVORITED } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IModule } from "@plane/types"; // ui import { FavoriteStar, TOAST_TYPE, Tooltip, setPromiseToast, setToast } from "@plane/ui"; @@ -37,6 +38,8 @@ export const ModuleListItemAction: FC = observer((props) => { const { getUserDetails } = useMember(); const { captureEvent } = useEventTracker(); + const { t } = useTranslation(); + // derived values const moduleStatus = MODULE_STATUS.find((status) => status.value === moduleDetails.status); @@ -145,8 +148,8 @@ export const ModuleListItemAction: FC = observer((props) => { }); }} placeholder={{ - from: "Start date", - to: "End date", + from: t("start_date"), + to: t("end_date"), }} disabled={isDisabled} hideIcon={{ from: renderIcon ?? true, to: renderIcon }} diff --git a/web/core/components/modules/quick-actions.tsx b/web/core/components/modules/quick-actions.tsx index 178c8337b58..cb52636df87 100644 --- a/web/core/components/modules/quick-actions.tsx +++ b/web/core/components/modules/quick-actions.tsx @@ -5,6 +5,8 @@ import { observer } from "mobx-react"; // icons import { ArchiveRestoreIcon, ExternalLink, LinkIcon, Pencil, Trash2 } from "lucide-react"; +// plane imports +import { useTranslation } from "@plane/i18n"; // ui import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; // components @@ -37,6 +39,8 @@ export const ModuleQuickActions: React.FC = observer((props) => { const { allowPermissions } = useUserPermissions(); const { getModuleById, restoreModule } = useModule(); + + const { t } = useTranslation(); // derived values const moduleDetails = getModuleById(moduleId); const isArchived = !!moduleDetails?.archived_at; @@ -95,7 +99,7 @@ export const ModuleQuickActions: React.FC = observer((props) => { const MENU_ITEMS: TContextMenuItem[] = [ { key: "edit", - title: "Edit", + title: t("edit"), icon: Pencil, action: handleEditModule, shouldRender: isEditingAllowed && !isArchived, @@ -103,22 +107,22 @@ export const ModuleQuickActions: 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: handleArchiveModule, - title: "Archive", - description: isInArchivableGroup ? undefined : "Only completed or canceled\nmodule can be archived.", + title: t("archive"), + description: isInArchivableGroup ? undefined : "project_module.quick_actions.archive_module_description", icon: ArchiveIcon, className: "items-start", iconClassName: "mt-1", @@ -128,14 +132,14 @@ export const ModuleQuickActions: React.FC = observer((props) => { { key: "restore", action: handleRestoreModule, - title: "Restore", + title: t("restore"), icon: ArchiveRestoreIcon, shouldRender: isEditingAllowed && isArchived, }, { key: "delete", action: handleDeleteModule, - title: "Delete", + title: t("delete"), icon: Trash2, shouldRender: isEditingAllowed, }, From 6a46d1809dbb578285b8585eb5129d773990b57c Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Tue, 28 Jan 2025 20:29:26 +0530 Subject: [PATCH 2/2] chore: code refactor --- packages/i18n/src/locales/en/translations.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index 283fd722ec9..ee4f0d086e9 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -342,7 +342,7 @@ "points": "Points", "no_assignee": "No assignee", "no_assignees_yet": "No assignees yet", - "No labels yet": "No labels yet", + "no_labels_yet": "No labels yet", "ideal": "Ideal", "current": "Current",