diff --git a/admin/Dockerfile.admin b/admin/Dockerfile.admin index ad9469110e7..3d9897705e8 100644 --- a/admin/Dockerfile.admin +++ b/admin/Dockerfile.admin @@ -83,4 +83,6 @@ ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL ENV NEXT_TELEMETRY_DISABLED 1 ENV TURBO_TELEMETRY_DISABLED 1 -EXPOSE 3000 \ No newline at end of file +EXPOSE 3000 + +CMD node admin/server.js admin \ No newline at end of file diff --git a/admin/ce/components/common/upgrade-button.tsx b/admin/ce/components/common/upgrade-button.tsx index aa3c95fdbed..a83202db957 100644 --- a/admin/ce/components/common/upgrade-button.tsx +++ b/admin/ce/components/common/upgrade-button.tsx @@ -9,8 +9,9 @@ import { getButtonStyling } from "@plane/ui"; import { cn } from "@/helpers/common.helper"; export const UpgradeButton: React.FC = () => ( - - Available on One - - + + // + // Available on One + // + // ); diff --git a/admin/core/components/admin-sidebar/help-section.tsx b/admin/core/components/admin-sidebar/help-section.tsx index abba68e3eae..dcb96d0a512 100644 --- a/admin/core/components/admin-sidebar/help-section.tsx +++ b/admin/core/components/admin-sidebar/help-section.tsx @@ -61,17 +61,6 @@ export const HelpSection: FC = observer(() => { {!isSidebarCollapsed && "Redirect to plane"} - - - )} diff --git a/space/core/components/issues/filters/priority.tsx b/space/core/components/issues/filters/priority.tsx index 51c1a751990..bcae7997dc5 100644 --- a/space/core/components/issues/filters/priority.tsx +++ b/space/core/components/issues/filters/priority.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; // ui import { PriorityIcon } from "@plane/ui"; // components @@ -17,6 +18,7 @@ type Props = { export const FilterPriority: React.FC = observer((props) => { const { appliedFilters, handleUpdate, searchQuery } = props; + const { t } = useTranslation(); const [previewEnabled, setPreviewEnabled] = useState(true); @@ -27,7 +29,7 @@ export const FilterPriority: React.FC = observer((props) => { return ( <> 0 ? ` (${appliedFiltersCount})` : ""}`} + title={`${t("Priority")}${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`} isPreviewEnabled={previewEnabled} handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)} /> @@ -40,11 +42,11 @@ export const FilterPriority: React.FC = observer((props) => { isChecked={appliedFilters?.includes(priority.key) ? true : false} onClick={() => handleUpdate(priority.key)} icon={} - title={priority.title} + title={t(priority.title)} /> )) ) : ( -

No matches found

+

{t("no_matches_found")}

)} )} diff --git a/space/core/components/issues/filters/root.tsx b/space/core/components/issues/filters/root.tsx index 641cf007c0e..52b31fbe08c 100644 --- a/space/core/components/issues/filters/root.tsx +++ b/space/core/components/issues/filters/root.tsx @@ -4,6 +4,7 @@ import { FC, useCallback } from "react"; import cloneDeep from "lodash/cloneDeep"; import { observer } from "mobx-react"; import { useRouter } from "next/navigation"; +import { useTranslation } from "@plane/i18n"; // components import { FiltersDropdown } from "@/components/issues/filters/helpers/dropdown"; import { FilterSelection } from "@/components/issues/filters/selection"; @@ -22,6 +23,7 @@ type IssueFiltersDropdownProps = { export const IssueFiltersDropdown: FC = observer((props) => { const { anchor } = props; + const { t } = useTranslation(); // router const router = useRouter(); // hooks @@ -59,7 +61,7 @@ export const IssueFiltersDropdown: FC = observer((pro return (
- + = observer((props) => { const { anchor } = props; + const { t } = useTranslation(); // states const [uploadedAssetIds, setUploadAssetIds] = useState([]); // refs @@ -91,7 +93,7 @@ export const AddComment: React.FC = observer((props) => { } onChange={(comment_json, comment_html) => onChange(comment_html)} isSubmitting={isSubmitting} - placeholder="Add Comment..." + placeholder={t("Add Comment...")} uploadFile={async (file) => { const { asset_id } = await uploadCommentAsset(file, anchor); setUploadAssetIds((prev) => [...prev, asset_id]); diff --git a/space/core/store/profile.store.ts b/space/core/store/profile.store.ts index d0332805890..5e001a875e8 100644 --- a/space/core/store/profile.store.ts +++ b/space/core/store/profile.store.ts @@ -54,6 +54,7 @@ export class ProfileStore implements IProfileStore { has_billing_address: false, created_at: "", updated_at: "", + language: "", }; // services diff --git a/space/core/types/issue.d.ts b/space/core/types/issue.d.ts index 3041a188d04..8d00ab73422 100644 --- a/space/core/types/issue.d.ts +++ b/space/core/types/issue.d.ts @@ -57,6 +57,17 @@ export interface IIssue | "sub_issues_count" | "link_count" | "estimate_point" + | "trip_reference_number" + | "reference_number" + | "hub_code" + | "hub_name" + | "customer_code" + | "customer_name" + | "vendor_name" + | "vendor_code" + | "worker_code" + | "worker_name" + | "business_type" > { comments: Comment[]; reaction_items: IIssueReaction[]; diff --git a/space/next.config.js b/space/next.config.js index d18ce805f4d..de3afceb17c 100644 --- a/space/next.config.js +++ b/space/next.config.js @@ -13,7 +13,7 @@ const nextConfig = { return [ { source: "/", - headers: [{ key: "X-Frame-Options", value: "SAMEORIGIN" }], // clickjacking protection + headers: [{ key: "X-Frame-Options", value: "ALLOWALL" }], // clickjacking protection }, ]; }, diff --git a/web/.eslintignore b/web/.eslintignore index 84f01402dd5..c6c5cb3a389 100644 --- a/web/.eslintignore +++ b/web/.eslintignore @@ -1,3 +1,5 @@ .next/* out/* -public/* \ No newline at end of file +public/* +app/* +core/* \ No newline at end of file diff --git a/web/Dockerfile.web b/web/Dockerfile.web index d7d924d7a47..eb67cbbc486 100644 --- a/web/Dockerfile.web +++ b/web/Dockerfile.web @@ -101,4 +101,7 @@ ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL ENV NEXT_TELEMETRY_DISABLED 1 ENV TURBO_TELEMETRY_DISABLED 1 + EXPOSE 3000 + +CMD node web/server.js web diff --git a/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx b/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx index 4edf41bbdba..4c39da81a02 100644 --- a/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx @@ -1,6 +1,6 @@ "use client"; - import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; // ui import { Breadcrumbs, ContrastIcon, Header } from "@plane/ui"; // components @@ -8,15 +8,17 @@ import { BreadcrumbLink } from "@/components/common"; // plane web components import { UpgradeBadge } from "@/plane-web/components/workspace"; -export const WorkspaceActiveCycleHeader = observer(() => ( -
- - +export const WorkspaceActiveCycleHeader = observer(() => { + const { t } = useTranslation(); + return ( +
+ + } /> } @@ -25,4 +27,5 @@ export const WorkspaceActiveCycleHeader = observer(() => (
-)); + ); +}); \ No newline at end of file diff --git a/web/app/[workspaceSlug]/(projects)/analytics/header.tsx b/web/app/[workspaceSlug]/(projects)/analytics/header.tsx index 4aa66e2a433..28fc008a7cc 100644 --- a/web/app/[workspaceSlug]/(projects)/analytics/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/analytics/header.tsx @@ -3,8 +3,8 @@ import { useEffect } from "react"; import { observer } from "mobx-react"; import { useSearchParams } from "next/navigation"; -// icons import { BarChart2, PanelRight } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; // ui import { Breadcrumbs, Header } from "@plane/ui"; // components @@ -13,8 +13,8 @@ import { BreadcrumbLink } from "@/components/common"; import { cn } from "@/helpers/common.helper"; // hooks import { useAppTheme } from "@/hooks/store"; - export const WorkspaceAnalyticsHeader = observer(() => { + const { t } = useTranslation(); const searchParams = useSearchParams(); const analytics_tab = searchParams.get("analytics_tab"); // store hooks @@ -41,7 +41,7 @@ export const WorkspaceAnalyticsHeader = observer(() => { } />} + link={} />} /> {analytics_tab === "custom" ? ( @@ -64,4 +64,4 @@ export const WorkspaceAnalyticsHeader = observer(() => {
); -}); +}); \ No newline at end of file diff --git a/web/app/[workspaceSlug]/(projects)/analytics/page.tsx b/web/app/[workspaceSlug]/(projects)/analytics/page.tsx index b66c0d19ee7..f96c5511318 100644 --- a/web/app/[workspaceSlug]/(projects)/analytics/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/analytics/page.tsx @@ -4,6 +4,7 @@ import React, { Fragment } from "react"; import { observer } from "mobx-react"; import { useSearchParams } from "next/navigation"; import { Tab } from "@headlessui/react"; +import { useTranslation } from "@plane/i18n"; // components import { Header, EHeaderVariant } from "@plane/ui"; import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics"; @@ -16,6 +17,7 @@ import { EmptyStateType } from "@/constants/empty-state"; import { useCommandPalette, useEventTracker, useProject, useWorkspace } from "@/hooks/store"; const AnalyticsPage = observer(() => { + const { t } = useTranslation(); const searchParams = useSearchParams(); const analytics_tab = searchParams.get("analytics_tab"); // store hooks @@ -45,7 +47,7 @@ const AnalyticsPage = observer(() => { selected ? "text-custom-primary-100 " : "hover:text-custom-text-200" }`} > - {tab.title} + {t(tab.title)}
diff --git a/web/app/[workspaceSlug]/(projects)/header.tsx b/web/app/[workspaceSlug]/(projects)/header.tsx index a7a19afe6b0..0d6a3e451db 100644 --- a/web/app/[workspaceSlug]/(projects)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/header.tsx @@ -1,17 +1,17 @@ "use client"; -import Image from "next/image"; +// import Image from "next/image"; import { useTheme } from "next-themes"; import { Home } from "lucide-react"; // images -import githubBlackImage from "/public/logos/github-black.png"; -import githubWhiteImage from "/public/logos/github-white.png"; +// import githubBlackImage from "/public/logos/github-black.png"; +// import githubWhiteImage from "/public/logos/github-white.png"; // ui import { Breadcrumbs, Header } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; // constants -import { GITHUB_REDIRECTED } from "@/constants/event-tracker"; +// import { GITHUB_REDIRECTED } from "@/constants/event-tracker"; // hooks import { useEventTracker } from "@/hooks/store"; @@ -33,27 +33,6 @@ export const WorkspaceDashboardHeader = () => {
- - - captureEvent(GITHUB_REDIRECTED, { - element: "navbar", - }) - } - className="flex flex-shrink-0 items-center gap-1.5 rounded bg-custom-background-80 px-3 py-1.5" - href="https://github.com/makeplane/plane" - target="_blank" - rel="noopener noreferrer" - > - GitHub Logo - Star us on GitHub - - ); diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx index bf1f88d15b4..85bdb197c95 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; // ui import { Button } from "@plane/ui"; // components @@ -15,6 +16,7 @@ import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/u const PER_PAGE = 100; const ProfileActivityPage = observer(() => { + const { t } = useTranslation(); // states const [pageCount, setPageCount] = useState(1); const [totalPages, setTotalPages] = useState(0); @@ -50,7 +52,7 @@ const ProfileActivityPage = observer(() => {
-

Recent activity

+

{t("Recent activity")}

{canDownloadActivity && }
diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/header.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/header.tsx index 13a944c8819..261d305c760 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/header.tsx @@ -7,6 +7,7 @@ import Link from "next/link"; import { useParams } from "next/navigation"; import { ChevronDown, PanelRight } from "lucide-react"; import { IUserProfileProjectSegregation } from "@plane/types"; +import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, Header, CustomMenu, UserActivityIcon } from "@plane/ui"; import { BreadcrumbLink } from "@/components/common"; // components @@ -24,6 +25,7 @@ type TUserProfileHeader = { export const UserProfileHeader: FC = observer((props) => { const { userProjectsData, type = undefined, showProfileIssuesFilter } = props; + const { t } = useTranslation(); // router const { workspaceSlug, userId } = useParams(); // store hooks @@ -86,7 +88,7 @@ export const UserProfileHeader: FC = observer((props) => { href={`/${workspaceSlug}/profile/${userId}/${tab.route}`} className="w-full text-custom-text-300" > - {tab.label} + {tab.key ? t(tab.key) : null} ))} diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx index f31ff959dce..1daa63e6ee5 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx @@ -4,6 +4,7 @@ import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; import useSWR from "swr"; // components +import { useTranslation } from "@plane/i18n"; import { AppHeader, ContentWrapper } from "@/components/core"; import { ProfileSidebar } from "@/components/profile"; // constants @@ -32,6 +33,7 @@ const UseProfileLayout: React.FC = observer((props) => { const pathname = usePathname(); // store hooks const { allowPermissions } = useUserPermissions(); + const { t } = useTranslation(); // derived values const isAuthorized = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], @@ -79,7 +81,7 @@ const UseProfileLayout: React.FC = observer((props) => {
{children}
) : (
- You do not have the permission to access this page. + {t("you_do_not_have_the_permission_to_access_this_page")}
)}
@@ -93,4 +95,4 @@ const UseProfileLayout: React.FC = observer((props) => { ); }); -export default UseProfileLayout; +export default UseProfileLayout; \ No newline at end of file diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/mobile-header.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/mobile-header.tsx index b963ca147c9..a453d615218 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/mobile-header.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/mobile-header.tsx @@ -8,6 +8,7 @@ import { ChevronDown } from "lucide-react"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types"; // ui +import { useTranslation } from "@plane/i18n"; import { CustomMenu } from "@plane/ui"; // components import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues"; @@ -25,6 +26,7 @@ import { isIssueFilterActive } from "@/helpers/filter.helper"; import { useIssues, useLabel } from "@/hooks/store"; export const ProfileIssuesMobileHeader = observer(() => { + const { t } = useTranslation(); // router const { workspaceSlug, userId } = useParams(); // store hook @@ -101,7 +103,7 @@ export const ProfileIssuesMobileHeader = observer(() => { workspaceSlug.toString(), undefined, EIssueFilterType.DISPLAY_PROPERTIES, - property, + property as IIssueDisplayProperties, userId.toString() ); }, @@ -145,7 +147,7 @@ export const ProfileIssuesMobileHeader = observer(() => { placement="bottom-end" menuButton={
- Filters + {t("filters")}
} @@ -171,7 +173,7 @@ export const ProfileIssuesMobileHeader = observer(() => { placement="bottom-end" menuButton={
- Display + {t("display")}
} diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/navbar.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/navbar.tsx index 4acb93217f6..b6195d6126a 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/navbar.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/navbar.tsx @@ -2,6 +2,7 @@ import React from "react"; import Link from "next/link"; import { useParams, usePathname } from "next/navigation"; +import { useTranslation } from "@plane/i18n"; // components // constants @@ -14,7 +15,7 @@ type Props = { export const ProfileNavbar: React.FC = (props) => { const { isAuthorized } = props; - + const { t } = useTranslation(); const { workspaceSlug, userId } = useParams(); const pathname = usePathname(); @@ -32,11 +33,11 @@ export const ProfileNavbar: React.FC = (props) => { : "border-transparent" }`} > - {tab.label} + {tab.key ? t(tab.key) : null} ))}
); -}; +}; \ No newline at end of file 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 902f15cde84..a5d23451575 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 @@ -9,6 +9,7 @@ import { ArrowRight, PanelRight } from "lucide-react"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui +import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip, Header } from "@plane/ui"; // components import { ProjectAnalyticsModal } from "@/components/analytics"; @@ -64,6 +65,7 @@ const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => { }; export const CycleIssuesHeader: React.FC = observer(() => { + const { t } = useTranslation(); // states const [analyticsModal, setAnalyticsModal] = useState(false); // router @@ -139,7 +141,7 @@ export const CycleIssuesHeader: React.FC = observer(() => { const handleDisplayProperties = useCallback( (property: Partial) => { if (!workspaceSlug || !projectId) return; - updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, cycleId); + updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property as IIssueDisplayProperties, cycleId); }, [workspaceSlug, projectId, cycleId, updateFilters] ); @@ -250,7 +252,7 @@ export const CycleIssuesHeader: React.FC = observer(() => { selectedLayout={activeLayout} /> @@ -269,7 +271,7 @@ export const CycleIssuesHeader: React.FC = observer(() => { moduleViewDisabled={!currentProjectDetails?.module_view} /> - + { {canUserCreateIssue && ( <> {!isCompletedCycle && ( )} diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx index aa81ae5816a..33bf64b816a 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/mobile-header.tsx @@ -7,6 +7,7 @@ import { Calendar, ChevronDown, Kanban, List } from "lucide-react"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui +import { useTranslation } from "@plane/i18n"; import { CustomMenu } from "@plane/ui"; // components import { ProjectAnalyticsModal } from "@/components/analytics"; @@ -25,6 +26,7 @@ import { isIssueFilterActive } from "@/helpers/filter.helper"; import { useIssues, useCycle, useProjectState, useLabel, useMember, useProject } from "@/hooks/store"; export const CycleIssuesMobileHeader = () => { + const { t } = useTranslation(); const [analyticsModal, setAnalyticsModal] = useState(false); const { getCycleById } = useCycle(); const layouts = [ @@ -108,7 +110,7 @@ export const CycleIssuesMobileHeader = () => { workspaceSlug.toString(), projectId.toString(), EIssueFilterType.DISPLAY_PROPERTIES, - property, + property as IIssueDisplayProperties, cycleId.toString() ); }, @@ -150,7 +152,7 @@ export const CycleIssuesMobileHeader = () => { placement="bottom-end" menuButton={ - Filters + {t("filters")} } @@ -178,7 +180,7 @@ export const CycleIssuesMobileHeader = () => { placement="bottom-end" menuButton={ - Display + {t("display")} } @@ -202,7 +204,7 @@ export const CycleIssuesMobileHeader = () => { onClick={() => setAnalyticsModal(true)} className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200" > - Analytics + {t("analytics")}
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx index 49816bad741..5c7df0fea8a 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/draft-issues/header.tsx @@ -6,6 +6,7 @@ import { useParams } from "next/navigation"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui +import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; @@ -24,6 +25,7 @@ import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/h import { usePlatformOS } from "@/hooks/use-platform-os"; export const ProjectDraftIssueHeader: FC = observer(() => { + const { t } = useTranslation(); // router const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string }; // store hooks @@ -79,7 +81,7 @@ export const ProjectDraftIssueHeader: FC = observer(() => { const handleDisplayProperties = useCallback( (property: Partial) => { if (!workspaceSlug || !projectId) return; - updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property); + updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property as IIssueDisplayProperties); }, [workspaceSlug, projectId, updateFilters] ); @@ -138,7 +140,7 @@ export const ProjectDraftIssueHeader: FC = observer(() => { onChange={(layout) => handleLayoutChange(layout)} selectedLayout={activeLayout} /> - + { moduleViewDisabled={!currentProjectDetails?.module_view} /> - + { + const { t } = useTranslation(); // router const router = useAppRouter(); const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string }; @@ -120,7 +122,7 @@ export const ProjectIssuesHeader = observer(() => { }} size="sm" > -
Add
Issue +
{t("add")}
{t("issues")} ) : ( <> diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx index 025c9fab04f..eebc0ecde41 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/mobile-header.tsx @@ -8,6 +8,7 @@ import { Calendar, ChevronDown, Kanban, List } from "lucide-react"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui +import { useTranslation } from "@plane/i18n"; import { CustomMenu } from "@plane/ui"; // components import { ProjectAnalyticsModal } from "@/components/analytics"; @@ -26,6 +27,7 @@ import { isIssueFilterActive } from "@/helpers/filter.helper"; import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store"; export const ProjectIssuesMobileHeader = observer(() => { + const { t } = useTranslation(); const layouts = [ { key: "list", title: "List", icon: List }, { key: "kanban", title: "Board", icon: Kanban }, @@ -89,7 +91,7 @@ export const ProjectIssuesMobileHeader = observer(() => { const handleDisplayProperties = useCallback( (property: Partial) => { if (!workspaceSlug || !projectId) return; - updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property); + updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property as IIssueDisplayProperties); }, [workspaceSlug, projectId, updateFilters] ); @@ -134,7 +136,7 @@ export const ProjectIssuesMobileHeader = observer(() => { placement="bottom-end" menuButton={ - Filters + {t("filters")} } @@ -162,7 +164,7 @@ export const ProjectIssuesMobileHeader = observer(() => { placement="bottom-end" menuButton={ - Display + {t("display")} } @@ -185,7 +187,7 @@ export const ProjectIssuesMobileHeader = observer(() => { onClick={() => setAnalyticsModal(true)} className="flex flex-grow justify-center border-l border-custom-border-200 text-sm text-custom-text-200" > - Analytics + {t("analytics")} diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx index 59dcae31b90..4c5e867f40a 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx @@ -9,6 +9,7 @@ import { ArrowRight, PanelRight } from "lucide-react"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui +import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip, Header } from "@plane/ui"; // components import { ProjectAnalyticsModal } from "@/components/analytics"; @@ -44,6 +45,7 @@ import { usePlatformOS } from "@/hooks/use-platform-os"; import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => { + const { t } = useTranslation(); // router const { workspaceSlug, projectId } = useParams(); // store hooks @@ -67,6 +69,7 @@ const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => { }; export const ModuleIssuesHeader: React.FC = observer(() => { + const { t } = useTranslation(); // states const [analyticsModal, setAnalyticsModal] = useState(false); // router @@ -140,7 +143,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => { const handleDisplayProperties = useCallback( (property: Partial) => { if (!projectId) return; - updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_PROPERTIES, property); + updateFilters(projectId.toString(), EIssueFilterType.DISPLAY_PROPERTIES, property as IIssueDisplayProperties); }, [projectId, updateFilters] ); @@ -248,7 +251,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => { selectedLayout={activeLayout} /> @@ -267,7 +270,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => { moduleViewDisabled={!currentProjectDetails?.module_view} /> - + { variant="neutral-primary" size="sm" > - Analytics + {t("analytics")} ) : ( diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx index 0edfa2b5a2b..afed25108a0 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/mobile-header.tsx @@ -8,6 +8,7 @@ import { Calendar, ChevronDown, Kanban, List } from "lucide-react"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui +import { useTranslation } from "@plane/i18n"; import { CustomMenu } from "@plane/ui"; // components import { ProjectAnalyticsModal } from "@/components/analytics"; @@ -26,6 +27,7 @@ import { isIssueFilterActive } from "@/helpers/filter.helper"; import { useIssues, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store"; export const ModuleIssuesMobileHeader = observer(() => { + const { t } = useTranslation(); const [analyticsModal, setAnalyticsModal] = useState(false); const { currentProjectDetails } = useProject(); const { getModuleById } = useModule(); @@ -91,7 +93,7 @@ export const ModuleIssuesMobileHeader = observer(() => { const handleDisplayProperties = useCallback( (property: Partial) => { if (!workspaceSlug || !projectId) return; - updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property, moduleId); + updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property as IIssueDisplayProperties, moduleId); }, [workspaceSlug, projectId, moduleId, updateFilters] ); @@ -131,7 +133,7 @@ export const ModuleIssuesMobileHeader = observer(() => { placement="bottom-end" menuButton={ - Filters + {t("filters")} } @@ -159,7 +161,7 @@ export const ModuleIssuesMobileHeader = observer(() => { placement="bottom-end" menuButton={ - Display + {t("display")} } @@ -183,7 +185,7 @@ export const ModuleIssuesMobileHeader = observer(() => { onClick={() => setAnalyticsModal(true)} className="flex flex-grow justify-center border-l border-custom-border-200 text-sm text-custom-text-200" > - Analytics + {t("analytics")} diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx index 71f2d8df425..46b8ebb7009 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx @@ -8,6 +8,7 @@ import { Layers, Lock } from "lucide-react"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui +import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, Button, CustomMenu, Tooltip, Header } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; @@ -39,6 +40,7 @@ import { import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; export const ProjectViewIssuesHeader: React.FC = observer(() => { + const { t } = useTranslation(); // router const { workspaceSlug, projectId, viewId } = useParams(); // store hooks @@ -121,7 +123,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => { workspaceSlug.toString(), projectId.toString(), EIssueFilterType.DISPLAY_PROPERTIES, - property, + property as IIssueDisplayProperties, viewId.toString() ); }, @@ -145,7 +147,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => { link={ @@ -161,7 +163,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => { link={ } /> } @@ -249,7 +251,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => { /> { moduleViewDisabled={!currentProjectDetails?.module_view} /> - + { }} size="sm" > - Add issue + {t("add_issue")} ) : ( <> diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx index 2fd2d652479..98cf18279d5 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx @@ -4,6 +4,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { Layers } from "lucide-react"; // ui +import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, Button, Header } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; @@ -12,6 +13,7 @@ import { ViewListHeader } from "@/components/views"; import { useCommandPalette, useProject } from "@/hooks/store"; export const ProjectViewsHeader = observer(() => { + const { t } = useTranslation(); // router const { workspaceSlug } = useParams(); // store hooks @@ -41,7 +43,7 @@ export const ProjectViewsHeader = observer(() => { /> } />} + link={} />} /> @@ -49,7 +51,7 @@ export const ProjectViewsHeader = observer(() => {
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/mobile-header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/mobile-header.tsx index 608ed5dff17..b7859b319ff 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/mobile-header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/mobile-header.tsx @@ -4,6 +4,7 @@ import { observer } from "mobx-react"; // icons import { ChevronDown, ListFilter } from "lucide-react"; // components +import { useTranslation } from "@plane/i18n"; import { Row } from "@plane/ui"; import { FiltersDropdown } from "@/components/issues/issue-layouts"; import { ViewFiltersSelection } from "@/components/views/filters/filter-selection"; @@ -12,6 +13,7 @@ import { ViewOrderByDropdown } from "@/components/views/filters/order-by"; import { useMember, useProjectView } from "@/hooks/store"; export const ViewMobileHeader = observer(() => { + const { t } = useTranslation(); // store hooks const { filters, updateFilters } = useProjectView(); const { @@ -40,7 +42,7 @@ export const ViewMobileHeader = observer(() => { isFiltersApplied={false} menuButton={ - Filters + {t("filters")} } diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx index 92fedb453e6..ffd161b7457 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx @@ -17,16 +17,18 @@ import { EViewAccess } from "@/constants/views"; import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks import { useProject, useProjectView } from "@/hooks/store"; +import { useTranslation } from "@plane/i18n"; const ProjectViewsPage = observer(() => { // router const { workspaceSlug, projectId } = useParams(); + const { t } = useTranslation(); // store const { getProjectById, currentProjectDetails } = useProject(); const { filters, updateFilters, clearAllFilters } = useProjectView(); // derived values const project = projectId ? getProjectById(projectId.toString()) : undefined; - const pageTitle = project?.name ? `${project?.name} - Views` : undefined; + const pageTitle = project?.name ? `${project?.name} - ${t("views")}` : undefined; if (!workspaceSlug || !projectId) return <>; @@ -66,8 +68,19 @@ const ProjectViewsPage = observer(() => {
{ + const ownedBy = filters.filters?.owned_by; + clearAllFilters(); + if (ownedBy) { + updateFilters("filters", { owned_by: ownedBy }); + } + }} + handleRemoveFilter={(key, value) => { + if (key === "owned_by") { + return; + } + handleRemoveFilter(key, value); + }} alwaysAllowEditing />
diff --git a/web/app/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx b/web/app/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx index 3eec4b02764..4c1fba76815 100644 --- a/web/app/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx @@ -10,16 +10,18 @@ import { GlobalViewsHeader } from "@/components/workspace"; import { DEFAULT_GLOBAL_VIEWS_LIST } from "@/constants/workspace"; // hooks import { useWorkspace } from "@/hooks/store"; +import { useTranslation } from "@plane/i18n"; const GlobalViewIssuesPage = observer(() => { // router const { globalViewId } = useParams(); // store hooks const { currentWorkspace } = useWorkspace(); + const { t } = useTranslation(); // derived values const defaultView = DEFAULT_GLOBAL_VIEWS_LIST.find((view) => view.key === globalViewId); - const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - All Views` : undefined; + const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - All ${t("views")}` : undefined; return ( <> diff --git a/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx b/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx index 500d23ed701..92200ac721a 100644 --- a/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx @@ -7,6 +7,7 @@ import { useParams } from "next/navigation"; import { Layers } from "lucide-react"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui +import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, Button, Header } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; @@ -20,6 +21,7 @@ import { isIssueFilterActive } from "@/helpers/filter.helper"; import { useLabel, useMember, useIssues, useGlobalView } from "@/hooks/store"; export const GlobalIssuesHeader = observer(() => { + const { t } = useTranslation(); // states const [createViewModal, setCreateViewModal] = useState(false); // router @@ -86,7 +88,7 @@ export const GlobalIssuesHeader = observer(() => { workspaceSlug.toString(), undefined, EIssueFilterType.DISPLAY_PROPERTIES, - property, + property as IIssueDisplayProperties, globalViewId.toString() ); }, @@ -103,7 +105,7 @@ export const GlobalIssuesHeader = observer(() => { } />} + link={} />} /> @@ -112,7 +114,7 @@ export const GlobalIssuesHeader = observer(() => { {!isLocked ? ( <> @@ -126,7 +128,7 @@ export const GlobalIssuesHeader = observer(() => { memberIds={workspaceMemberIds ?? undefined} /> - + { )} diff --git a/web/app/[workspaceSlug]/(projects)/workspace-views/page.tsx b/web/app/[workspaceSlug]/(projects)/workspace-views/page.tsx index 7de44258473..3981228b9b6 100644 --- a/web/app/[workspaceSlug]/(projects)/workspace-views/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/workspace-views/page.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react"; // icons import { Search } from "lucide-react"; // ui +import { useTranslation } from "@plane/i18n"; import { Input } from "@plane/ui"; // components import { PageHead } from "@/components/core"; @@ -16,10 +17,11 @@ import { useWorkspace } from "@/hooks/store"; const WorkspaceViewsPage = observer(() => { const [query, setQuery] = useState(""); + const { t } = useTranslation(); // store const { currentWorkspace } = useWorkspace(); // derived values - const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - All Views` : undefined; + const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - All ${t("views")}` : undefined; return ( <> diff --git a/web/app/create-workspace/page.tsx b/web/app/create-workspace/page.tsx index 1a5625ad646..ee1c909f982 100644 --- a/web/app/create-workspace/page.tsx +++ b/web/app/create-workspace/page.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react"; import Image from "next/image"; import Link from "next/link"; import { useTheme } from "next-themes"; +import { useTranslation } from "@plane/i18n"; import { IWorkspace } from "@plane/types"; // components import { CreateWorkspaceForm } from "@/components/workspace"; @@ -18,6 +19,7 @@ import BlackHorizontalLogo from "@/public/plane-logos/black-horizontal-with-blue import WhiteHorizontalLogo from "@/public/plane-logos/white-horizontal-with-blue-logo.png"; const CreateWorkspacePage = observer(() => { + const { t } = useTranslation(); // router const router = useAppRouter(); // store hooks @@ -48,7 +50,7 @@ const CreateWorkspacePage = observer(() => { href="/" >
- Plane logo + {t("plane_logo")}
@@ -72,4 +74,4 @@ const CreateWorkspacePage = observer(() => { ); }); -export default CreateWorkspacePage; +export default CreateWorkspacePage; \ No newline at end of file diff --git a/web/app/invitations/page.tsx b/web/app/invitations/page.tsx index 580896bb569..a6df6b51c33 100644 --- a/web/app/invitations/page.tsx +++ b/web/app/invitations/page.tsx @@ -7,8 +7,8 @@ import Link from "next/link"; import { useTheme } from "next-themes"; import useSWR, { mutate } from "swr"; -// icons import { CheckCircle2 } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; // types import type { IWorkspaceMemberInvitation } from "@plane/types"; // ui @@ -45,6 +45,7 @@ const UserInvitationsPage = observer(() => { // router const router = useAppRouter(); // store hooks + const { t } = useTranslation(); const { captureEvent, joinWorkspaceMetricGroup } = useEventTracker(); const { data: currentUser } = useUser(); const { updateUserProfile } = useUserProfile(); @@ -72,8 +73,8 @@ const UserInvitationsPage = observer(() => { if (invitationsRespond.length === 0) { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Please select at least one invitation.", + title: t("error"), + message: t("please_select_at_least_one_invitation"), }); return; } @@ -107,8 +108,8 @@ const UserInvitationsPage = observer(() => { .catch(() => { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Something went wrong, Please try again.", + title: t("error"), + message: t("something_went_wrong_please_try_again"), }); setIsJoiningWorkspaces(false); }); @@ -122,8 +123,8 @@ const UserInvitationsPage = observer(() => { }); setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Something went wrong, Please try again.", + title: t("error"), + message: t("something_went_wrong_please_try_again"), }); setIsJoiningWorkspaces(false); }); @@ -149,8 +150,8 @@ const UserInvitationsPage = observer(() => { invitations.length > 0 ? (
-
We see that someone has invited you to
-

Join a workspace

+
{t("we_see_that_someone_has_invited_you_to_join_a_workspace")}
+

{t("join_a_workspace")}

{invitations.map((invitation) => { const isSelected = invitationsRespond.includes(invitation.id); @@ -204,12 +205,12 @@ const UserInvitationsPage = observer(() => { disabled={isJoiningWorkspaces || invitationsRespond.length === 0} loading={isJoiningWorkspaces} > - Accept & Join + {t("accept_and_join")} @@ -219,11 +220,11 @@ const UserInvitationsPage = observer(() => { ) : (
router.push("/"), }} /> @@ -235,4 +236,4 @@ const UserInvitationsPage = observer(() => { ); }); -export default UserInvitationsPage; +export default UserInvitationsPage; \ No newline at end of file diff --git a/web/app/profile/activity/page.tsx b/web/app/profile/activity/page.tsx index afc9b29bf8d..b3bfeede629 100644 --- a/web/app/profile/activity/page.tsx +++ b/web/app/profile/activity/page.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; // ui import { Button } from "@plane/ui"; // components @@ -18,6 +19,7 @@ import { EmptyStateType } from "@/constants/empty-state"; const PER_PAGE = 100; const ProfileActivityPage = observer(() => { + const { t } = useTranslation(); // states const [pageCount, setPageCount] = useState(1); const [totalPages, setTotalPages] = useState(0); @@ -55,12 +57,12 @@ const ProfileActivityPage = observer(() => { <> - + {activityPages} {isLoadMoreVisible && (
)} @@ -69,4 +71,4 @@ const ProfileActivityPage = observer(() => { ); }); -export default ProfileActivityPage; +export default ProfileActivityPage; \ No newline at end of file diff --git a/web/app/profile/appearance/page.tsx b/web/app/profile/appearance/page.tsx index 775ff637b04..61130b33345 100644 --- a/web/app/profile/appearance/page.tsx +++ b/web/app/profile/appearance/page.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from "react"; import { observer } from "mobx-react"; import { useTheme } from "next-themes"; +import { useTranslation } from "@plane/i18n"; import { IUserTheme } from "@plane/types"; import { setPromiseToast } from "@plane/ui"; // components @@ -15,8 +16,8 @@ import { I_THEME_OPTION, THEME_OPTIONS } from "@/constants/themes"; import { applyTheme, unsetCustomCssVariables } from "@/helpers/theme.helper"; // hooks import { useUserProfile } from "@/hooks/store"; - const ProfileAppearancePage = observer(() => { + const { t } = useTranslation(); const { setTheme } = useTheme(); // states const [currentTheme, setCurrentTheme] = useState(null); @@ -62,11 +63,11 @@ const ProfileAppearancePage = observer(() => { {userProfile ? ( - +
-

Theme

-

Select or customize your interface color scheme.

+

{t("theme")}

+

{t("select_or_customize_your_interface_color_scheme")}

@@ -83,4 +84,4 @@ const ProfileAppearancePage = observer(() => { ); }); -export default ProfileAppearancePage; +export default ProfileAppearancePage; \ No newline at end of file diff --git a/web/app/profile/notifications/page.tsx b/web/app/profile/notifications/page.tsx index b39563378b1..d41e2b8084b 100644 --- a/web/app/profile/notifications/page.tsx +++ b/web/app/profile/notifications/page.tsx @@ -2,6 +2,7 @@ import useSWR from "swr"; // components +import { useTranslation } from "@plane/i18n"; import { PageHead } from "@/components/core"; import { ProfileSettingContentHeader, ProfileSettingContentWrapper } from "@/components/profile"; import { EmailNotificationForm } from "@/components/profile/notification"; @@ -12,6 +13,7 @@ import { UserService } from "@/services/user.service"; const userService = new UserService(); export default function ProfileNotificationPage() { + const { t } = useTranslation(); // fetching user email notification settings const { data, isLoading } = useSWR("CURRENT_USER_EMAIL_NOTIFICATION_SETTINGS", () => userService.currentUserEmailNotificationSettings() @@ -23,14 +25,14 @@ export default function ProfileNotificationPage() { return ( <> - + ); -} +} \ No newline at end of file diff --git a/web/app/profile/page.tsx b/web/app/profile/page.tsx index 1ec755bd1e1..1948b336943 100644 --- a/web/app/profile/page.tsx +++ b/web/app/profile/page.tsx @@ -1,137 +1,18 @@ "use client"; -import React, { useEffect, useState } from "react"; import { observer } from "mobx-react"; -import { Controller, useForm } from "react-hook-form"; -import { ChevronDown, CircleUserRound } from "lucide-react"; -import { Disclosure, Transition } from "@headlessui/react"; -import type { IUser } from "@plane/types"; -import { - Button, - CustomSelect, - CustomSearchSelect, - Input, - TOAST_TYPE, - setPromiseToast, - setToast, - Tooltip, -} from "@plane/ui"; +import { useTranslation } from "@plane/i18n"; // components -import { DeactivateAccountModal } from "@/components/account"; import { LogoSpinner } from "@/components/common"; -import { ImagePickerPopover, UserImageUploadModal, PageHead } from "@/components/core"; -import { ProfileSettingContentWrapper } from "@/components/profile"; -// constants -import { TIME_ZONES, TTimezone } from "@/constants/timezones"; -import { USER_ROLES } from "@/constants/workspace"; -// helpers -import { getFileURL } from "@/helpers/file.helper"; +import { PageHead } from "@/components/core"; +import { ProfileSettingContentWrapper, ProfileForm } from "@/components/profile"; // hooks import { useUser } from "@/hooks/store"; -const defaultValues: Partial = { - avatar_url: "", - cover_image_url: "", - first_name: "", - last_name: "", - display_name: "", - email: "", - role: "Product / Project Manager", - user_timezone: "Asia/Kolkata", -}; - const ProfileSettingsPage = observer(() => { - // states - const [isLoading, setIsLoading] = useState(false); - const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false); - const [deactivateAccountModal, setDeactivateAccountModal] = useState(false); - // form info - const { - handleSubmit, - reset, - watch, - control, - setValue, - formState: { errors }, - } = useForm({ defaultValues }); - // derived values - const userAvatar = watch("avatar_url"); - const userCover = watch("cover_image_url"); + const { t } = useTranslation(); // store hooks - const { data: currentUser, updateCurrentUser } = useUser(); - - useEffect(() => { - reset({ ...defaultValues, ...currentUser }); - }, [currentUser, reset]); - - const onSubmit = async (formData: IUser) => { - setIsLoading(true); - const payload: Partial = { - first_name: formData.first_name, - last_name: formData.last_name, - avatar_url: formData.avatar_url, - cover_image_url: formData.cover_image_url, - role: formData.role, - display_name: formData?.display_name, - user_timezone: formData.user_timezone, - }; - - const updateCurrentUserDetail = updateCurrentUser(payload).finally(() => setIsLoading(false)); - setPromiseToast(updateCurrentUserDetail, { - loading: "Updating...", - success: { - title: "Success!", - message: () => `Profile updated successfully.`, - }, - error: { - title: "Error!", - message: () => `There was some error in updating your profile. Please try again.`, - }, - }); - }; - - const handleDelete = async (url: string | null | undefined) => { - if (!url) return; - - await updateCurrentUser({ - avatar_url: "", - }) - .then(() => { - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Profile picture deleted successfully.", - }); - setValue("avatar_url", ""); - }) - .catch(() => { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "There was some error in deleting your profile picture. Please try again.", - }); - }) - .finally(() => { - setIsImageUploadModalOpen(false); - }); - }; - - const getTimeZoneLabel = (timezone: TTimezone | undefined) => { - if (!timezone) return undefined; - return ( -
- {timezone.gmtOffset} - {timezone.name} -
- ); - }; - - const timeZoneOptions = TIME_ZONES.map((timeZone) => ({ - value: timeZone.value, - query: timeZone.name + " " + timeZone.gmtOffset + " " + timeZone.value, - content: getTimeZoneLabel(timeZone), - })); - + const { data: currentUser, userProfile } = useUser(); if (!currentUser) return (
@@ -141,317 +22,12 @@ const ProfileSettingsPage = observer(() => { return ( <> - + - ( - setIsImageUploadModalOpen(false)} - handleRemove={async () => await handleDelete(currentUser?.avatar_url)} - onSuccess={(url) => { - onChange(url); - handleSubmit(onSubmit)(); - setIsImageUploadModalOpen(false); - }} - value={value && value.trim() !== "" ? value : null} - /> - )} - /> - setDeactivateAccountModal(false)} /> -
-
-
- {currentUser?.first_name -
-
-
- -
-
-
-
- ( - onChange(imageUrl)} - control={control} - value={value ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"} - isProfileCover - /> - )} - /> -
-
-
-
-
- {`${watch("first_name")} ${watch("last_name")}`} -
- {watch("email")} -
-
-
-
-
-

- First name* -

- ( - - )} - /> - {errors.first_name && {errors.first_name.message}} -
-
-

Last name

- ( - - )} - /> -
-
-

- Display name* -

- { - if (value.trim().length < 1) return "Display name can't be empty."; - if (value.split(" ").length > 1) return "Display name can't have two consecutive spaces."; - if (value.replace(/\s/g, "").length < 1) - return "Display name must be at least 1 character long."; - if (value.replace(/\s/g, "").length > 20) - return "Display name must be less than 20 characters long."; - return true; - }, - }} - render={({ field: { value, onChange, ref } }) => ( - - )} - /> - {errors?.display_name && ( - {errors?.display_name?.message} - )} -
-
-

- Email* -

- ( - - )} - /> -
-
-

- Role* -

- ( - - {USER_ROLES.map((item) => ( - - {item.label} - - ))} - - )} - /> - {errors.role && Please select a role} -
-
-
-
-
-
-

- Timezone* -

- ( - t.value === value)) ?? value) - : "Select a timezone" - } - options={timeZoneOptions} - onChange={onChange} - buttonClassName={errors.user_timezone ? "border-red-500" : ""} - className="rounded-md border-[0.5px] !border-custom-border-200" - optionsClassName="w-72" - input - /> - )} - /> - {errors.user_timezone && {errors.user_timezone.message}} -
- -
-

Language

- {}} - className="rounded-md bg-custom-background-90" - input - disabled - /> -
-
-
-
- -
-
-
-
- - {({ open }) => ( - <> - - Deactivate account - - - - -
- - When deactivating an account, all of the data and resources within that account will be - permanently removed and cannot be recovered. - -
- -
-
-
-
- - )} -
+
); }); -export default ProfileSettingsPage; +export default ProfileSettingsPage; \ No newline at end of file diff --git a/web/app/profile/security/page.tsx b/web/app/profile/security/page.tsx index 594816cc165..7d167d0e84d 100644 --- a/web/app/profile/security/page.tsx +++ b/web/app/profile/security/page.tsx @@ -4,6 +4,7 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; import { Eye, EyeOff } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; // ui import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui"; // components @@ -55,6 +56,8 @@ const SecurityPage = observer(() => { const oldPassword = watch("old_password"); const password = watch("new_password"); const confirmPassword = watch("confirm_password"); + // i18n + const { t } = useTranslation(); const isNewPasswordSameAsOldPassword = oldPassword !== "" && password !== "" && password === oldPassword; @@ -76,8 +79,8 @@ const SecurityPage = observer(() => { setShowPassword(defaultShowPassword); setToast({ type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Password changed successfully.", + title: t("success"), + message: t("password_changed_successfully"), }); } catch (err: any) { const errorInfo = authErrorHandler(err.error_code?.toString()); @@ -85,7 +88,7 @@ const SecurityPage = observer(() => { type: TOAST_TYPE.ERROR, title: errorInfo?.title ?? "Error!", message: - typeof errorInfo?.message === "string" ? errorInfo.message : "Something went wrong. Please try again 2.", + typeof errorInfo?.message === "string" ? errorInfo.message : t("something_went_wrong_please_try_again"), }); } }; @@ -109,17 +112,17 @@ const SecurityPage = observer(() => { <> - +
-

Current password

+

{t("current_password")}

( { type={showPassword?.oldPassword ? "text" : "password"} value={value} onChange={onChange} - placeholder="Old password" + placeholder={t("old_password")} className="w-full" hasError={Boolean(errors.old_password)} /> @@ -148,20 +151,20 @@ const SecurityPage = observer(() => { {errors.old_password && {errors.old_password.message}}
-

New password

+

{t("new_password")}

( {
{passwordSupport} {isNewPasswordSameAsOldPassword && !isPasswordInputFocused && ( - New password must be different from old password + {t("new_password_must_be_different_from_old_password")} )}
-

Confirm password

+

{t("confirm_password")}

( { )}
{!!confirmPassword && password !== confirmPassword && renderPasswordMatchError && ( - Passwords don{"'"}t match + {t("passwords_dont_match")} )}
@@ -239,4 +242,4 @@ const SecurityPage = observer(() => { ); }); -export default SecurityPage; +export default SecurityPage; \ No newline at end of file diff --git a/web/app/profile/sidebar.tsx b/web/app/profile/sidebar.tsx index aec3e24dcaa..146a76c585d 100644 --- a/web/app/profile/sidebar.tsx +++ b/web/app/profile/sidebar.tsx @@ -6,9 +6,9 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; // icons import { ChevronLeft, LogOut, MoveLeft, Plus, UserPlus } from "lucide-react"; -// plane helpers +// plane imports import { useOutsideClickDetector } from "@plane/helpers"; -// ui +import { useTranslation } from "@plane/i18n"; import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; // components import { SidebarNavItem } from "@/components/sidebar"; @@ -22,12 +22,12 @@ import { useAppTheme, useUser, useUserSettings, useWorkspace } from "@/hooks/sto import { usePlatformOS } from "@/hooks/use-platform-os"; const WORKSPACE_ACTION_LINKS = [ - { - key: "create-workspace", - Icon: Plus, - label: "Create workspace", - href: "/create-workspace", - }, + // { + // key: "create_workspace", + // Icon: Plus, + // label: "Create workspace", + // href: "/create-workspace", + // }, { key: "invitations", Icon: UserPlus, @@ -47,6 +47,7 @@ export const ProfileLayoutSidebar = observer(() => { const { data: currentUserSettings } = useUserSettings(); const { workspaces } = useWorkspace(); const { isMobile } = usePlatformOS(); + const { t } = useTranslation(); const workspacesList = Object.values(workspaces ?? {}); @@ -91,8 +92,8 @@ export const ProfileLayoutSidebar = observer(() => { .catch(() => setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Failed to sign out. Please try again.", + title: t("error"), + message: t("failed_to_sign_out_please_try_again"), }) ) .finally(() => setIsSigningOut(false)); @@ -117,13 +118,13 @@ export const ProfileLayoutSidebar = observer(() => { {!sidebarCollapsed && ( -

Profile settings

+

{t("profile_settings")}

)}
{!sidebarCollapsed && ( -
Your account
+
{t("your_account")}
)}
{PROFILE_ACTION_LINKS.map((link) => { @@ -132,7 +133,7 @@ export const ProfileLayoutSidebar = observer(() => { return ( { >
- {!sidebarCollapsed &&

{link.label}

} + {!sidebarCollapsed &&

{t(link.key)}

}
@@ -156,7 +157,7 @@ export const ProfileLayoutSidebar = observer(() => {
{!sidebarCollapsed && ( -
Workspaces
+
{t("workspaces")}
)} {workspacesList && workspacesList.length > 0 && (
{ {WORKSPACE_ACTION_LINKS.map((link) => ( { }`} > {} - {!sidebarCollapsed && link.label} + {!sidebarCollapsed && t(link.key)}
@@ -238,7 +239,7 @@ export const ProfileLayoutSidebar = observer(() => { disabled={isSigningOut} > - {!sidebarCollapsed && {isSigningOut ? "Signing out..." : "Sign out"}} + {!sidebarCollapsed && {isSigningOut ? `${t("signing_out")}...` : t("sign_out")}}
); -}); +}); \ No newline at end of file diff --git a/web/app/provider.tsx b/web/app/provider.tsx index dba975a6373..0513e9a6aec 100644 --- a/web/app/provider.tsx +++ b/web/app/provider.tsx @@ -1,15 +1,18 @@ "use client"; -import { FC, ReactNode } from "react"; +import { FC, ReactNode, useEffect } from "react"; import dynamic from "next/dynamic"; +import { usePathname } from "next/navigation";//ui import { useTheme, ThemeProvider } from "next-themes"; import { SWRConfig } from "swr"; -// ui -import { Toast } from "@plane/ui"; +import { TranslationProvider } from "@plane/i18n"; +import { Toast, setToast, TOAST_TYPE } from "@plane/ui"; +// Plane Imports // constants import { SWR_CONFIG } from "@/constants/swr-config"; //helpers import { resolveGeneralTheme } from "@/helpers/theme.helper"; +import { useUser } from "@/hooks/store"; // nprogress import { AppProgressBar } from "@/lib/n-progress"; // polyfills @@ -35,23 +38,60 @@ const ToastWithTheme = () => { export const AppProvider: FC = (props) => { const { children } = props; // themes + + const pathname = usePathname(); + const { signOut } = useUser(); + + const handleSignOut = async () => { + await signOut().catch(() => + setToast({ + type: TOAST_TYPE.ERROR, + title: "Error!", + message: "Failed to sign out. Please try again.", + }) + ); + }; + + useEffect(() => { + console.log(pathname, 'pathname'); + window.parent.postMessage({ type: "ROUTE_CHANGE", path: pathname }, "*"); + }, [pathname]); + + useEffect(() => { + const onIncomingMessage = async (event: MessageEvent) => { + console.log('onIncomingMessage', event); + if (event.data.type === "PLANE_SIGN_OUT") { + try { + await handleSignOut(); + window.parent.postMessage({ type: "PLANE_SIGN_OUT_SUCCESS", path: pathname }, "*"); + } catch (err) {} + } + }; + window.addEventListener('message', onIncomingMessage); + return () => { + window.removeEventListener("message", onIncomingMessage); + }; + }, []); + return ( <> - - - - - {children} - - - - + + + + + + {children} + + + + + ); -}; +}; \ No newline at end of file diff --git a/web/app/workspace-invitations/page.tsx b/web/app/workspace-invitations/page.tsx index a6829019892..a715a2f469f 100644 --- a/web/app/workspace-invitations/page.tsx +++ b/web/app/workspace-invitations/page.tsx @@ -4,7 +4,7 @@ import React from "react"; import { observer } from "mobx-react"; import { useSearchParams } from "next/navigation"; import useSWR from "swr"; -import { Boxes, Check, Share2, Star, User2, X } from "lucide-react"; +import { Boxes, Check, Share2, User2, X } from "lucide-react"; // components import { LogoSpinner } from "@/components/common"; import { EmptySpace, EmptySpaceItem } from "@/components/ui/empty-space"; @@ -107,7 +107,6 @@ const WorkspaceInvitationPage = observer(() => { ) : ( )} - { + const { t } = useTranslation(); // store hooks const { userProfile: { data: userProfile }, @@ -33,10 +35,8 @@ export const WorkspaceActiveCyclesUpgrade = observer(() => { >
-

On-demand snapshots of all your cycles

-

- Monitor cycles across projects, track high-priority issues, and zoom in cycles that need attention. -

+

{t("on_demand_snapshots_of_all_your_cycles")}

+

{t("active_cycles_description")}

@@ -81,14 +81,14 @@ export const WorkspaceActiveCyclesUpgrade = observer(() => {
{WORKSPACE_ACTIVE_CYCLES_DETAILS.map((item) => (
-
-

{item.title}

- +
+

{t(item.key)}

+
- {item.description} + {t(`${item.key}_description`)}
))}
); -}); +}); \ No newline at end of file diff --git a/web/ce/components/global/product-updates-header.tsx b/web/ce/components/global/product-updates-header.tsx index a5965bb2d14..0ff31a92069 100644 --- a/web/ce/components/global/product-updates-header.tsx +++ b/web/ce/components/global/product-updates-header.tsx @@ -1,5 +1,6 @@ import { observer } from "mobx-react"; import Image from "next/image"; +import { useTranslation } from "@plane/i18n"; // helpers import { cn } from "@/helpers/common.helper"; // assets @@ -7,20 +8,23 @@ import PlaneLogo from "@/public/plane-logos/blue-without-text.png"; // package.json import packageJson from "package.json"; -export const ProductUpdatesHeader = observer(() => ( -
-
-
What's new
+export const ProductUpdatesHeader = observer(() => { + const { t } = useTranslation(); + return ( +
+
+
{t("whats_new")}
- Version: v{packageJson.version} + className={cn( + "px-2 mx-2 py-0.5 text-center text-xs font-medium rounded-full bg-custom-primary-100/20 text-custom-primary-100" + )} + > + {t("version")}: v{packageJson.version} +
+
+
+ Plane
-
- Plane -
-
-)); + ); +}); \ No newline at end of file diff --git a/web/ce/components/global/version-number.tsx b/web/ce/components/global/version-number.tsx index 47ff380d371..2ce0a72649c 100644 --- a/web/ce/components/global/version-number.tsx +++ b/web/ce/components/global/version-number.tsx @@ -1,4 +1,8 @@ // assets +import { useTranslation } from "@plane/i18n"; import packageJson from "package.json"; -export const PlaneVersionNumber: React.FC = () => Version: v{packageJson.version}; +export const PlaneVersionNumber: React.FC = () => { + const { t } = useTranslation(); + return {t("version")}: v{packageJson.version}; +}; \ No newline at end of file diff --git a/web/ce/components/issues/worklog/activity/filter-root.tsx b/web/ce/components/issues/worklog/activity/filter-root.tsx index 2d11ae34148..0d4f5512799 100644 --- a/web/ce/components/issues/worklog/activity/filter-root.tsx +++ b/web/ce/components/issues/worklog/activity/filter-root.tsx @@ -1,6 +1,7 @@ "use client"; import { FC } from "react"; +import { useTranslation } from "@plane/i18n"; // components import { ActivityFilter } from "@/components/issues"; // plane web constants @@ -15,12 +16,13 @@ export type TActivityFilterRoot = { export const ActivityFilterRoot: FC = (props) => { const { selectedFilters, toggleFilter } = props; + const { t } = useTranslation(); const filters: TActivityFilterOption[] = Object.entries(ACTIVITY_FILTER_TYPE_OPTIONS).map(([key, value]) => { const filterKey = key as TActivityFilters; return { key: filterKey, - label: value.label, + label: t(value.label), isSelected: selectedFilters.includes(filterKey), onClick: () => toggleFilter(filterKey), }; diff --git a/web/ce/components/projects/create/attributes.tsx b/web/ce/components/projects/create/attributes.tsx index 178275641c4..ccbd2e09ebd 100644 --- a/web/ce/components/projects/create/attributes.tsx +++ b/web/ce/components/projects/create/attributes.tsx @@ -1,6 +1,7 @@ "use client"; import { FC } from "react"; import { Controller, useFormContext } from "react-hook-form"; +import { useTranslation } from "@plane/i18n"; import { IProject } from "@plane/types"; // ui import { CustomSelect } from "@plane/ui"; @@ -18,6 +19,7 @@ type Props = { const ProjectAttributes: FC = (props) => { const { isMobile = false } = props; + const { t } = useTranslation(); const { control } = useFormContext(); const { getIndex } = getTabIndex(ETabIndices.PROJECT_CREATE, isMobile); return ( @@ -41,7 +43,7 @@ const ProjectAttributes: FC = (props) => { {currentNetwork.label} ) : ( - Select network + {t("select_network")} )}
} @@ -77,7 +79,7 @@ const ProjectAttributes: FC = (props) => { onChange(lead === value ? null : lead)} - placeholder="Lead" + placeholder={t("lead")} multiple={false} buttonVariant="border-with-text" tabIndex={5} @@ -91,4 +93,4 @@ const ProjectAttributes: FC = (props) => { ); }; -export default ProjectAttributes; +export default ProjectAttributes; \ No newline at end of file diff --git a/web/ce/components/projects/create/root.tsx b/web/ce/components/projects/create/root.tsx index 04c6dfc882f..5a7a1361323 100644 --- a/web/ce/components/projects/create/root.tsx +++ b/web/ce/components/projects/create/root.tsx @@ -3,6 +3,7 @@ import { useState, FC } from "react"; import { observer } from "mobx-react"; import { FormProvider, useForm } from "react-hook-form"; +import { useTranslation } from "@plane/i18n"; // ui import { setToast, TOAST_TYPE } from "@plane/ui"; // constants @@ -47,6 +48,7 @@ const defaultValues: Partial = { export const CreateProjectForm: FC = observer((props) => { const { setToFavorite, workspaceSlug, onClose, handleNextStep, updateCoverImageStatus } = props; // store + const { t } = useTranslation(); const { captureProjectEvent } = useEventTracker(); const { addProjectToFavorites, createProject } = useProject(); // states @@ -64,8 +66,8 @@ export const CreateProjectForm: FC = observer((props) = addProjectToFavorites(workspaceSlug.toString(), projectId).catch(() => { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Couldn't remove the project from favorites. Please try again.", + title: t("error"), + message: t("failed_to_remove_project_from_favorites"), }); }); }; @@ -90,8 +92,8 @@ export const CreateProjectForm: FC = observer((props) = }); setToast({ type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Project created successfully.", + title: t("success"), + message: t("project_created_successfully"), }); if (setToFavorite) { handleAddToFavorites(res.id); @@ -102,7 +104,7 @@ export const CreateProjectForm: FC = observer((props) = Object.keys(err.data).map((key) => { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", + title: t("error"), message: err.data[key], }); captureProjectEvent({ @@ -142,4 +144,4 @@ export const CreateProjectForm: FC = observer((props) = ); -}); +}); \ No newline at end of file diff --git a/web/ce/components/projects/mobile-header.tsx b/web/ce/components/projects/mobile-header.tsx index 3804721600e..f07edbd3af3 100644 --- a/web/ce/components/projects/mobile-header.tsx +++ b/web/ce/components/projects/mobile-header.tsx @@ -4,6 +4,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // icons import { ChevronDown, ListFilter } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; // types import { TProjectFilters } from "@plane/types"; // hooks @@ -15,6 +16,7 @@ import { calculateTotalFilters } from "@/helpers/filter.helper"; import { useMember, useProjectFilter } from "@/hooks/store"; export const ProjectsListMobileHeader = observer(() => { + const { t } = useTranslation(); // router const { workspaceSlug } = useParams(); const { @@ -68,7 +70,7 @@ export const ProjectsListMobileHeader = observer(() => { menuButton={
- Filters + {t("filters")}
} diff --git a/web/ce/components/projects/settings/intake/header.tsx b/web/ce/components/projects/settings/intake/header.tsx index e733317d2b3..bd48c80ba9f 100644 --- a/web/ce/components/projects/settings/intake/header.tsx +++ b/web/ce/components/projects/settings/intake/header.tsx @@ -4,6 +4,7 @@ import { FC, useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { RefreshCcw } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; // ui import { Breadcrumbs, Button, Intake, Header } from "@plane/ui"; // components @@ -14,6 +15,7 @@ import { useProject, useProjectInbox, useUserPermissions } from "@/hooks/store"; import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; export const ProjectInboxHeader: FC = observer(() => { + const { t } = useTranslation(); // states const [createIssueModal, setCreateIssueModal] = useState(false); // router @@ -77,7 +79,7 @@ export const ProjectInboxHeader: FC = observer(() => { />
) : ( diff --git a/web/ce/components/workspace/edition-badge.tsx b/web/ce/components/workspace/edition-badge.tsx index e1c3d1c1ddb..729ec6d236e 100644 --- a/web/ce/components/workspace/edition-badge.tsx +++ b/web/ce/components/workspace/edition-badge.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; // ui import { Button, Tooltip } from "@plane/ui"; // hooks @@ -11,6 +12,7 @@ import { PaidPlanUpgradeModal } from "./upgrade"; export const WorkspaceEditionBadge = observer(() => { const { isMobile } = usePlatformOS(); + const { t } = useTranslation(); // states const [isPaidPlanPurchaseModalOpen, setIsPaidPlanPurchaseModalOpen] = useState(false); @@ -27,9 +29,9 @@ export const WorkspaceEditionBadge = observer(() => { className="w-fit min-w-24 cursor-pointer rounded-2xl px-2 py-1 text-center text-sm font-medium outline-none" onClick={() => setIsPaidPlanPurchaseModalOpen(true)} > - Upgrade + {t("upgrade")} ); -}); +}); \ No newline at end of file diff --git a/web/ce/constants/issues.ts b/web/ce/constants/issues.ts index d30ec492e56..19728231294 100644 --- a/web/ce/constants/issues.ts +++ b/web/ce/constants/issues.ts @@ -9,10 +9,10 @@ export type TActivityFilters = EActivityFilterType; export const ACTIVITY_FILTER_TYPE_OPTIONS: Record = { [EActivityFilterType.ACTIVITY]: { - label: "Updates", + label: "updates", }, [EActivityFilterType.COMMENT]: { - label: "Comments", + label: "comments", }, }; diff --git a/web/ce/constants/project/settings/features.tsx b/web/ce/constants/project/settings/features.tsx index b9b39700d68..52fd81590e1 100644 --- a/web/ce/constants/project/settings/features.tsx +++ b/web/ce/constants/project/settings/features.tsx @@ -4,6 +4,7 @@ import { IProject } from "@plane/types"; import { ContrastIcon, DiceIcon, Intake } from "@plane/ui"; export type TProperties = { + key: string; property: string; title: string; description: string; @@ -22,6 +23,7 @@ export type TFeatureList = { export type TProjectFeatures = { [key: string]: { + key: string; title: string; description: string; featureList: TFeatureList; @@ -30,10 +32,12 @@ export type TProjectFeatures = { export const PROJECT_FEATURES_LIST: TProjectFeatures = { project_features: { + key: "projects_and_issues", title: "Projects and issues", description: "Toggle these on or off this project.", featureList: { cycles: { + key: "cycles", property: "cycle_view", title: "Cycles", description: "Timebox work as you see fit per project and change frequency from one period to the next.", @@ -42,6 +46,7 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { isEnabled: true, }, modules: { + key: "modules", property: "module_view", title: "Modules", description: "Group work into sub-project-like set-ups with their own leads and assignees.", @@ -50,6 +55,7 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { isEnabled: true, }, views: { + key: "views", property: "issue_views_view", title: "Views", description: "Save sorts, filters, and display options for later or share them.", @@ -58,6 +64,7 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { isEnabled: true, }, pages: { + key: "pages", property: "page_view", title: "Pages", description: "Write anything like you write anything.", @@ -66,6 +73,7 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { isEnabled: true, }, inbox: { + key: "intake", property: "inbox_view", title: "Intake", description: "Consider and discuss issues before you add them to your project.", @@ -76,17 +84,19 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { }, }, project_others: { + key: "work_management", title: "Work management", description: "Available only on some plans as indicated by the label next to the feature below.", featureList: { is_time_tracking_enabled: { + key: "time_tracking", property: "is_time_tracking_enabled", title: "Time Tracking", description: "Log time, see timesheets, and download full CSVs for your entire workspace.", icon: , - isPro: true, - isEnabled: false, + isPro: false, + isEnabled: true, }, }, }, -}; +}; \ No newline at end of file diff --git a/web/core/components/account/deactivate-account-modal.tsx b/web/core/components/account/deactivate-account-modal.tsx index aa27af4664c..a93cdf52e89 100644 --- a/web/core/components/account/deactivate-account-modal.tsx +++ b/web/core/components/account/deactivate-account-modal.tsx @@ -3,6 +3,7 @@ import React, { useState } from "react"; import { Trash2 } from "lucide-react"; import { Dialog, Transition } from "@headlessui/react"; +import { useTranslation } from "@plane/i18n"; // ui import { Button, TOAST_TYPE, setToast } from "@plane/ui"; // hooks @@ -18,6 +19,7 @@ export const DeactivateAccountModal: React.FC = (props) => { const router = useAppRouter(); const { isOpen, onClose } = props; // hooks + const { t } = useTranslation(); const { deactivateAccount, signOut } = useUser(); // states @@ -90,11 +92,10 @@ export const DeactivateAccountModal: React.FC = (props) => {
- Deactivate your account? + {t("deactivate_your_account")}

- Once deactivated, you can{"'"}t be assigned issues and be billed for your workspace.To - reactivate your account, you will need an invite to a workspace at this email address. + {t("deactivate_your_account_description")}

@@ -102,10 +103,10 @@ export const DeactivateAccountModal: React.FC = (props) => {
@@ -115,4 +116,4 @@ export const DeactivateAccountModal: React.FC = (props) => { ); -}; +}; \ No newline at end of file diff --git a/web/core/components/account/password-strength-meter.tsx b/web/core/components/account/password-strength-meter.tsx index 342f77efb70..f0d1c59d988 100644 --- a/web/core/components/account/password-strength-meter.tsx +++ b/web/core/components/account/password-strength-meter.tsx @@ -1,6 +1,7 @@ "use client"; import { FC, useMemo } from "react"; +import { useTranslation } from "@plane/i18n"; // import { CircleCheck } from "lucide-react"; // helpers import { cn } from "@/helpers/common.helper"; @@ -17,6 +18,7 @@ type TPasswordStrengthMeter = { export const PasswordStrengthMeter: FC = (props) => { const { password, isFocused = false } = props; + const { t } = useTranslation(); // derived values const strength = useMemo(() => getPasswordStrength(password), [password]); const strengthBars = useMemo(() => { @@ -24,40 +26,40 @@ export const PasswordStrengthMeter: FC = (props) => { case E_PASSWORD_STRENGTH.EMPTY: { return { bars: [`bg-custom-text-100`, `bg-custom-text-100`, `bg-custom-text-100`], - text: "Please enter your password.", + text: t("please_enter_your_password"), textColor: "text-custom-text-100", }; } case E_PASSWORD_STRENGTH.LENGTH_NOT_VALID: { return { bars: [`bg-red-500`, `bg-custom-text-100`, `bg-custom-text-100`], - text: "Password length should me more than 8 characters.", + text: t("password_length_should_me_more_than_8_characters"), textColor: "text-red-500", }; } case E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID: { return { bars: [`bg-red-500`, `bg-custom-text-100`, `bg-custom-text-100`], - text: "Password is weak.", + text: t("password_is_weak"), textColor: "text-red-500", }; } case E_PASSWORD_STRENGTH.STRENGTH_VALID: { return { bars: [`bg-green-500`, `bg-green-500`, `bg-green-500`], - text: "Password is strong.", + text: t("password_is_strong"), textColor: "text-green-500", }; } default: { return { bars: [`bg-custom-text-100`, `bg-custom-text-100`, `bg-custom-text-100`], - text: "Please enter your password.", + text: t("please_enter_your_password"), textColor: "text-custom-text-100", }; } } - }, [strength]); + }, [strength,t]); const isPasswordMeterVisible = isFocused ? true : strength === E_PASSWORD_STRENGTH.STRENGTH_VALID ? false : true; @@ -91,4 +93,4 @@ export const PasswordStrengthMeter: FC = (props) => {
*/}
); -}; +}; \ No newline at end of file diff --git a/web/core/components/analytics/project-modal/header.tsx b/web/core/components/analytics/project-modal/header.tsx index 79d63ce3c0f..2d19863f685 100644 --- a/web/core/components/analytics/project-modal/header.tsx +++ b/web/core/components/analytics/project-modal/header.tsx @@ -1,4 +1,5 @@ import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; // icons import { Expand, Shrink, X } from "lucide-react"; @@ -12,10 +13,11 @@ type Props = { export const ProjectAnalyticsModalHeader: React.FC = observer((props) => { const { fullScreen, handleClose, setFullScreen, title } = props; + const { t } = useTranslation(); return (
-

Analytics for {title}

+

{t("Analytics for")} {title}

); -}); +}); \ No newline at end of file diff --git a/web/core/components/core/theme/theme-switch.tsx b/web/core/components/core/theme/theme-switch.tsx index b79e2104eb2..6db67ef05c4 100644 --- a/web/core/components/core/theme/theme-switch.tsx +++ b/web/core/components/core/theme/theme-switch.tsx @@ -1,6 +1,7 @@ "use client"; import { FC } from "react"; +import { useTranslation } from "@plane/i18n"; // constants import { CustomSelect } from "@plane/ui"; import { THEME_OPTIONS, I_THEME_OPTION } from "@/constants/themes"; @@ -13,7 +14,7 @@ type Props = { export const ThemeSwitch: FC = (props) => { const { value, onChange } = props; - + const { t } = useTranslation(); return ( = (props) => { }} />
- {value.label} + {t(value.key)}
) : ( - "Select your theme" + t("select_your_theme") ) } onChange={onChange} @@ -72,10 +73,10 @@ export const ThemeSwitch: FC = (props) => { }} />
- {themeOption.label} + {t(themeOption.key)}
))} ); -}; +}; \ No newline at end of file diff --git a/web/core/components/cycles/applied-filters/date.tsx b/web/core/components/cycles/applied-filters/date.tsx index 488eef12ce9..4e9121925f1 100644 --- a/web/core/components/cycles/applied-filters/date.tsx +++ b/web/core/components/cycles/applied-filters/date.tsx @@ -4,6 +4,7 @@ import { X } from "lucide-react"; import { DATE_AFTER_FILTER_OPTIONS } from "@/constants/filters"; import { renderFormattedDate } from "@/helpers/date-time.helper"; import { capitalizeFirstLetter } from "@/helpers/string.helper"; +import { useTranslation } from "@plane/i18n"; // constants type Props = { @@ -14,13 +15,14 @@ type Props = { export const AppliedDateFilters: React.FC = observer((props) => { const { editable, handleRemove, values } = props; + const { t } = useTranslation(); const getDateLabel = (value: string): string => { let dateLabel = ""; const dateDetails = DATE_AFTER_FILTER_OPTIONS.find((d) => d.value === value); - if (dateDetails) dateLabel = dateDetails.name; + if (dateDetails) dateLabel = t(dateDetails.name); else { const dateParts = value.split(";"); diff --git a/web/core/components/cycles/dropdowns/filters/end-date.tsx b/web/core/components/cycles/dropdowns/filters/end-date.tsx index e5b4a7a8639..40900846d74 100644 --- a/web/core/components/cycles/dropdowns/filters/end-date.tsx +++ b/web/core/components/cycles/dropdowns/filters/end-date.tsx @@ -8,6 +8,7 @@ import { FilterHeader, FilterOption } from "@/components/issues"; import { DATE_AFTER_FILTER_OPTIONS } from "@/constants/filters"; // helpers import { isInDateFormat } from "@/helpers/date-time.helper"; +import { useTranslation } from "@plane/i18n"; type Props = { appliedFilters: string[] | null; @@ -17,6 +18,7 @@ type Props = { export const FilterEndDate: React.FC = observer((props) => { const { appliedFilters, handleUpdate, searchQuery } = props; + const { t } = useTranslation(); const [previewEnabled, setPreviewEnabled] = useState(true); const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false); @@ -62,7 +64,7 @@ export const FilterEndDate: React.FC = observer((props) => { key={option.value} isChecked={appliedFilters?.includes(option.value) ? true : false} onClick={() => handleUpdate(option.value)} - title={option.name} + title={t(option.name)} multiple /> ))} diff --git a/web/core/components/cycles/dropdowns/filters/start-date.tsx b/web/core/components/cycles/dropdowns/filters/start-date.tsx index 9bfd8f2d8e2..466563976d3 100644 --- a/web/core/components/cycles/dropdowns/filters/start-date.tsx +++ b/web/core/components/cycles/dropdowns/filters/start-date.tsx @@ -8,6 +8,7 @@ import { FilterHeader, FilterOption } from "@/components/issues"; import { DATE_AFTER_FILTER_OPTIONS } from "@/constants/filters"; // helpers import { isInDateFormat } from "@/helpers/date-time.helper"; +import { useTranslation } from "@plane/i18n"; type Props = { appliedFilters: string[] | null; @@ -17,6 +18,7 @@ type Props = { export const FilterStartDate: React.FC = observer((props) => { const { appliedFilters, handleUpdate, searchQuery } = props; + const { t } = useTranslation(); const [previewEnabled, setPreviewEnabled] = useState(true); const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false); @@ -62,7 +64,7 @@ export const FilterStartDate: React.FC = observer((props) => { key={option.value} isChecked={appliedFilters?.includes(option.value) ? true : false} onClick={() => handleUpdate(option.value)} - title={option.name} + title={t(option.name)} multiple /> ))} diff --git a/web/core/components/dashboard/home-dashboard-widgets.tsx b/web/core/components/dashboard/home-dashboard-widgets.tsx index fe64278dc99..552244d9ee2 100644 --- a/web/core/components/dashboard/home-dashboard-widgets.tsx +++ b/web/core/components/dashboard/home-dashboard-widgets.tsx @@ -47,6 +47,8 @@ export const DashboardWidgets = observer(() => { const WidgetComponent = widget.component; // if the widget doesn't exist, return null if (!doesWidgetExist(key as TWidgetKeys)) return null; + // Disable recent_collaborators widget. Currently query to fetch this is not optimised + if (key === "recent_collaborators") return null; // if the widget is full width, return it in a 2 column grid if (widget.fullWidth) return ( diff --git a/web/core/components/dashboard/widgets/assigned-issues.tsx b/web/core/components/dashboard/widgets/assigned-issues.tsx index 30bfbdad1b4..41ec71a948b 100644 --- a/web/core/components/dashboard/widgets/assigned-issues.tsx +++ b/web/core/components/dashboard/widgets/assigned-issues.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import { Tab } from "@headlessui/react"; import { TAssignedIssuesWidgetFilters, TAssignedIssuesWidgetResponse } from "@plane/types"; // hooks +import { useTranslation } from "@plane/i18n"; import { Card } from "@plane/ui"; import { DurationFilterDropdown, @@ -25,6 +26,7 @@ const WIDGET_KEY = "assigned_issues"; export const AssignedIssuesWidget: React.FC = observer((props) => { const { dashboardId, workspaceSlug } = props; + const { t } = useTranslation(); // states const [fetching, setFetching] = useState(false); // store hooks @@ -99,7 +101,7 @@ export const AssignedIssuesWidget: React.FC = observer((props) => { href={`/${workspaceSlug}/workspace-views/assigned/${filterParams}`} className="text-lg font-semibold text-custom-text-300 hover:underline" > - Assigned to you + {t("assigned_to_you")} = observer((props) => { const { dashboardId, workspaceSlug } = props; + const { t } = useTranslation(); // states const [fetching, setFetching] = useState(false); // store hooks @@ -96,7 +98,7 @@ export const CreatedIssuesWidget: React.FC = observer((props) => { href={`/${workspaceSlug}/workspace-views/created/${filterParams}`} className="text-lg font-semibold text-custom-text-300 hover:underline" > - Created by you + {t("created_by_you")} = (props) => { const { isLoading, tab, type, widgetStats, workspaceSlug } = props; + const { t } = useTranslation(); // hooks const { isMobile } = usePlatformOS(); const { handleRedirection } = useIssuePeekOverviewRedirection(); @@ -83,16 +85,16 @@ export const WidgetIssuesList: React.FC = (props) => { "col-span-9": type === "created" && tab === "completed", })} > - Issues + {t("issues")} {widgetStats.count} -
Priority
- {["upcoming", "pending"].includes(tab) &&
Due date
} - {tab === "overdue" &&
Due by
} - {type === "assigned" && tab !== "completed" &&
Blocked by
} - {type === "created" &&
Assigned to
} +
{t("priority")}
+ {["upcoming", "pending"].includes(tab) &&
{t("due_date")}
} + {tab === "overdue" &&
{t("due_by")}
} + {type === "assigned" && tab !== "completed" &&
{t("blocked_by")}
} + {type === "created" &&
{t("assigned_to")}
}
{issuesList.map((issue) => { @@ -126,7 +128,7 @@ export const WidgetIssuesList: React.FC = (props) => { "w-min my-3 mx-auto py-1 px-2 text-xs hover:bg-custom-primary-100/20" )} > - View all issues + {t("view_all_issues")} )} diff --git a/web/core/components/dashboard/widgets/issue-panels/tabs-list.tsx b/web/core/components/dashboard/widgets/issue-panels/tabs-list.tsx index d2b7df8ed0f..f40e0bfa72f 100644 --- a/web/core/components/dashboard/widgets/issue-panels/tabs-list.tsx +++ b/web/core/components/dashboard/widgets/issue-panels/tabs-list.tsx @@ -6,6 +6,7 @@ import { EDurationFilters, FILTERED_ISSUES_TABS_LIST, UNFILTERED_ISSUES_TABS_LIS import { cn } from "@/helpers/common.helper"; // types // constants +import { useTranslation } from "@plane/i18n"; type Props = { durationFilter: EDurationFilters; @@ -13,6 +14,7 @@ type Props = { }; export const TabsList: React.FC = observer((props) => { + const { t } = useTranslation(); const { durationFilter, selectedTab } = props; const tabsList = durationFilter === "none" ? UNFILTERED_ISSUES_TABS_LIST : FILTERED_ISSUES_TABS_LIST; @@ -53,7 +55,7 @@ export const TabsList: React.FC = observer((props) => { } )} > - {tab.label} + {t(tab.key)} ))} diff --git a/web/core/components/dashboard/widgets/issues-by-priority.tsx b/web/core/components/dashboard/widgets/issues-by-priority.tsx index 9da47bc8481..47fbaefeb70 100644 --- a/web/core/components/dashboard/widgets/issues-by-priority.tsx +++ b/web/core/components/dashboard/widgets/issues-by-priority.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react"; import Link from "next/link"; // types import { TIssuesByPriorityWidgetFilters, TIssuesByPriorityWidgetResponse } from "@plane/types"; +import { useTranslation } from "@plane/i18n"; // components import { Card } from "@plane/ui"; import { @@ -24,6 +25,7 @@ const WIDGET_KEY = "issues_by_priority"; export const IssuesByPriorityWidget: React.FC = observer((props) => { const { dashboardId, workspaceSlug } = props; + const { t } = useTranslation(); // router const router = useAppRouter(); // store hooks @@ -76,7 +78,7 @@ export const IssuesByPriorityWidget: React.FC = observer((props) => href={`/${workspaceSlug}/workspace-views/assigned`} className="text-lg font-semibold text-custom-text-300 hover:underline" > - Assigned by priority + {t("assigned_by_priority")} = observer((props) => { const { dashboardId, workspaceSlug } = props; + const { t } = useTranslation(); // states const [defaultStateGroup, setDefaultStateGroup] = useState(null); const [activeStateGroup, setActiveStateGroup] = useState(null); @@ -128,7 +130,7 @@ export const IssuesByStateGroupWidget: React.FC = observer((props) dominantBaseline="central" className="text-sm font-medium fill-custom-text-300 capitalize" > - {data?.id} + {data.id ? t(data.id) : ""} ); @@ -141,7 +143,7 @@ export const IssuesByStateGroupWidget: React.FC = observer((props) href={`/${workspaceSlug}/workspace-views/assigned`} className="text-lg font-semibold text-custom-text-300 hover:underline" > - Assigned by state + {t("assigned_by_state")} = observer((props) backgroundColor: item.color, }} /> - {item.label} + {t(item.label)}
{item.value.toFixed(0)}% diff --git a/web/core/components/dashboard/widgets/overview-stats.tsx b/web/core/components/dashboard/widgets/overview-stats.tsx index 62200102777..7c7e8d17329 100644 --- a/web/core/components/dashboard/widgets/overview-stats.tsx +++ b/web/core/components/dashboard/widgets/overview-stats.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react"; import Link from "next/link"; import { TOverviewStatsWidgetResponse } from "@plane/types"; // hooks +import { useTranslation } from "@plane/i18n"; import { Card, ECardSpacing } from "@plane/ui"; import { WidgetLoader } from "@/components/dashboard/widgets"; import { cn } from "@/helpers/common.helper"; @@ -21,6 +22,7 @@ const WIDGET_KEY = "overview_stats"; export const OverviewStatsWidget: React.FC = observer((props) => { const { dashboardId, workspaceSlug } = props; + const { t } = useTranslation(); // store hooks const { fetchWidgetStats, getWidgetStats } = useDashboard(); // derived values @@ -30,27 +32,27 @@ export const OverviewStatsWidget: React.FC = observer((props) => { const STATS_LIST = [ { key: "assigned", - title: "Issues assigned", + title: t("issues_assigned"), count: widgetStats?.assigned_issues_count, link: `/${workspaceSlug}/workspace-views/assigned`, }, { key: "overdue", - title: "Issues overdue", + title: t("issues_overdue"), count: widgetStats?.pending_issues_count, link: `/${workspaceSlug}/workspace-views/assigned/?state_group=backlog,unstarted,started&target_date=${today};before`, }, { key: "created", - title: "Issues created", + title: t("issues_created"), count: widgetStats?.created_issues_count, link: `/${workspaceSlug}/workspace-views/created`, }, { key: "completed", - title: "Issues completed", + title: t("issues_completed"), count: widgetStats?.completed_issues_count, - link: `/${workspaceSlug}/workspace-views/assigned?state_group=completed`, + link: `/${workspaceSlug}/workspace-views/completed`, }, ]; diff --git a/web/core/components/dashboard/widgets/recent-activity.tsx b/web/core/components/dashboard/widgets/recent-activity.tsx index dd21815ccce..3e94702d575 100644 --- a/web/core/components/dashboard/widgets/recent-activity.tsx +++ b/web/core/components/dashboard/widgets/recent-activity.tsx @@ -7,6 +7,7 @@ import { History } from "lucide-react"; // types import { TRecentActivityWidgetResponse } from "@plane/types"; // components +import { useTranslation } from "@plane/i18n"; import { Card, Avatar, getButtonStyling } from "@plane/ui"; import { ActivityIcon, ActivityMessage, IssueLink } from "@/components/core"; import { RecentActivityEmptyState, WidgetLoader, WidgetProps } from "@/components/dashboard/widgets"; @@ -21,6 +22,7 @@ const WIDGET_KEY = "recent_activity"; export const RecentActivityWidget: React.FC = observer((props) => { const { dashboardId, workspaceSlug } = props; + const { t } = useTranslation(); // store hooks const { data: currentUser } = useUser(); // derived values @@ -40,7 +42,7 @@ export const RecentActivityWidget: React.FC = observer((props) => { return ( - Your issue activities + {t("your_issue_activities")} {widgetStats.length > 0 ? (
@@ -96,7 +98,7 @@ export const RecentActivityWidget: React.FC = observer((props) => { "mx-auto w-min px-2 py-1 text-xs hover:bg-custom-primary-100/20" )} > - View all + {t("view_all")}
) : ( diff --git a/web/core/components/dashboard/widgets/recent-collaborators/collaborators-list.tsx b/web/core/components/dashboard/widgets/recent-collaborators/collaborators-list.tsx index 2dc1f0d3d99..a2d613a04c4 100644 --- a/web/core/components/dashboard/widgets/recent-collaborators/collaborators-list.tsx +++ b/web/core/components/dashboard/widgets/recent-collaborators/collaborators-list.tsx @@ -7,6 +7,7 @@ import useSWR from "swr"; // types import { TRecentCollaboratorsWidgetResponse } from "@plane/types"; // ui +import { useTranslation } from "@plane/i18n"; import { Avatar } from "@plane/ui"; // helpers import { getFileURL } from "@/helpers/file.helper"; @@ -63,6 +64,7 @@ const WIDGET_KEY = "recent_collaborators"; export const CollaboratorsList: React.FC = (props) => { const { dashboardId, searchQuery = "", workspaceSlug } = props; + const { t } = useTranslation(); // state const [visibleItems, setVisibleItems] = useState(16); @@ -146,7 +148,7 @@ export const CollaboratorsList: React.FC = (props) => { )} {isExpanded && (
-
Hide
+
{t("hide")}
)} diff --git a/web/core/components/dashboard/widgets/recent-collaborators/root.tsx b/web/core/components/dashboard/widgets/recent-collaborators/root.tsx index 4d30642071b..572362da3c4 100644 --- a/web/core/components/dashboard/widgets/recent-collaborators/root.tsx +++ b/web/core/components/dashboard/widgets/recent-collaborators/root.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { Search } from "lucide-react"; // types +import { useTranslation } from "@plane/i18n"; import { Card } from "@plane/ui"; import { WidgetProps } from "@/components/dashboard/widgets"; // components @@ -8,6 +9,7 @@ import { CollaboratorsList } from "./collaborators-list"; export const RecentCollaboratorsWidget: React.FC = (props) => { const { dashboardId, workspaceSlug } = props; + const { t } = useTranslation(); // states const [searchQuery, setSearchQuery] = useState(""); @@ -15,7 +17,7 @@ export const RecentCollaboratorsWidget: React.FC = (props) => {
-

Collaborators

+

{t("collaborators")}

View and find all members you collaborate with across projects

@@ -24,7 +26,7 @@ export const RecentCollaboratorsWidget: React.FC = (props) => { setSearchQuery(e.target.value)} /> diff --git a/web/core/components/dashboard/widgets/recent-projects.tsx b/web/core/components/dashboard/widgets/recent-projects.tsx index 5255908717e..37a1f811b4c 100644 --- a/web/core/components/dashboard/widgets/recent-projects.tsx +++ b/web/core/components/dashboard/widgets/recent-projects.tsx @@ -3,10 +3,11 @@ import { useEffect } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; -import { Plus } from "lucide-react"; +// import { Plus } from "lucide-react"; // plane types import { TRecentProjectsWidgetResponse } from "@plane/types"; // plane ui +import { useTranslation } from "@plane/i18n"; import { Avatar, AvatarGroup, Card } from "@plane/ui"; // components import { Logo } from "@/components/common"; @@ -68,6 +69,7 @@ const ProjectListItem: React.FC = observer((props) => { export const RecentProjectsWidget: React.FC = observer((props) => { const { dashboardId, workspaceSlug } = props; + const { t } = useTranslation(); // store hooks const { toggleCreateProjectModal } = useCommandPalette(); const { setTrackElement } = useEventTracker(); @@ -95,10 +97,10 @@ export const RecentProjectsWidget: React.FC = observer((props) => { href={`/${workspaceSlug}/projects`} className="text-lg font-semibold text-custom-text-300 hover:underline mb-4" > - Recent projects + {t("recent_projects")}
- {canCreateProject && ( + {/* {canCreateProject && (

- Create new project + {t("create_new_project")}

- )} + )} */} {widgetStats.map((projectId) => ( ))} diff --git a/web/core/components/dropdowns/member/index.tsx b/web/core/components/dropdowns/member/index.tsx index f905bc27b2a..b0b3e802902 100644 --- a/web/core/components/dropdowns/member/index.tsx +++ b/web/core/components/dropdowns/member/index.tsx @@ -1,6 +1,7 @@ import { useRef, useState } from "react"; import { observer } from "mobx-react"; import { ChevronDown, LucideIcon } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; // ui import { ComboDropDown } from "@plane/ui"; // helpers @@ -26,6 +27,7 @@ type Props = { } & MemberDropdownProps; export const MemberDropdown: React.FC = observer((props) => { + const { t } = useTranslation(); const { button, buttonClassName, @@ -40,7 +42,7 @@ export const MemberDropdown: React.FC = observer((props) => { multiple, onChange, onClose, - placeholder = "Members", + placeholder = t("members"), tooltipContent, placement, projectId, @@ -86,7 +88,7 @@ export const MemberDropdown: React.FC = observer((props) => { if (value.length === 1) { return getUserDetails(value[0])?.display_name || placeholder; } else { - return showUserDetails ? `${value.length} members` : ""; + return showUserDetails ? `${value.length} ${t("members").toLocaleLowerCase()}` : ""; } } else { return placeholder; @@ -131,7 +133,9 @@ export const MemberDropdown: React.FC = observer((props) => { className={cn("text-xs", buttonClassName)} isActive={isOpen} tooltipHeading={placeholder} - tooltipContent={tooltipContent ?? `${value?.length ?? 0} assignee${value?.length !== 1 ? "s" : ""}`} + tooltipContent={ + tooltipContent ?? `${value?.length ?? 0} ${value?.length !== 1 ? t("assignees") : t("assignee")}` + } showTooltip={showTooltip} variant={buttonVariant} renderToolTipByDefault={renderByDefault} @@ -174,4 +178,4 @@ export const MemberDropdown: React.FC = observer((props) => { )} ); -}); +}); \ No newline at end of file diff --git a/web/core/components/dropdowns/member/member-options.tsx b/web/core/components/dropdowns/member/member-options.tsx index a2283fa4bc2..7b5b04fc4ca 100644 --- a/web/core/components/dropdowns/member/member-options.tsx +++ b/web/core/components/dropdowns/member/member-options.tsx @@ -8,6 +8,7 @@ import { createPortal } from "react-dom"; import { usePopper } from "react-popper"; import { Check, Search } from "lucide-react"; import { Combobox } from "@headlessui/react"; +import { useTranslation } from "@plane/i18n"; // plane ui import { Avatar } from "@plane/ui"; // helpers @@ -34,6 +35,7 @@ export const MemberOptions: React.FC = observer((props: Props) => { // refs const inputRef = useRef(null); // store hooks + const { t } = useTranslation(); const { workspaceSlug } = useParams(); const { getUserDetails, @@ -85,7 +87,7 @@ export const MemberOptions: React.FC = observer((props: Props) => { content: (
- {currentUser?.id === userId ? "You" : userDetails?.display_name} + {currentUser?.id === userId ? t("you") : userDetails?.display_name}
), }; @@ -115,7 +117,7 @@ export const MemberOptions: React.FC = 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("search")} displayValue={(assigned: any) => assigned?.name} onKeyDown={searchInputKeyDown} /> @@ -142,14 +144,14 @@ export const MemberOptions: React.FC = observer((props: Props) => { )) ) : ( -

No matching results

+

{t("no_matching_results")}

) ) : ( -

Loading...

+

{t("loading")}

)}
, document.body ); -}); +}); \ No newline at end of file diff --git a/web/core/components/dropdowns/priority.tsx b/web/core/components/dropdowns/priority.tsx index ee11679156c..621bad38b95 100644 --- a/web/core/components/dropdowns/priority.tsx +++ b/web/core/components/dropdowns/priority.tsx @@ -5,6 +5,7 @@ import { useTheme } from "next-themes"; import { usePopper } from "react-popper"; import { Check, ChevronDown, Search, SignalHigh } from "lucide-react"; import { Combobox } from "@headlessui/react"; +import { useTranslation } from "@plane/i18n"; // types import { TIssuePriorities } from "@plane/types"; // ui @@ -71,11 +72,12 @@ const BorderButton = (props: ButtonProps) => { }; const { isMobile } = usePlatformOS(); + const { t } = useTranslation(); return ( { ) : ( ))} - {!hideText && {priorityDetails?.title ?? placeholder}} + {!hideText && {t(priorityDetails?.key ?? "priority") ?? placeholder}} {dropdownArrow && (