[WEB-2870]feat: language support (#6215)

* fix: adding language support package

* fix: language support implementation using mobx

* fix: adding more languages for support

* fix: profile settings translations

* feat: added language support for sidebar and user settings

* feat: added language support for deactivation modal

* fix: added project sync after transfer issues (#6200)

* code refactor and improvement (#6203)

* chore: package code refactoring

* chore: component restructuring and refactor

* chore: comment create improvement

* refactor: enhance workspace and project wrapper modularity (#6207)

* [WEB-2678]feat: added functionality to add labels directly from dropdown (#6211)

* enhancement:added functionality to add features directly from dropdown

* fix: fixed import order

* fix: fixed lint errors

* chore: added common component for project activity (#6212)

* chore: added common component for project activity

* fix: added enum

* fix: added enum for initiatives

* - Do not clear temp files that are locked. (#6214)

- Handle edge cases in sync workspace

* fix: labels empty state for drop down (#6216)

* refactor: remove cn helper function from the editor package (#6217)

* * feat: added language support to issue create modal in sidebar
* fix: project activity type

* * fix: added missing translations
* fix: modified translation for plurals

* fix: fixed spanish translation

* dev: language type error in space user profile types

* fix: type fixes

* chore: added alpha tag

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: Akshita Goyal <36129505+gakshita@users.noreply.github.com>
Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: gurusinath <gurusainath007@gmail.com>
This commit is contained in:
Vamsi Krishna 2025-01-03 14:16:26 +05:30 committed by GitHub
parent ade0aa1643
commit 873e4330bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
84 changed files with 2588 additions and 873 deletions

View file

@ -3,6 +3,7 @@
import React, { useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation";
import { useTranslation } from "@plane/i18n";
// types
import { EIssuesStoreType } from "@plane/constants";
import type { TBaseIssue, TIssue } from "@plane/types";
@ -54,6 +55,7 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
const [uploadedAssetIds, setUploadedAssetIds] = useState<string[]>([]);
const [isDuplicateModalOpen, setIsDuplicateModalOpen] = useState(false);
// store hooks
const { t } = useTranslation();
const { captureIssueEvent } = useEventTracker();
const { workspaceSlug, projectId: routerProjectId, cycleId, moduleId } = useParams();
const { projectsWithCreatePermissions } = useUser();
@ -218,8 +220,8 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: `${is_draft_issue ? "Draft created." : "Issue created successfully."} `,
title: t("success"),
message: `${is_draft_issue ? t("draft_created") : t("issue_created_successfully")} `,
actionItems: !is_draft_issue && response?.project_id && (
<CreateIssueToastActionItems
workspaceSlug={workspaceSlug.toString()}
@ -241,8 +243,8 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: `${is_draft_issue ? "Draft issue" : "Issue"} could not be created. Please try again.`,
title: t("error"),
message: t(is_draft_issue ? "draft_creation_failed" : "issue_creation_failed"),
});
captureIssueEvent({
eventName: ISSUE_CREATED,
@ -287,8 +289,8 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Issue updated successfully.",
title: t("success"),
message: t("issue_updated_successfully"),
});
captureIssueEvent({
eventName: ISSUE_UPDATED,
@ -300,8 +302,8 @@ export const CreateUpdateIssueModalBase: React.FC<IssuesModalProps> = observer((
console.error(error);
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Issue could not be updated. Please try again.",
title: t("error"),
message: t("issue_could_not_be_updated"),
});
captureIssueEvent({
eventName: ISSUE_UPDATED,

View file

@ -4,6 +4,7 @@ import React, { useState } from "react";
import { observer } from "mobx-react";
import { Control, Controller } from "react-hook-form";
import { LayoutPanelTop } from "lucide-react";
import { useTranslation } from "@plane/i18n";
// types
import { ISearchIssueResponse, TIssue } from "@plane/types";
// ui
@ -65,6 +66,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
// states
const [parentIssueListModalOpen, setParentIssueListModalOpen] = useState(false);
// store hooks
const { t } = useTranslation();
const { areEstimateEnabledByProjectId } = useProjectEstimates();
const { getProjectById } = useProject();
const { isMobile } = usePlatformOS();
@ -133,7 +135,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
}}
buttonVariant={value?.length > 0 ? "transparent-without-text" : "border-with-text"}
buttonClassName={value?.length > 0 ? "hover:bg-transparent" : ""}
placeholder="Assignees"
placeholder={t("assignees")}
multiple
tabIndex={getIndex("assignee_ids")}
/>
@ -172,7 +174,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
}}
buttonVariant="border-with-text"
maxDate={maxDate ?? undefined}
placeholder="Start date"
placeholder={t("start_date")}
tabIndex={getIndex("start_date")}
/>
</div>
@ -191,7 +193,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
}}
buttonVariant="border-with-text"
minDate={minDate ?? undefined}
placeholder="Due date"
placeholder={t("due_date")}
tabIndex={getIndex("target_date")}
/>
</div>
@ -209,7 +211,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
onChange(cycleId);
handleFormChange();
}}
placeholder="Cycle"
placeholder={t("cycle")}
value={value}
buttonVariant="border-with-text"
tabIndex={getIndex("cycle_id")}
@ -231,7 +233,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
onChange(moduleIds);
handleFormChange();
}}
placeholder="Modules"
placeholder={t("modules")}
buttonVariant="border-with-text"
tabIndex={getIndex("module_ids")}
multiple
@ -256,7 +258,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
projectId={projectId}
buttonVariant="border-with-text"
tabIndex={getIndex("estimate_point")}
placeholder="Estimate"
placeholder={t("estimate")}
/>
</div>
)}
@ -288,7 +290,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
>
<>
<CustomMenu.MenuItem className="!p-1" onClick={() => setParentIssueListModalOpen(true)}>
Change parent issue
{t("change_parent_issue")}
</CustomMenu.MenuItem>
<Controller
control={control}
@ -301,7 +303,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
handleFormChange();
}}
>
Remove parent issue
{t("remove_parent_issue")}
</CustomMenu.MenuItem>
)}
/>
@ -314,7 +316,7 @@ export const IssueDefaultProperties: React.FC<TIssueDefaultPropertiesProps> = ob
onClick={() => setParentIssueListModalOpen(true)}
>
<LayoutPanelTop className="h-3 w-3 flex-shrink-0" />
<span className="whitespace-nowrap">Add parent</span>
<span className="whitespace-nowrap">{t("add_parent")}</span>
</button>
)}
</div>

View file

@ -4,6 +4,7 @@ import React from "react";
import { observer } from "mobx-react";
import { Control, Controller, FieldErrors } from "react-hook-form";
// types
import { useTranslation } from "@plane/i18n";
import { TIssue } from "@plane/types";
// ui
import { Input } from "@plane/ui";
@ -25,12 +26,13 @@ export const IssueTitleInput: React.FC<TIssueTitleInputProps> = observer((props)
const { control, issueTitleRef, errors, handleFormChange } = props;
// store hooks
const { isMobile } = usePlatformOS();
const { t } = useTranslation();
const { getIndex } = getTabIndex(ETabIndices.ISSUE_FORM, isMobile);
const validateWhitespace = (value: string) => {
if (value.trim() === "") {
return "Title is required";
return t("title_is_required");
}
return undefined;
};
@ -41,10 +43,10 @@ export const IssueTitleInput: React.FC<TIssueTitleInputProps> = observer((props)
name="name"
rules={{
validate: validateWhitespace,
required: "Title is required",
required: t("title_is_required"),
maxLength: {
value: 255,
message: "Title should be less than 255 characters",
message: t("title_should_be_less_than_255_characters"),
},
}}
render={({ field: { value, onChange, ref } }) => (
@ -59,7 +61,7 @@ export const IssueTitleInput: React.FC<TIssueTitleInputProps> = observer((props)
}}
ref={issueTitleRef || ref}
hasError={Boolean(errors.name)}
placeholder="Title"
placeholder={t("title")}
className="w-full text-base"
tabIndex={getIndex("name")}
autoFocus

View file

@ -7,6 +7,8 @@ import { useForm } from "react-hook-form";
// editor
import { EIssuesStoreType } from "@plane/constants";
import { EditorRefApi } from "@plane/editor";
// i18n
import { useTranslation } from "@plane/i18n";
// types
import type { TIssue, ISearchIssueResponse, TWorkspaceDraftIssue } from "@plane/types";
// hooks
@ -77,6 +79,7 @@ export interface IssueFormProps {
}
export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
const { t } = useTranslation();
const {
data,
issueTitleRef,
@ -89,10 +92,10 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
onCreateMoreToggleChange,
isDraft,
moveToIssue = false,
modalTitle,
modalTitle = `${data?.id ? t("update") : isDraft ? t("create_a_draft") : t("create_new_issue")}`,
primaryButtonText = {
default: `${data?.id ? "Update" : isDraft ? "Save to Drafts" : "Save"}`,
loading: `${data?.id ? "Updating" : "Saving"}`,
default: `${data?.id ? t("update") : isDraft ? t("save_to_drafts") : t("save")}`,
loading: `${data?.id ? t("updating") : t("saving")}`,
},
isDuplicateModalOpen,
handleDuplicateIssueModal,
@ -198,8 +201,8 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
if (!editorRef.current?.isEditorReadyToDiscard()) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Editor is not ready to discard changes.",
title: t("error"),
message: t("editor_is_not_ready_to_discard_changes"),
});
return;
}
@ -391,7 +394,11 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
<DeDupeButtonRoot
workspaceSlug={workspaceSlug?.toString()}
isDuplicateModalOpen={isDuplicateModalOpen}
label={`${duplicateIssues.length} duplicate issue${duplicateIssues.length > 1 ? "s" : ""} found!`}
label={
duplicateIssues.length === 1
? `${duplicateIssues.length} ${t("duplicate_issue_found")}`
: `${duplicateIssues.length} ${t("duplicate_issues_found")}`
}
handleOnClick={() => handleDuplicateIssueModal(!isDuplicateModalOpen)}
/>
)}
@ -491,7 +498,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
role="button"
>
<ToggleSwitch value={isCreateMoreToggleEnabled} onChange={() => {}} size="sm" />
<span className="text-xs">Create more</span>
<span className="text-xs">{t("create_more")}</span>
</div>
)}
<div className="flex items-center gap-2">
@ -511,7 +518,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
}}
tabIndex={getIndex("discard_button")}
>
Discard
{t("discard")}
</Button>
<Button
variant={moveToIssue ? "neutral-primary" : "primary"}

View file

@ -4,8 +4,8 @@ import { useParams } from "next/navigation";
import { usePopper } from "react-popper";
import { Check, Component, Plus, Search, Tag } from "lucide-react";
import { Combobox } from "@headlessui/react";
// plane helpers
import { useOutsideClickDetector } from "@plane/hooks";
import { useTranslation } from "@plane/i18n";
// components
import { IssueLabelsList } from "@/components/ui";
// helpers
@ -39,6 +39,7 @@ export const IssueLabelSelect: React.FC<Props> = observer((props) => {
createLabelEnabled = false,
buttonClassName,
} = props;
const { t } = useTranslation();
// router
const { workspaceSlug } = useParams();
// store hooks
@ -131,7 +132,7 @@ export const IssueLabelSelect: React.FC<Props> = observer((props) => {
) : (
<div className="h-full flex items-center justify-center gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1 text-xs hover:bg-custom-background-80">
<Tag className="h-3 w-3 flex-shrink-0" />
<span>Labels</span>
<span>{t("labels")}</span>
</div>
)}
</button>
@ -152,7 +153,7 @@ export const IssueLabelSelect: React.FC<Props> = observer((props) => {
ref={inputRef}
className="w-full bg-transparent py-1 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none"
onChange={(event) => setQuery(event.target.value)}
placeholder="Search"
placeholder={t("search")}
displayValue={(assigned: any) => assigned?.name}
/>
</div>
@ -232,10 +233,10 @@ export const IssueLabelSelect: React.FC<Props> = observer((props) => {
);
})
) : (
<p className="text-custom-text-400 italic py-1 px-1.5">No matching results</p>
<p className="text-custom-text-400 italic py-1 px-1.5">{t("no_matching_results")}</p>
)
) : (
<p className="text-custom-text-400 italic py-1 px-1.5">Loading...</p>
<p className="text-custom-text-400 italic py-1 px-1.5">{t("loading")}</p>
)}
{createLabelEnabled && (
<button
@ -244,7 +245,7 @@ export const IssueLabelSelect: React.FC<Props> = observer((props) => {
onClick={() => setIsOpen(true)}
>
<Plus className="h-3 w-3" aria-hidden="true" />
<span className="whitespace-nowrap">Create new label</span>
<span className="whitespace-nowrap">{t("create_new_label")}</span>
</button>
)}
</div>