diff --git a/admin/app/page.tsx b/admin/app/page.tsx index b402fc44d39..1a274025a0a 100644 --- a/admin/app/page.tsx +++ b/admin/app/page.tsx @@ -7,15 +7,15 @@ import { DefaultLayout } from "@/layouts/default-layout"; export const metadata: Metadata = { title: "Plane | Simple, extensible, open-source project management tool.", description: - "Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.", + "Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.", openGraph: { title: "Plane | Simple, extensible, open-source project management tool.", description: - "Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.", + "Open-source project management tool to manage work items, sprints, and product roadmaps with peace of mind.", url: "https://plane.so/", }, keywords: - "software development, customer feedback, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration", + "software development, customer feedback, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration", twitter: { site: "@planepowers", }, diff --git a/packages/constants/src/analytics.ts b/packages/constants/src/analytics.ts index 710ac412b74..6c8211ae0ab 100644 --- a/packages/constants/src/analytics.ts +++ b/packages/constants/src/analytics.ts @@ -2,8 +2,11 @@ import { TXAxisValues, TYAxisValues } from "@plane/types"; export const ANALYTICS_TABS = [ - { key: "scope_and_demand", title: "Scope and Demand" }, - { key: "custom", title: "Custom Analytics" }, + { + key: "scope_and_demand", + i18n_title: "workspace_analytics.tabs.scope_and_demand", + }, + { key: "custom", i18n_title: "workspace_analytics.tabs.custom" }, ]; export const ANALYTICS_X_AXIS_VALUES: { value: TXAxisValues; label: string }[] = @@ -62,7 +65,7 @@ export const ANALYTICS_Y_AXIS_VALUES: { value: TYAxisValues; label: string }[] = [ { value: "issue_count", - label: "Issue Count", + label: "Work item Count", }, { value: "estimate", diff --git a/web/core/constants/cycle.ts b/packages/constants/src/cycle.ts similarity index 57% rename from web/core/constants/cycle.ts rename to packages/constants/src/cycle.ts index 025e26c9bad..752f23dbee2 100644 --- a/web/core/constants/cycle.ts +++ b/packages/constants/src/cycle.ts @@ -1,56 +1,40 @@ // types -import { TCycleLayoutOptions, TCycleTabOptions } from "@plane/types"; - -export const CYCLE_TABS_LIST: { - key: TCycleTabOptions; - name: string; -}[] = [ - { - key: "active", - name: "Active", - }, - { - key: "all", - name: "All", - }, -]; - export const CYCLE_STATUS: { - label: string; + i18n_label: string; value: "current" | "upcoming" | "completed" | "draft"; - title: string; + i18n_title: string; color: string; textColor: string; bgColor: string; }[] = [ { - label: "day left", + i18n_label: "project_cycles.status.days_left", value: "current", - title: "In progress", + i18n_title: "project_cycles.status.in_progress", color: "#F59E0B", textColor: "text-amber-500", bgColor: "bg-amber-50", }, { - label: "Yet to start", + i18n_label: "project_cycles.status.yet_to_start", value: "upcoming", - title: "Yet to start", + i18n_title: "project_cycles.status.yet_to_start", color: "#3F76FF", textColor: "text-blue-500", bgColor: "bg-indigo-50", }, { - label: "Completed", + i18n_label: "project_cycles.status.completed", value: "completed", - title: "Completed", + i18n_title: "project_cycles.status.completed", color: "#16A34A", textColor: "text-green-600", bgColor: "bg-green-50", }, { - label: "Draft", + i18n_label: "project_cycles.status.draft", value: "draft", - title: "Draft", + i18n_title: "project_cycles.status.draft", color: "#525252", textColor: "text-custom-text-300", bgColor: "bg-custom-background-90", diff --git a/packages/constants/src/dashboard.ts b/packages/constants/src/dashboard.ts new file mode 100644 index 00000000000..d7a3ae61186 --- /dev/null +++ b/packages/constants/src/dashboard.ts @@ -0,0 +1,92 @@ +// types +import { TIssuesListTypes } from "@plane/types"; + +export enum EDurationFilters { + NONE = "none", + TODAY = "today", + THIS_WEEK = "this_week", + THIS_MONTH = "this_month", + THIS_YEAR = "this_year", + CUSTOM = "custom", +} + +// filter duration options +export const DURATION_FILTER_OPTIONS: { + key: EDurationFilters; + label: string; +}[] = [ + { + key: EDurationFilters.NONE, + label: "All time", + }, + { + key: EDurationFilters.TODAY, + label: "Due today", + }, + { + key: EDurationFilters.THIS_WEEK, + label: "Due this week", + }, + { + key: EDurationFilters.THIS_MONTH, + label: "Due this month", + }, + { + key: EDurationFilters.THIS_YEAR, + label: "Due this year", + }, + { + key: EDurationFilters.CUSTOM, + label: "Custom", + }, +]; + +// random background colors for project cards +export const PROJECT_BACKGROUND_COLORS = [ + "bg-gray-500/20", + "bg-green-500/20", + "bg-red-500/20", + "bg-orange-500/20", + "bg-blue-500/20", + "bg-yellow-500/20", + "bg-pink-500/20", + "bg-purple-500/20", +]; + +// assigned and created issues widgets tabs list +export const FILTERED_ISSUES_TABS_LIST: { + key: TIssuesListTypes; + label: string; +}[] = [ + { + key: "upcoming", + label: "Upcoming", + }, + { + key: "overdue", + label: "Overdue", + }, + { + key: "completed", + label: "Marked completed", + }, +]; + +// assigned and created issues widgets tabs list +export const UNFILTERED_ISSUES_TABS_LIST: { + key: TIssuesListTypes; + label: string; +}[] = [ + { + key: "pending", + label: "Pending", + }, + { + key: "completed", + label: "Marked completed", + }, +]; + +export type TLinkOptions = { + userId: string | undefined; +}; diff --git a/packages/constants/src/error.ts b/packages/constants/src/error.ts deleted file mode 100644 index 46277abf712..00000000000 --- a/packages/constants/src/error.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum E_ARCHIVE_ERROR_CODES { - "INVALID_ARCHIVE_STATE_GROUP" = 4091, - "INVALID_ISSUE_START_DATE" = 4101, - "INVALID_ISSUE_TARGET_DATE" = 4102, -} diff --git a/web/core/constants/event-tracker.ts b/packages/constants/src/event-tracker.ts similarity index 95% rename from web/core/constants/event-tracker.ts rename to packages/constants/src/event-tracker.ts index cd2e1c3bec6..4fb1ea15c21 100644 --- a/web/core/constants/event-tracker.ts +++ b/packages/constants/src/event-tracker.ts @@ -104,7 +104,10 @@ export const getIssueEventPayload = (props: IssueEventProps) => { module_id: payload.module_id, archived_at: payload.archived_at, state: payload.state, - view_id: path?.includes("workspace-views") || path?.includes("views") ? path.split("/").pop() : "", + view_id: + path?.includes("workspace-views") || path?.includes("views") + ? path.split("/").pop() + : "", }; if (eventName === ISSUE_UPDATED) { @@ -166,12 +169,12 @@ export const MODULE_LINK_CREATED = "Module link created"; export const MODULE_LINK_UPDATED = "Module link updated"; export const MODULE_LINK_DELETED = "Module link deleted"; // Issue Events -export const ISSUE_CREATED = "Issue created"; -export const ISSUE_UPDATED = "Issue updated"; -export const ISSUE_DELETED = "Issue deleted"; -export const ISSUE_ARCHIVED = "Issue archived"; -export const ISSUE_RESTORED = "Issue restored"; -export const ISSUE_OPENED = "Issue opened"; +export const ISSUE_CREATED = "Work item created"; +export const ISSUE_UPDATED = "Work item updated"; +export const ISSUE_DELETED = "Work item deleted"; +export const ISSUE_ARCHIVED = "Work item archived"; +export const ISSUE_RESTORED = "Work item restored"; +export const ISSUE_OPENED = "Work item opened"; // Project State Events export const STATE_CREATED = "State created"; export const STATE_UPDATED = "State updated"; diff --git a/packages/constants/src/event.ts b/packages/constants/src/event.ts deleted file mode 100644 index 5e7e22004d5..00000000000 --- a/packages/constants/src/event.ts +++ /dev/null @@ -1 +0,0 @@ -export const SIDEBAR_CLICKED = "Sidenav clicked"; diff --git a/packages/constants/src/filter.ts b/packages/constants/src/filter.ts index 20de817a28f..cc8d98c45c3 100644 --- a/packages/constants/src/filter.ts +++ b/packages/constants/src/filter.ts @@ -2,3 +2,56 @@ export enum E_SORT_ORDER { ASC = "asc", DESC = "desc", } +export const DATE_AFTER_FILTER_OPTIONS = [ + { + name: "1 week from now", + value: "1_weeks;after;fromnow", + }, + { + name: "2 weeks from now", + value: "2_weeks;after;fromnow", + }, + { + name: "1 month from now", + value: "1_months;after;fromnow", + }, + { + name: "2 months from now", + value: "2_months;after;fromnow", + }, +]; + +export const DATE_BEFORE_FILTER_OPTIONS = [ + { + name: "1 week ago", + value: "1_weeks;before;fromnow", + }, + { + name: "2 weeks ago", + value: "2_weeks;before;fromnow", + }, + { + name: "1 month ago", + i18n_name: "date_filters.1_month_ago", + value: "1_months;before;fromnow", + }, +]; + +export const PROJECT_CREATED_AT_FILTER_OPTIONS = [ + { + name: "Today", + value: "today;custom;custom", + }, + { + name: "Yesterday", + value: "yesterday;custom;custom", + }, + { + name: "Last 7 days", + value: "last_7_days;custom;custom", + }, + { + name: "Last 30 days", + value: "last_30_days;custom;custom", + }, +]; diff --git a/packages/constants/src/inbox.ts b/packages/constants/src/inbox.ts new file mode 100644 index 00000000000..cf5270a0440 --- /dev/null +++ b/packages/constants/src/inbox.ts @@ -0,0 +1,91 @@ +import { TInboxDuplicateIssueDetails, TIssue } from "@plane/types"; + +export enum EInboxIssueCurrentTab { + OPEN = "open", + CLOSED = "closed", +} + +export enum EInboxIssueStatus { + PENDING = -2, + DECLINED = -1, + SNOOZED = 0, + ACCEPTED = 1, + DUPLICATE = 2, +} + +export type TInboxIssueCurrentTab = EInboxIssueCurrentTab; +export type TInboxIssueStatus = EInboxIssueStatus; +export type TInboxIssue = { + id: string; + status: TInboxIssueStatus; + snoozed_till: Date | null; + duplicate_to: string | undefined; + source: string; + issue: TIssue; + created_by: string; + duplicate_issue_detail: TInboxDuplicateIssueDetails | undefined; +}; + +export const INBOX_STATUS: { + key: string; + status: TInboxIssueStatus; + i18n_title: string; + i18n_description: () => string; +}[] = [ + { + key: "pending", + i18n_title: "inbox_issue.status.pending.title", + status: EInboxIssueStatus.PENDING, + i18n_description: () => `inbox_issue.status.pending.description`, + }, + { + key: "declined", + i18n_title: "inbox_issue.status.declined.title", + status: EInboxIssueStatus.DECLINED, + i18n_description: () => `inbox_issue.status.declined.description`, + }, + { + key: "snoozed", + i18n_title: "inbox_issue.status.snoozed.title", + status: EInboxIssueStatus.SNOOZED, + i18n_description: () => `inbox_issue.status.snoozed.description`, + }, + { + key: "accepted", + i18n_title: "inbox_issue.status.accepted.title", + status: EInboxIssueStatus.ACCEPTED, + i18n_description: () => `inbox_issue.status.accepted.description`, + }, + { + key: "duplicate", + i18n_title: "inbox_issue.status.duplicate.title", + status: EInboxIssueStatus.DUPLICATE, + i18n_description: () => `inbox_issue.status.duplicate.description`, + }, +]; + +export const INBOX_ISSUE_ORDER_BY_OPTIONS = [ + { + key: "issue__created_at", + i18n_label: "inbox_issue.order_by.created_at", + }, + { + key: "issue__updated_at", + i18n_label: "inbox_issue.order_by.updated_at", + }, + { + key: "issue__sequence_id", + i18n_label: "inbox_issue.order_by.id", + }, +]; + +export const INBOX_ISSUE_SORT_BY_OPTIONS = [ + { + key: "asc", + i18n_label: "common.sort.asc", + }, + { + key: "desc", + i18n_label: "common.sort.desc", + }, +]; diff --git a/packages/constants/src/index.ts b/packages/constants/src/index.ts index 2b3964ae9fd..ece5aad4c34 100644 --- a/packages/constants/src/index.ts +++ b/packages/constants/src/index.ts @@ -2,15 +2,29 @@ export * from "./ai"; export * from "./analytics"; export * from "./auth"; export * from "./endpoints"; -export * from "./event"; export * from "./file"; export * from "./filter"; export * from "./graph"; export * from "./instance"; export * from "./issue"; export * from "./metadata"; +export * from "./notification"; export * from "./state"; export * from "./swr"; +export * from "./tab-indices"; export * from "./user"; export * from "./workspace"; export * from "./stickies"; +export * from "./cycle"; +export * from "./module"; +export * from "./project"; +export * from "./views"; +export * from "./themes"; +export * from "./inbox"; +export * from "./profile"; +export * from "./workspace-drafts"; +export * from "./label"; +export * from "./event-tracker"; +export * from "./spreadsheet"; +export * from "./dashboard"; +export * from "./page"; diff --git a/packages/constants/src/issue.ts b/packages/constants/src/issue.ts deleted file mode 100644 index 19cfe60f3a6..00000000000 --- a/packages/constants/src/issue.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { List, Kanban } from "lucide-react"; - -export const ALL_ISSUES = "All Issues"; - -export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none"; - -export type TIssueFilterKeys = "priority" | "state" | "labels"; - -export type TIssueLayout = - | "list" - | "kanban" - | "calendar" - | "spreadsheet" - | "gantt"; - -export type TIssueFilterPriorityObject = { - key: TIssuePriorities; - title: string; - className: string; - icon: string; -}; - -export enum EIssueGroupByToServerOptions { - "state" = "state_id", - "priority" = "priority", - "labels" = "labels__id", - "state_detail.group" = "state__group", - "assignees" = "assignees__id", - "cycle" = "cycle_id", - "module" = "issue_module__module_id", - "target_date" = "target_date", - "project" = "project_id", - "created_by" = "created_by", - "team_project" = "project_id", -} - -export enum EIssueGroupBYServerToProperty { - "state_id" = "state_id", - "priority" = "priority", - "labels__id" = "label_ids", - "state__group" = "state__group", - "assignees__id" = "assignee_ids", - "cycle_id" = "cycle_id", - "issue_module__module_id" = "module_ids", - "target_date" = "target_date", - "project_id" = "project_id", - "created_by" = "created_by", -} - -export enum EServerGroupByToFilterOptions { - "state_id" = "state", - "priority" = "priority", - "labels__id" = "labels", - "state__group" = "state_group", - "assignees__id" = "assignees", - "cycle_id" = "cycle", - "issue_module__module_id" = "module", - "target_date" = "target_date", - "project_id" = "project", - "created_by" = "created_by", -} - -export enum EIssueServiceType { - ISSUES = "issues", - EPICS = "epics", -} - -export enum EIssueLayoutTypes { - LIST = "list", - KANBAN = "kanban", - CALENDAR = "calendar", - GANTT = "gantt_chart", - SPREADSHEET = "spreadsheet", -} - -export enum EIssuesStoreType { - GLOBAL = "GLOBAL", - PROFILE = "PROFILE", - TEAM = "TEAM", - PROJECT = "PROJECT", - CYCLE = "CYCLE", - MODULE = "MODULE", - TEAM_VIEW = "TEAM_VIEW", - PROJECT_VIEW = "PROJECT_VIEW", - ARCHIVED = "ARCHIVED", - DRAFT = "DRAFT", - DEFAULT = "DEFAULT", - WORKSPACE_DRAFT = "WORKSPACE_DRAFT", - EPIC = "EPIC", -} - -export enum EIssueFilterType { - FILTERS = "filters", - DISPLAY_FILTERS = "display_filters", - DISPLAY_PROPERTIES = "display_properties", - KANBAN_FILTERS = "kanban_filters", -} - -export enum EIssueCommentAccessSpecifier { - EXTERNAL = "EXTERNAL", - INTERNAL = "INTERNAL", -} - -export enum EIssueListRow { - HEADER = "HEADER", - ISSUE = "ISSUE", - NO_ISSUES = "NO_ISSUES", - QUICK_ADD = "QUICK_ADD", -} - -export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { - [key in TIssueLayout]: Record<"filters", TIssueFilterKeys[]>; -} = { - list: { - filters: ["priority", "state", "labels"], - }, - kanban: { - filters: ["priority", "state", "labels"], - }, - calendar: { - filters: ["priority", "state", "labels"], - }, - spreadsheet: { - filters: ["priority", "state", "labels"], - }, - gantt: { - filters: ["priority", "state", "labels"], - }, -}; - -export const ISSUE_PRIORITIES: { - key: TIssuePriorities; - title: string; -}[] = [ - { key: "urgent", title: "Urgent" }, - { key: "high", title: "High" }, - { key: "medium", title: "Medium" }, - { key: "low", title: "Low" }, - { key: "none", title: "None" }, -]; - -export const ISSUE_PRIORITY_FILTERS: TIssueFilterPriorityObject[] = [ - { - key: "urgent", - title: "Urgent", - className: "bg-red-500 border-red-500 text-white", - icon: "error", - }, - { - key: "high", - title: "High", - className: "text-orange-500 border-custom-border-300", - icon: "signal_cellular_alt", - }, - { - key: "medium", - title: "Medium", - className: "text-yellow-500 border-custom-border-300", - icon: "signal_cellular_alt_2_bar", - }, - { - key: "low", - title: "Low", - className: "text-green-500 border-custom-border-300", - icon: "signal_cellular_alt_1_bar", - }, - { - key: "none", - title: "None", - className: "text-gray-500 border-custom-border-300", - icon: "block", - }, -]; - -export const SITES_ISSUE_LAYOUTS: { - key: TIssueLayout; - title: string; - icon: any; -}[] = [ - { key: "list", title: "List", icon: List }, - { key: "kanban", title: "Kanban", icon: Kanban }, - // { key: "calendar", title: "Calendar", icon: Calendar }, - // { key: "spreadsheet", title: "Spreadsheet", icon: Sheet }, - // { key: "gantt", title: "Gantt chart", icon: GanttChartSquare }, -]; diff --git a/packages/constants/src/issue/common.ts b/packages/constants/src/issue/common.ts new file mode 100644 index 00000000000..a63dd110d39 --- /dev/null +++ b/packages/constants/src/issue/common.ts @@ -0,0 +1,217 @@ +import { + TIssueGroupByOptions, + TIssueOrderByOptions, + IIssueDisplayProperties, +} from "@plane/types"; + +export const ALL_ISSUES = "All Issues"; + +export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none"; + +export type TIssueFilterPriorityObject = { + key: TIssuePriorities; + titleTranslationKey: string; + className: string; + icon: string; +}; + +export enum EIssueGroupByToServerOptions { + "state" = "state_id", + "priority" = "priority", + "labels" = "labels__id", + "state_detail.group" = "state__group", + "assignees" = "assignees__id", + "cycle" = "cycle_id", + "module" = "issue_module__module_id", + "target_date" = "target_date", + "project" = "project_id", + "created_by" = "created_by", + "team_project" = "project_id", +} + +export enum EIssueGroupBYServerToProperty { + "state_id" = "state_id", + "priority" = "priority", + "labels__id" = "label_ids", + "state__group" = "state__group", + "assignees__id" = "assignee_ids", + "cycle_id" = "cycle_id", + "issue_module__module_id" = "module_ids", + "target_date" = "target_date", + "project_id" = "project_id", + "created_by" = "created_by", +} + +export enum EIssueServiceType { + ISSUES = "issues", + EPICS = "epics", +} + +export enum EIssuesStoreType { + GLOBAL = "GLOBAL", + PROFILE = "PROFILE", + TEAM = "TEAM", + PROJECT = "PROJECT", + CYCLE = "CYCLE", + MODULE = "MODULE", + TEAM_VIEW = "TEAM_VIEW", + PROJECT_VIEW = "PROJECT_VIEW", + ARCHIVED = "ARCHIVED", + DRAFT = "DRAFT", + DEFAULT = "DEFAULT", + WORKSPACE_DRAFT = "WORKSPACE_DRAFT", + EPIC = "EPIC", +} + +export enum EIssueCommentAccessSpecifier { + EXTERNAL = "EXTERNAL", + INTERNAL = "INTERNAL", +} + +export enum EIssueListRow { + HEADER = "HEADER", + ISSUE = "ISSUE", + NO_ISSUES = "NO_ISSUES", + QUICK_ADD = "QUICK_ADD", +} + +export const ISSUE_PRIORITIES: { + key: TIssuePriorities; + title: string; +}[] = [ + { + key: "urgent", + title: "Urgent", + }, + { + key: "high", + title: "High", + }, + { + key: "medium", + title: "Medium", + }, + { + key: "low", + title: "Low", + }, + { + key: "none", + title: "None", + }, +]; + +export const DRAG_ALLOWED_GROUPS: TIssueGroupByOptions[] = [ + "state", + "priority", + "assignees", + "labels", + "module", + "cycle", +]; + +export type TCreateModalStoreTypes = + | EIssuesStoreType.TEAM + | EIssuesStoreType.PROJECT + | EIssuesStoreType.TEAM_VIEW + | EIssuesStoreType.PROJECT_VIEW + | EIssuesStoreType.PROFILE + | EIssuesStoreType.CYCLE + | EIssuesStoreType.MODULE + | EIssuesStoreType.EPIC; + +export const ISSUE_GROUP_BY_OPTIONS: { + key: TIssueGroupByOptions; + titleTranslationKey: string; +}[] = [ + { key: "state", titleTranslationKey: "common.states" }, + { key: "state_detail.group", titleTranslationKey: "common.state_groups" }, + { key: "priority", titleTranslationKey: "common.priority" }, + { key: "team_project", titleTranslationKey: "common.team_project" }, // required this on team issues + { key: "project", titleTranslationKey: "common.project" }, // required this on my issues + { key: "cycle", titleTranslationKey: "common.cycle" }, // required this on my issues + { key: "module", titleTranslationKey: "common.module" }, // required this on my issues + { key: "labels", titleTranslationKey: "common.labels" }, + { key: "assignees", titleTranslationKey: "common.assignees" }, + { key: "created_by", titleTranslationKey: "common.created_by" }, + { key: null, titleTranslationKey: "common.none" }, +]; + +export const ISSUE_ORDER_BY_OPTIONS: { + key: TIssueOrderByOptions; + titleTranslationKey: string; +}[] = [ + { key: "sort_order", titleTranslationKey: "common.order_by.manual" }, + { key: "-created_at", titleTranslationKey: "common.order_by.last_created" }, + { key: "-updated_at", titleTranslationKey: "common.order_by.last_updated" }, + { key: "start_date", titleTranslationKey: "common.order_by.start_date" }, + { key: "target_date", titleTranslationKey: "common.order_by.due_date" }, + { key: "-priority", titleTranslationKey: "common.priority" }, +]; + +export const ISSUE_DISPLAY_PROPERTIES_KEYS: (keyof IIssueDisplayProperties)[] = + [ + "assignee", + "start_date", + "due_date", + "labels", + "key", + "priority", + "state", + "sub_issue_count", + "link", + "attachment_count", + "estimate", + "created_on", + "updated_on", + "modules", + "cycle", + "issue_type", + ]; + +export const ISSUE_DISPLAY_PROPERTIES: { + key: keyof IIssueDisplayProperties; + titleTranslationKey: string; +}[] = [ + { + key: "key", + titleTranslationKey: "issue.display.properties.id", + }, + { + key: "issue_type", + titleTranslationKey: "issue.display.properties.issue_type", + }, + { + key: "assignee", + titleTranslationKey: "common.assignee", + }, + { + key: "start_date", + titleTranslationKey: "common.order_by.start_date", + }, + { + key: "due_date", + titleTranslationKey: "common.order_by.due_date", + }, + { key: "labels", titleTranslationKey: "common.labels" }, + { + key: "priority", + titleTranslationKey: "common.priority", + }, + { key: "state", titleTranslationKey: "common.state" }, + { + key: "sub_issue_count", + titleTranslationKey: "issue.display.properties.sub_issue_count", + }, + { + key: "attachment_count", + titleTranslationKey: "issue.display.properties.attachment_count", + }, + { key: "link", titleTranslationKey: "common.link" }, + { + key: "estimate", + titleTranslationKey: "common.estimate", + }, + { key: "modules", titleTranslationKey: "common.module" }, + { key: "cycle", titleTranslationKey: "common.cycle" }, +]; diff --git a/packages/constants/src/issue/filter.ts b/packages/constants/src/issue/filter.ts new file mode 100644 index 00000000000..5d1d694b722 --- /dev/null +++ b/packages/constants/src/issue/filter.ts @@ -0,0 +1,530 @@ +import { + ILayoutDisplayFiltersOptions, + TIssueActivityComment, +} from "@plane/types"; +import { + TIssueFilterPriorityObject, + ISSUE_DISPLAY_PROPERTIES_KEYS, + EIssuesStoreType, +} from "./common"; + +import { TIssueLayout } from "./layout"; + +export type TIssueFilterKeys = "priority" | "state" | "labels"; + +export enum EServerGroupByToFilterOptions { + "state_id" = "state", + "priority" = "priority", + "labels__id" = "labels", + "state__group" = "state_group", + "assignees__id" = "assignees", + "cycle_id" = "cycle", + "issue_module__module_id" = "module", + "target_date" = "target_date", + "project_id" = "project", + "created_by" = "created_by", +} + +export enum EIssueFilterType { + FILTERS = "filters", + DISPLAY_FILTERS = "display_filters", + DISPLAY_PROPERTIES = "display_properties", + KANBAN_FILTERS = "kanban_filters", +} + +export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { + [key in TIssueLayout]: Record<"filters", TIssueFilterKeys[]>; +} = { + list: { + filters: ["priority", "state", "labels"], + }, + kanban: { + filters: ["priority", "state", "labels"], + }, + calendar: { + filters: ["priority", "state", "labels"], + }, + spreadsheet: { + filters: ["priority", "state", "labels"], + }, + gantt: { + filters: ["priority", "state", "labels"], + }, +}; + +export const ISSUE_PRIORITY_FILTERS: TIssueFilterPriorityObject[] = [ + { + key: "urgent", + titleTranslationKey: "issue.priority.urgent", + className: "bg-red-500 border-red-500 text-white", + icon: "error", + }, + { + key: "high", + titleTranslationKey: "issue.priority.high", + className: "text-orange-500 border-custom-border-300", + icon: "signal_cellular_alt", + }, + { + key: "medium", + titleTranslationKey: "issue.priority.medium", + className: "text-yellow-500 border-custom-border-300", + icon: "signal_cellular_alt_2_bar", + }, + { + key: "low", + titleTranslationKey: "issue.priority.low", + className: "text-green-500 border-custom-border-300", + icon: "signal_cellular_alt_1_bar", + }, + { + key: "none", + titleTranslationKey: "common.none", + className: "text-gray-500 border-custom-border-300", + icon: "block", + }, +]; + +export type TFiltersByLayout = { + [layoutType: string]: ILayoutDisplayFiltersOptions; +}; + +export type TIssueFiltersToDisplayByPageType = { + [pageType: string]: TFiltersByLayout; +}; + +export const ISSUE_DISPLAY_FILTERS_BY_PAGE: TIssueFiltersToDisplayByPageType = { + profile_issues: { + list: { + filters: [ + "priority", + "state_group", + "labels", + "start_date", + "target_date", + ], + display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, + display_filters: { + group_by: ["state_detail.group", "priority", "project", "labels", null], + order_by: [ + "sort_order", + "-created_at", + "-updated_at", + "start_date", + "-priority", + ], + type: [null, "active", "backlog"], + }, + extra_options: { + access: true, + values: ["show_empty_groups", "sub_issue"], + }, + }, + kanban: { + filters: [ + "priority", + "state_group", + "labels", + "start_date", + "target_date", + ], + display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, + display_filters: { + group_by: ["state_detail.group", "priority", "project", "labels"], + order_by: [ + "sort_order", + "-created_at", + "-updated_at", + "start_date", + "-priority", + ], + type: [null, "active", "backlog"], + }, + extra_options: { + access: true, + values: ["show_empty_groups"], + }, + }, + }, + archived_issues: { + list: { + filters: [ + "priority", + "state", + "cycle", + "module", + "assignees", + "created_by", + "labels", + "start_date", + "target_date", + "issue_type", + ], + display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, + display_filters: { + group_by: [ + "state", + "cycle", + "module", + "state_detail.group", + "priority", + "labels", + "assignees", + "created_by", + null, + ], + order_by: [ + "sort_order", + "-created_at", + "-updated_at", + "start_date", + "-priority", + ], + type: [null, "active", "backlog"], + }, + extra_options: { + access: true, + values: ["show_empty_groups"], + }, + }, + }, + draft_issues: { + list: { + filters: [ + "priority", + "state_group", + "cycle", + "module", + "labels", + "start_date", + "target_date", + "issue_type", + ], + display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, + display_filters: { + group_by: [ + "state_detail.group", + "cycle", + "module", + "priority", + "project", + "labels", + null, + ], + order_by: [ + "sort_order", + "-created_at", + "-updated_at", + "start_date", + "-priority", + ], + type: [null, "active", "backlog"], + }, + extra_options: { + access: true, + values: ["show_empty_groups"], + }, + }, + kanban: { + filters: [ + "priority", + "state_group", + "cycle", + "module", + "labels", + "start_date", + "target_date", + "issue_type", + ], + display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, + display_filters: { + group_by: [ + "state_detail.group", + "cycle", + "module", + "priority", + "project", + "labels", + ], + order_by: [ + "sort_order", + "-created_at", + "-updated_at", + "start_date", + "-priority", + ], + type: [null, "active", "backlog"], + }, + extra_options: { + access: true, + values: ["show_empty_groups"], + }, + }, + }, + my_issues: { + spreadsheet: { + filters: [ + "priority", + "state_group", + "labels", + "assignees", + "created_by", + "subscriber", + "project", + "start_date", + "target_date", + ], + display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, + display_filters: { + order_by: [], + type: [null, "active", "backlog"], + }, + extra_options: { + access: true, + values: ["sub_issue"], + }, + }, + list: { + filters: [ + "priority", + "state_group", + "labels", + "assignees", + "created_by", + "subscriber", + "project", + "start_date", + "target_date", + ], + display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, + display_filters: { + type: [null, "active", "backlog"], + }, + extra_options: { + access: false, + values: [], + }, + }, + }, + issues: { + list: { + filters: [ + "priority", + "state", + "cycle", + "module", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "target_date", + "issue_type", + ], + display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, + display_filters: { + group_by: [ + "state", + "priority", + "cycle", + "module", + "labels", + "assignees", + "created_by", + null, + ], + order_by: [ + "sort_order", + "-created_at", + "-updated_at", + "start_date", + "-priority", + ], + type: [null, "active", "backlog"], + }, + extra_options: { + access: true, + values: ["show_empty_groups", "sub_issue"], + }, + }, + kanban: { + filters: [ + "priority", + "state", + "cycle", + "module", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "target_date", + "issue_type", + ], + display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, + display_filters: { + group_by: [ + "state", + "priority", + "cycle", + "module", + "labels", + "assignees", + "created_by", + ], + sub_group_by: [ + "state", + "priority", + "cycle", + "module", + "labels", + "assignees", + "created_by", + null, + ], + order_by: [ + "sort_order", + "-created_at", + "-updated_at", + "start_date", + "-priority", + "target_date", + ], + type: [null, "active", "backlog"], + }, + extra_options: { + access: true, + values: ["show_empty_groups", "sub_issue"], + }, + }, + calendar: { + filters: [ + "priority", + "state", + "cycle", + "module", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "issue_type", + ], + display_properties: ["key", "issue_type"], + display_filters: { + type: [null, "active", "backlog"], + }, + extra_options: { + access: true, + values: ["sub_issue"], + }, + }, + spreadsheet: { + filters: [ + "priority", + "state", + "cycle", + "module", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "target_date", + "issue_type", + ], + display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, + display_filters: { + order_by: [ + "sort_order", + "-created_at", + "-updated_at", + "start_date", + "-priority", + ], + type: [null, "active", "backlog"], + }, + extra_options: { + access: true, + values: ["sub_issue"], + }, + }, + gantt_chart: { + filters: [ + "priority", + "state", + "cycle", + "module", + "assignees", + "mentions", + "created_by", + "labels", + "start_date", + "target_date", + "issue_type", + ], + display_properties: ["key", "issue_type"], + display_filters: { + order_by: [ + "sort_order", + "-created_at", + "-updated_at", + "start_date", + "-priority", + ], + type: [null, "active", "backlog"], + }, + extra_options: { + access: true, + values: ["sub_issue"], + }, + }, + }, +}; + +export const ISSUE_STORE_TO_FILTERS_MAP: Partial< + Record +> = { + [EIssuesStoreType.PROJECT]: ISSUE_DISPLAY_FILTERS_BY_PAGE.issues, +}; + +export enum EActivityFilterType { + ACTIVITY = "ACTIVITY", + COMMENT = "COMMENT", +} + +export type TActivityFilters = EActivityFilterType; + +export const ACTIVITY_FILTER_TYPE_OPTIONS: Record< + TActivityFilters, + { labelTranslationKey: string } +> = { + [EActivityFilterType.ACTIVITY]: { + labelTranslationKey: "common.updates", + }, + [EActivityFilterType.COMMENT]: { + labelTranslationKey: "common.comments", + }, +}; + +export type TActivityFilterOption = { + key: TActivityFilters; + labelTranslationKey: string; + isSelected: boolean; + onClick: () => void; +}; + +export const defaultActivityFilters: TActivityFilters[] = [ + EActivityFilterType.ACTIVITY, + EActivityFilterType.COMMENT, +]; + +export const filterActivityOnSelectedFilters = ( + activity: TIssueActivityComment[], + filters: TActivityFilters[] +): TIssueActivityComment[] => + activity.filter((activity) => + filters.includes(activity.activity_type as TActivityFilters) + ); + +export const ENABLE_ISSUE_DEPENDENCIES = false; diff --git a/packages/constants/src/issue/index.ts b/packages/constants/src/issue/index.ts new file mode 100644 index 00000000000..63320322340 --- /dev/null +++ b/packages/constants/src/issue/index.ts @@ -0,0 +1,3 @@ +export * from "./common"; +export * from "./filter"; +export * from "./layout"; diff --git a/packages/constants/src/issue/layout.ts b/packages/constants/src/issue/layout.ts new file mode 100644 index 00000000000..6dd62fd177d --- /dev/null +++ b/packages/constants/src/issue/layout.ts @@ -0,0 +1,76 @@ +export type TIssueLayout = + | "list" + | "kanban" + | "calendar" + | "spreadsheet" + | "gantt"; + +export enum EIssueLayoutTypes { + LIST = "list", + KANBAN = "kanban", + CALENDAR = "calendar", + GANTT = "gantt_chart", + SPREADSHEET = "spreadsheet", +} + +export type TIssueLayoutMap = Record< + EIssueLayoutTypes, + { + key: EIssueLayoutTypes; + i18n_title: string; + i18n_label: string; + } +>; + +export const SITES_ISSUE_LAYOUTS: { + key: TIssueLayout; + titleTranslationKey: string; + icon: any; +}[] = [ + { + key: "list", + icon: "List", + titleTranslationKey: "issue.layouts.list", + }, + { + key: "kanban", + icon: "Kanban", + titleTranslationKey: "issue.layouts.kanban", + }, + // { key: "calendar", title: "Calendar", icon: Calendar }, + // { key: "spreadsheet", title: "Spreadsheet", icon: Sheet }, + // { key: "gantt", title: "Gantt chart", icon: GanttChartSquare }, +]; + +export const ISSUE_LAYOUT_MAP: TIssueLayoutMap = { + [EIssueLayoutTypes.LIST]: { + key: EIssueLayoutTypes.LIST, + i18n_title: "issue.layouts.title.list", + i18n_label: "issue.layouts.list", + }, + [EIssueLayoutTypes.KANBAN]: { + key: EIssueLayoutTypes.KANBAN, + i18n_title: "issue.layouts.title.kanban", + i18n_label: "issue.layouts.kanban", + }, + [EIssueLayoutTypes.CALENDAR]: { + key: EIssueLayoutTypes.CALENDAR, + i18n_title: "issue.layouts.title.calendar", + i18n_label: "issue.layouts.calendar", + }, + [EIssueLayoutTypes.SPREADSHEET]: { + key: EIssueLayoutTypes.SPREADSHEET, + i18n_title: "issue.layouts.title.spreadsheet", + i18n_label: "issue.layouts.spreadsheet", + }, + [EIssueLayoutTypes.GANTT]: { + key: EIssueLayoutTypes.GANTT, + i18n_title: "issue.layouts.title.gantt", + i18n_label: "issue.layouts.gantt", + }, +}; + +export const ISSUE_LAYOUTS: { + key: EIssueLayoutTypes; + i18n_title: string; +}[] = Object.values(ISSUE_LAYOUT_MAP); diff --git a/web/core/constants/label.ts b/packages/constants/src/label.ts similarity index 100% rename from web/core/constants/label.ts rename to packages/constants/src/label.ts diff --git a/packages/constants/src/metadata.ts b/packages/constants/src/metadata.ts index b3563882678..017cc84c252 100644 --- a/packages/constants/src/metadata.ts +++ b/packages/constants/src/metadata.ts @@ -3,9 +3,9 @@ export const SITE_NAME = export const SITE_TITLE = "Plane | Simple, extensible, open-source project management tool."; export const SITE_DESCRIPTION = - "Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind."; + "Open-source project management tool to manage work items, cycles, and product roadmaps easily"; export const SITE_KEYWORDS = - "software development, plan, ship, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration"; + "software development, plan, ship, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration"; export const SITE_URL = "https://app.plane.so/"; export const TWITTER_USER_NAME = "Plane | Simple, extensible, open-source project management tool."; @@ -18,6 +18,6 @@ export const SPACE_SITE_TITLE = export const SPACE_SITE_DESCRIPTION = "Plane Publish is a customer feedback management tool built on top of plane.so"; export const SPACE_SITE_KEYWORDS = - "software development, customer feedback, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration"; + "software development, customer feedback, software, accelerate, code management, release management, project management, work items tracking, agile, scrum, kanban, collaboration"; export const SPACE_SITE_URL = "https://app.plane.so/"; export const SPACE_TWITTER_USER_NAME = "planepowers"; diff --git a/web/core/constants/module.ts b/packages/constants/src/module.ts similarity index 51% rename from web/core/constants/module.ts rename to packages/constants/src/module.ts index 8d26ab5f4e7..6ce30f0dcf1 100644 --- a/web/core/constants/module.ts +++ b/packages/constants/src/module.ts @@ -1,51 +1,54 @@ -import { GanttChartSquare, LayoutGrid, List } from "lucide-react"; // types -import { TModuleLayoutOptions, TModuleOrderByOptions, TModuleStatus } from "@plane/types"; +import { + TModuleLayoutOptions, + TModuleOrderByOptions, + TModuleStatus, +} from "@plane/types"; export const MODULE_STATUS: { - label: string; + i18n_label: string; value: TModuleStatus; color: string; textColor: string; bgColor: string; }[] = [ { - label: "Backlog", + i18n_label: "project_modules.status.backlog", value: "backlog", color: "#a3a3a2", textColor: "text-custom-text-400", bgColor: "bg-custom-background-80", }, { - label: "Planned", + i18n_label: "project_modules.status.planned", value: "planned", color: "#3f76ff", textColor: "text-blue-500", bgColor: "bg-indigo-50", }, { - label: "In Progress", + i18n_label: "project_modules.status.in_progress", value: "in-progress", color: "#f39e1f", textColor: "text-amber-500", bgColor: "bg-amber-50", }, { - label: "Paused", + i18n_label: "project_modules.status.paused", value: "paused", color: "#525252", textColor: "text-custom-text-300", bgColor: "bg-custom-background-90", }, { - label: "Completed", + i18n_label: "project_modules.status.completed", value: "completed", color: "#16a34a", textColor: "text-green-600", bgColor: "bg-green-100", }, { - label: "Cancelled", + i18n_label: "project_modules.status.cancelled", value: "cancelled", color: "#ef4444", textColor: "text-red-500", @@ -53,47 +56,50 @@ export const MODULE_STATUS: { }, ]; -export const MODULE_VIEW_LAYOUTS: { key: TModuleLayoutOptions; icon: any; title: string }[] = [ +export const MODULE_VIEW_LAYOUTS: { + key: TModuleLayoutOptions; + i18n_title: string; +}[] = [ { key: "list", - icon: List, - title: "List layout", + i18n_title: "project_modules.layout.list", }, { key: "board", - icon: LayoutGrid, - title: "Gallery layout", + i18n_title: "project_modules.layout.board", }, { key: "gantt", - icon: GanttChartSquare, - title: "Timeline layout", + i18n_title: "project_modules.layout.timeline", }, ]; -export const MODULE_ORDER_BY_OPTIONS: { key: TModuleOrderByOptions; label: string }[] = [ +export const MODULE_ORDER_BY_OPTIONS: { + key: TModuleOrderByOptions; + i18n_label: string; +}[] = [ { key: "name", - label: "Name", + i18n_label: "project_modules.order_by.name", }, { key: "progress", - label: "Progress", + i18n_label: "project_modules.order_by.progress", }, { key: "issues_length", - label: "Number of issues", + i18n_label: "project_modules.order_by.issues", }, { key: "target_date", - label: "Due date", + i18n_label: "project_modules.order_by.due_date", }, { key: "created_at", - label: "Created date", + i18n_label: "project_modules.order_by.created_at", }, { key: "sort_order", - label: "Manual", + i18n_label: "project_modules.order_by.manual", }, ]; diff --git a/web/core/constants/notification.ts b/packages/constants/src/notification.ts similarity index 82% rename from web/core/constants/notification.ts rename to packages/constants/src/notification.ts index 36ab3c8eeda..cb267c4ad1f 100644 --- a/web/core/constants/notification.ts +++ b/packages/constants/src/notification.ts @@ -29,12 +29,13 @@ export type TNotificationTab = ENotificationTab.ALL | ENotificationTab.MENTIONS; export const NOTIFICATION_TABS = [ { - label: "All", + i18n_label: "notification.tabs.all", value: ENotificationTab.ALL, - count: (unReadNotification: TUnreadNotificationsCount) => unReadNotification?.total_unread_notifications_count || 0, + count: (unReadNotification: TUnreadNotificationsCount) => + unReadNotification?.total_unread_notifications_count || 0, }, { - label: "Mentions", + i18n_label: "notification.tabs.mentions", value: ENotificationTab.MENTIONS, count: (unReadNotification: TUnreadNotificationsCount) => unReadNotification?.mention_unread_notifications_count || 0, @@ -43,15 +44,15 @@ export const NOTIFICATION_TABS = [ export const FILTER_TYPE_OPTIONS = [ { - label: "Assigned to me", + i18n_label: "notification.filter.assigned", value: ENotificationFilterType.ASSIGNED, }, { - label: "Created by me", + i18n_label: "notification.filter.created", value: ENotificationFilterType.CREATED, }, { - label: "Subscribed by me", + i18n_label: "notification.filter.subscribed", value: ENotificationFilterType.SUBSCRIBED, }, ]; @@ -59,7 +60,7 @@ export const FILTER_TYPE_OPTIONS = [ export const NOTIFICATION_SNOOZE_OPTIONS = [ { key: "1_day", - label: "1 day", + i18n_label: "notification.snooze.1_day", value: () => { const date = new Date(); return new Date(date.getTime() + 24 * 60 * 60 * 1000); @@ -67,7 +68,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [ }, { key: "3_days", - label: "3 days", + i18n_label: "notification.snooze.3_days", value: () => { const date = new Date(); return new Date(date.getTime() + 3 * 24 * 60 * 60 * 1000); @@ -75,7 +76,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [ }, { key: "5_days", - label: "5 days", + i18n_label: "notification.snooze.5_days", value: () => { const date = new Date(); return new Date(date.getTime() + 5 * 24 * 60 * 60 * 1000); @@ -83,7 +84,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [ }, { key: "1_week", - label: "1 week", + i18n_label: "notification.snooze.1_week", value: () => { const date = new Date(); return new Date(date.getTime() + 7 * 24 * 60 * 60 * 1000); @@ -91,7 +92,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [ }, { key: "2_weeks", - label: "2 weeks", + i18n_label: "notification.snooze.2_weeks", value: () => { const date = new Date(); return new Date(date.getTime() + 14 * 24 * 60 * 60 * 1000); @@ -99,7 +100,7 @@ export const NOTIFICATION_SNOOZE_OPTIONS = [ }, { key: "custom", - label: "Custom", + i18n_label: "notification.snooze.custom", value: undefined, }, ]; diff --git a/packages/constants/src/page.ts b/packages/constants/src/page.ts new file mode 100644 index 00000000000..182cff40e33 --- /dev/null +++ b/packages/constants/src/page.ts @@ -0,0 +1,14 @@ +export enum EPageAccess { + PUBLIC = 0, + PRIVATE = 1, +} + +export type TCreatePageModal = { + isOpen: boolean; + pageAccess?: EPageAccess; +}; + +export const DEFAULT_CREATE_PAGE_MODAL_DATA: TCreatePageModal = { + isOpen: false, + pageAccess: EPageAccess.PUBLIC, +}; diff --git a/web/core/constants/profile.ts b/packages/constants/src/profile.ts similarity index 70% rename from web/core/constants/profile.ts rename to packages/constants/src/profile.ts index d6ccf658324..f7765a0cfc0 100644 --- a/web/core/constants/profile.ts +++ b/packages/constants/src/profile.ts @@ -1,48 +1,38 @@ -import React from "react"; -// icons -import { Activity, Bell, CircleUser, KeyRound, LucideProps, Settings2 } from "lucide-react"; - export const PROFILE_ACTION_LINKS: { key: string; - label: string; + i18n_label: string; href: string; highlight: (pathname: string) => boolean; - Icon: React.FC; }[] = [ { key: "profile", - label: "Profile", + i18n_label: "profile.actions.profile", href: `/profile`, highlight: (pathname: string) => pathname === "/profile/", - Icon: CircleUser, }, { key: "security", - label: "Security", + i18n_label: "profile.actions.security", href: `/profile/security`, highlight: (pathname: string) => pathname === "/profile/security/", - Icon: KeyRound, }, { key: "activity", - label: "Activity", + i18n_label: "profile.actions.activity", href: `/profile/activity`, highlight: (pathname: string) => pathname === "/profile/activity/", - Icon: Activity, }, { key: "appearance", - label: "Appearance", + i18n_label: "profile.actions.appearance", href: `/profile/appearance`, highlight: (pathname: string) => pathname.includes("/profile/appearance"), - Icon: Settings2, }, { key: "notifications", - label: "Notifications", + i18n_label: "profile.actions.notifications", href: `/profile/notifications`, highlight: (pathname: string) => pathname === "/profile/notifications/", - Icon: Bell, }, ]; @@ -50,7 +40,7 @@ export const PROFILE_VIEWER_TAB = [ { key: "summary", route: "", - label: "Summary", + i18n_label: "profile.tabs.summary", selected: "/", }, ]; @@ -59,24 +49,25 @@ export const PROFILE_ADMINS_TAB = [ { key: "assigned", route: "assigned", - label: "Assigned", + i18n_label: "profile.tabs.assigned", selected: "/assigned/", }, { + key: "created", route: "created", - label: "Created", + i18n_label: "profile.tabs.created", selected: "/created/", }, { key: "subscribed", route: "subscribed", - label: "Subscribed", + i18n_label: "profile.tabs.subscribed", selected: "/subscribed/", }, { key: "activity", route: "activity", - label: "Activity", + i18n_label: "profile.tabs.activity", selected: "/activity/", }, ]; diff --git a/web/core/constants/project.ts b/packages/constants/src/project.ts similarity index 61% rename from web/core/constants/project.ts rename to packages/constants/src/project.ts index e984cb968a3..a4d68295f43 100644 --- a/web/core/constants/project.ts +++ b/packages/constants/src/project.ts @@ -1,41 +1,65 @@ // icons -import { Globe2, Lock, LucideIcon } from "lucide-react"; -import { TProjectAppliedDisplayFilterKeys, TProjectOrderByOptions } from "@plane/types"; +import { + TProjectAppliedDisplayFilterKeys, + TProjectOrderByOptions, +} from "@plane/types"; -export const NETWORK_CHOICES: { +export type TNetworkChoiceIconKey = "Lock" | "Globe2"; + +export type TNetworkChoice = { key: 0 | 2; - label: string; + labelKey: string; + i18n_label: string; description: string; - icon: LucideIcon; -}[] = [ + iconKey: TNetworkChoiceIconKey; +}; + +export const NETWORK_CHOICES: TNetworkChoice[] = [ { key: 0, - label: "Private", - description: "Accessible only by invite", - icon: Lock, + labelKey: "Private", + i18n_label: "workspace_projects.network.private.title", + description: "workspace_projects.network.private.description", //"Accessible only by invite", + iconKey: "Lock", }, { key: 2, - label: "Public", - description: "Anyone in the workspace except Guests can join", - icon: Globe2, + labelKey: "Public", + i18n_label: "workspace_projects.network.public.title", + description: "workspace_projects.network.public.description", //"Anyone in the workspace except Guests can join", + iconKey: "Globe2", }, ]; export const GROUP_CHOICES = { - backlog: "Backlog", - unstarted: "Unstarted", - started: "Started", - completed: "Completed", - cancelled: "Cancelled", + backlog: { + key: "backlog", + i18n_label: "workspace_projects.state.backlog", + }, + unstarted: { + key: "unstarted", + i18n_label: "workspace_projects.state.unstarted", + }, + started: { + key: "started", + i18n_label: "workspace_projects.state.started", + }, + completed: { + key: "completed", + i18n_label: "workspace_projects.state.completed", + }, + cancelled: { + key: "cancelled", + i18n_label: "workspace_projects.state.cancelled", + }, }; export const PROJECT_AUTOMATION_MONTHS = [ - { label: "1 month", value: 1 }, - { label: "3 months", value: 3 }, - { label: "6 months", value: 6 }, - { label: "9 months", value: 9 }, - { label: "12 months", value: 12 }, + { i18n_label: "common.months_count", value: 1 }, + { i18n_label: "common.months_count", value: 3 }, + { i18n_label: "common.months_count", value: 6 }, + { i18n_label: "common.months_count", value: 9 }, + { i18n_label: "common.months_count", value: 12 }, ]; export const PROJECT_UNSPLASH_COVERS = [ @@ -59,55 +83,55 @@ export const PROJECT_UNSPLASH_COVERS = [ export const PROJECT_ORDER_BY_OPTIONS: { key: TProjectOrderByOptions; - label: string; + i18n_label: string; }[] = [ { key: "sort_order", - label: "Manual", + i18n_label: "workspace_projects.sort.manual", }, { key: "name", - label: "Name", + i18n_label: "workspace_projects.sort.name", }, { key: "created_at", - label: "Created date", + i18n_label: "workspace_projects.sort.created_at", }, { key: "members_length", - label: "Number of members", + i18n_label: "workspace_projects.sort.members_length", }, ]; export const PROJECT_DISPLAY_FILTER_OPTIONS: { key: TProjectAppliedDisplayFilterKeys; - label: string; + i18n_label: string; }[] = [ { key: "my_projects", - label: "My projects", + i18n_label: "workspace_projects.scope.my_projects", }, { key: "archived_projects", - label: "Archived", + i18n_label: "workspace_projects.scope.archived_projects", }, ]; export const PROJECT_ERROR_MESSAGES = { permissionError: { - title: "You don't have permission to perform this action.", - message: undefined, + i18n_title: "workspace_projects.error.permission", + i18n_message: undefined, }, cycleDeleteError: { - title: "Error", - message: "Failed to delete cycle", + i18n_title: "error", + i18n_message: "workspace_projects.error.cycle_delete", }, moduleDeleteError: { - title: "Error", - message: "Failed to delete module", + i18n_title: "error", + i18n_message: "workspace_projects.error.module_delete", }, issueDeleteError: { - title: "Error", - message: "Failed to delete issue", + i18n_title: "error", + i18n_message: "workspace_projects.error.issue_delete", }, }; diff --git a/packages/constants/src/spreadsheet.ts b/packages/constants/src/spreadsheet.ts new file mode 100644 index 00000000000..57723e44916 --- /dev/null +++ b/packages/constants/src/spreadsheet.ts @@ -0,0 +1 @@ +export const SPREADSHEET_SELECT_GROUP = "spreadsheet-issues"; diff --git a/packages/constants/src/state.ts b/packages/constants/src/state.ts index 790f7828647..9f5db17c7f2 100644 --- a/packages/constants/src/state.ts +++ b/packages/constants/src/state.ts @@ -5,6 +5,11 @@ export type TStateGroups = | "completed" | "cancelled"; +export type TDraggableData = { + groupKey: TStateGroups; + id: string; +}; + export const STATE_GROUPS: { [key in TStateGroups]: { key: TStateGroups; @@ -43,6 +48,13 @@ export const ARCHIVABLE_STATE_GROUPS = [ STATE_GROUPS.completed.key, STATE_GROUPS.cancelled.key, ]; +export const COMPLETED_STATE_GROUPS = [STATE_GROUPS.completed.key]; +export const PENDING_STATE_GROUPS = [ + STATE_GROUPS.backlog.key, + STATE_GROUPS.unstarted.key, + STATE_GROUPS.started.key, + STATE_GROUPS.cancelled.key, +]; export const PROGRESS_STATE_GROUPS_DETAILS = [ { @@ -66,3 +78,5 @@ export const PROGRESS_STATE_GROUPS_DETAILS = [ color: "#A3A3A3", }, ]; + +export const DISPLAY_WORKFLOW_PRO_CTA = false; diff --git a/packages/constants/src/swr.ts b/packages/constants/src/swr.ts index d3eef1cdfe8..e981dd1d0c1 100644 --- a/packages/constants/src/swr.ts +++ b/packages/constants/src/swr.ts @@ -6,3 +6,11 @@ export const DEFAULT_SWR_CONFIG = { refreshInterval: 600000, errorRetryCount: 3, }; + +export const WEB_SWR_CONFIG = { + refreshWhenHidden: false, + revalidateIfStale: true, + revalidateOnFocus: true, + revalidateOnMount: true, + errorRetryCount: 3, +}; diff --git a/web/core/constants/tab-indices.ts b/packages/constants/src/tab-indices.ts similarity index 85% rename from web/core/constants/tab-indices.ts rename to packages/constants/src/tab-indices.ts index 58a12f4174c..0668528893f 100644 --- a/web/core/constants/tab-indices.ts +++ b/packages/constants/src/tab-indices.ts @@ -54,7 +54,14 @@ export const PROJECT_CREATE_TAB_INDICES = [ "logo_props", ]; -export const PROJECT_CYCLE_TAB_INDICES = ["name", "description", "date_range", "cancel", "submit", "project_id"]; +export const PROJECT_CYCLE_TAB_INDICES = [ + "name", + "description", + "date_range", + "cancel", + "submit", + "project_id", +]; export const PROJECT_MODULE_TAB_INDICES = [ "name", @@ -67,9 +74,21 @@ export const PROJECT_MODULE_TAB_INDICES = [ "submit", ]; -export const PROJECT_VIEW_TAB_INDICES = ["name", "description", "filters", "cancel", "submit"]; +export const PROJECT_VIEW_TAB_INDICES = [ + "name", + "description", + "filters", + "cancel", + "submit", +]; -export const PROJECT_PAGE_TAB_INDICES = ["name", "public", "private", "cancel", "submit"]; +export const PROJECT_PAGE_TAB_INDICES = [ + "name", + "public", + "private", + "cancel", + "submit", +]; export enum ETabIndices { ISSUE_FORM = "issue-form", diff --git a/web/core/constants/themes.ts b/packages/constants/src/themes.ts similarity index 79% rename from web/core/constants/themes.ts rename to packages/constants/src/themes.ts index dd758481181..84e8c0d0b70 100644 --- a/web/core/constants/themes.ts +++ b/packages/constants/src/themes.ts @@ -1,9 +1,15 @@ -export const THEMES = ["light", "dark", "light-contrast", "dark-contrast", "custom"]; +export const THEMES = [ + "light", + "dark", + "light-contrast", + "dark-contrast", + "custom", +]; export interface I_THEME_OPTION { key: string; value: string; - label: string; + i18n_label: string; type: string; icon: { border: string; @@ -16,7 +22,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [ { key: "system_preference", value: "system", - label: "System preference", + i18n_label: "System preference", type: "light", icon: { border: "#DEE2E6", @@ -27,7 +33,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [ { key: "light", value: "light", - label: "Light", + i18n_label: "Light", type: "light", icon: { border: "#DEE2E6", @@ -38,7 +44,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [ { key: "dark", value: "dark", - label: "Dark", + i18n_label: "Dark", type: "dark", icon: { border: "#2E3234", @@ -49,7 +55,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [ { key: "light_contrast", value: "light-contrast", - label: "Light high contrast", + i18n_label: "Light high contrast", type: "light", icon: { border: "#000000", @@ -60,7 +66,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [ { key: "dark_contrast", value: "dark-contrast", - label: "Dark high contrast", + i18n_label: "Dark high contrast", type: "dark", icon: { border: "#FFFFFF", @@ -71,7 +77,7 @@ export const THEME_OPTIONS: I_THEME_OPTION[] = [ { key: "custom", value: "custom", - label: "Custom theme", + i18n_label: "Custom theme", type: "light", icon: { border: "#FFC9C9", diff --git a/packages/constants/src/user.ts b/packages/constants/src/user.ts index f10801807c5..99675694996 100644 --- a/packages/constants/src/user.ts +++ b/packages/constants/src/user.ts @@ -36,3 +36,40 @@ export enum EUserProjectRoles { MEMBER = 15, GUEST = 5, } + +export type TUserPermissionsLevel = EUserPermissionsLevel; + +export enum EUserPermissions { + ADMIN = 20, + MEMBER = 15, + GUEST = 5, +} +export type TUserPermissions = EUserPermissions; + +export type TUserAllowedPermissionsObject = { + create: TUserPermissions[]; + update: TUserPermissions[]; + delete: TUserPermissions[]; + read: TUserPermissions[]; +}; +export type TUserAllowedPermissions = { + workspace: { + [key: string]: Partial; + }; + project: { + [key: string]: Partial; + }; +}; + +export const USER_ALLOWED_PERMISSIONS: TUserAllowedPermissions = { + workspace: { + dashboard: { + read: [ + EUserPermissions.ADMIN, + EUserPermissions.MEMBER, + EUserPermissions.GUEST, + ], + }, + }, + project: {}, +}; diff --git a/packages/constants/src/views.ts b/packages/constants/src/views.ts new file mode 100644 index 00000000000..8ac837de807 --- /dev/null +++ b/packages/constants/src/views.ts @@ -0,0 +1,23 @@ +export enum EViewAccess { + PRIVATE, + PUBLIC, +} + +export const VIEW_ACCESS_SPECIFIERS: { + key: EViewAccess; + i18n_label: string; +}[] = [ + { key: EViewAccess.PUBLIC, i18n_label: "common.access.public" }, + { key: EViewAccess.PRIVATE, i18n_label: "common.access.private" }, +]; + +export const VIEW_SORTING_KEY_OPTIONS = [ + { key: "name", i18n_label: "project_view.sort_by.name" }, + { key: "created_at", i18n_label: "project_view.sort_by.created_at" }, + { key: "updated_at", i18n_label: "project_view.sort_by.updated_at" }, +]; + +export const VIEW_SORT_BY_OPTIONS = [ + { key: "asc", i18n_label: "common.order_by.asc" }, + { key: "desc", i18n_label: "common.order_by.desc" }, +]; diff --git a/web/core/constants/workspace-drafts.ts b/packages/constants/src/workspace-drafts.ts similarity index 100% rename from web/core/constants/workspace-drafts.ts rename to packages/constants/src/workspace-drafts.ts diff --git a/packages/constants/src/workspace.ts b/packages/constants/src/workspace.ts index d37decf3f2f..3bbb5b1f31f 100644 --- a/packages/constants/src/workspace.ts +++ b/packages/constants/src/workspace.ts @@ -1,3 +1,6 @@ +import { TStaticViewTypes } from "@plane/types"; +import { EUserWorkspaceRoles } from "./user"; + export const ORGANIZATION_SIZE = [ "Just myself", // TODO: translate "2-10", @@ -74,3 +77,182 @@ export const RESTRICTED_URLS = [ "instances", "instance", ]; + +export const WORKSPACE_SETTINGS = { + general: { + key: "general", + i18n_label: "workspace_settings.settings.general.title", + href: `/settings`, + access: [EUserWorkspaceRoles.ADMIN], + highlight: (pathname: string, baseUrl: string) => + pathname === `${baseUrl}/settings/`, + }, + members: { + key: "members", + i18n_label: "workspace_settings.settings.members.title", + href: `/settings/members`, + access: [EUserWorkspaceRoles.ADMIN], + highlight: (pathname: string, baseUrl: string) => + pathname === `${baseUrl}/settings/members/`, + }, + "billing-and-plans": { + key: "billing-and-plans", + i18n_label: "workspace_settings.settings.billing_and_plans.title", + href: `/settings/billing`, + access: [EUserWorkspaceRoles.ADMIN], + highlight: (pathname: string, baseUrl: string) => + pathname === `${baseUrl}/settings/billing/`, + }, + export: { + key: "export", + i18n_label: "workspace_settings.settings.exports.title", + href: `/settings/exports`, + access: [EUserWorkspaceRoles.ADMIN], + highlight: (pathname: string, baseUrl: string) => + pathname === `${baseUrl}/settings/exports/`, + }, + webhooks: { + key: "webhooks", + i18n_label: "workspace_settings.settings.webhooks.title", + href: `/settings/webhooks`, + access: [EUserWorkspaceRoles.ADMIN], + highlight: (pathname: string, baseUrl: string) => + pathname === `${baseUrl}/settings/webhooks/`, + }, + "api-tokens": { + key: "api-tokens", + i18n_label: "workspace_settings.settings.api_tokens.title", + href: `/settings/api-tokens`, + access: [EUserWorkspaceRoles.ADMIN], + highlight: (pathname: string, baseUrl: string) => + pathname === `${baseUrl}/settings/api-tokens/`, + }, +}; + +export const WORKSPACE_SETTINGS_LINKS: { + key: string; + i18n_label: string; + href: string; + access: EUserWorkspaceRoles[]; + highlight: (pathname: string, baseUrl: string) => boolean; +}[] = [ + WORKSPACE_SETTINGS["general"], + WORKSPACE_SETTINGS["members"], + WORKSPACE_SETTINGS["billing-and-plans"], + WORKSPACE_SETTINGS["export"], + WORKSPACE_SETTINGS["webhooks"], + WORKSPACE_SETTINGS["api-tokens"], +]; + +export const ROLE = { + [EUserWorkspaceRoles.GUEST]: "Guest", + [EUserWorkspaceRoles.MEMBER]: "Member", + [EUserWorkspaceRoles.ADMIN]: "Admin", +}; + +export const ROLE_DETAILS = { + [EUserWorkspaceRoles.GUEST]: { + i18n_title: "role_details.guest.title", + i18n_description: "role_details.guest.description", + }, + [EUserWorkspaceRoles.MEMBER]: { + i18n_title: "role_details.member.title", + i18n_description: "role_details.member.description", + }, + [EUserWorkspaceRoles.ADMIN]: { + i18n_title: "role_details.admin.title", + i18n_description: "role_details.admin.description", + }, +}; + +export const USER_ROLES = [ + { + value: "Product / Project Manager", + i18n_label: "user_roles.product_or_project_manager", + }, + { + value: "Development / Engineering", + i18n_label: "user_roles.development_or_engineering", + }, + { + value: "Founder / Executive", + i18n_label: "user_roles.founder_or_executive", + }, + { + value: "Freelancer / Consultant", + i18n_label: "user_roles.freelancer_or_consultant", + }, + { value: "Marketing / Growth", i18n_label: "user_roles.marketing_or_growth" }, + { + value: "Sales / Business Development", + i18n_label: "user_roles.sales_or_business_development", + }, + { + value: "Support / Operations", + i18n_label: "user_roles.support_or_operations", + }, + { + value: "Student / Professor", + i18n_label: "user_roles.student_or_professor", + }, + { value: "Human Resources", i18n_label: "user_roles.human_resources" }, + { value: "Other", i18n_label: "user_roles.other" }, +]; + +export const IMPORTERS_LIST = [ + { + provider: "github", + type: "import", + i18n_title: "importer.github.title", + i18n_description: "importer.github.description", + }, + { + provider: "jira", + type: "import", + i18n_title: "importer.jira.title", + i18n_description: "importer.jira.description", + }, +]; + +export const EXPORTERS_LIST = [ + { + provider: "csv", + type: "export", + i18n_title: "exporter.csv.title", + i18n_description: "exporter.csv.description", + }, + { + provider: "xlsx", + type: "export", + i18n_title: "exporter.excel.title", + i18n_description: "exporter.csv.description", + }, + { + provider: "json", + type: "export", + i18n_title: "exporter.json.title", + i18n_description: "exporter.csv.description", + }, +]; + +export const DEFAULT_GLOBAL_VIEWS_LIST: { + key: TStaticViewTypes; + i18n_label: string; +}[] = [ + { + key: "all-issues", + i18n_label: "default_global_view.all_issues", + }, + { + key: "assigned", + i18n_label: "default_global_view.assigned", + }, + { + key: "created", + i18n_label: "default_global_view.created", + }, + { + key: "subscribed", + i18n_label: "default_global_view.subscribed", + }, +]; diff --git a/packages/i18n/src/locales/en/core.json b/packages/i18n/src/locales/en/core.json new file mode 100644 index 00000000000..a17de227eb6 --- /dev/null +++ b/packages/i18n/src/locales/en/core.json @@ -0,0 +1,171 @@ +{ + "sidebar": { + "projects": "Projects", + "pages": "Pages", + "new_work_item": "New work item", + "home": "Home", + "your_work": "Your work", + "inbox": "Inbox", + "workspace": "Workspace", + "views": "Views", + "analytics": "Analytics", + "work_items": "Work items", + "cycles": "Cycles", + "modules": "Modules", + "intake": "Intake", + "drafts": "Drafts", + "favorites": "Favorites", + "pro": "Pro", + "upgrade": "Upgrade" + }, + + "auth": { + "common": { + "email": { + "label": "Email", + "placeholder": "name@company.com", + "errors": { + "required": "Email is required", + "invalid": "Email is invalid" + } + }, + "password": { + "label": "Password", + "set_password": "Set a password", + "placeholder": "Enter password", + "confirm_password": { + "label": "Confirm password", + "placeholder": "Confirm password" + }, + "current_password": { + "label": "Current password" + }, + "new_password": { + "label": "New password", + "placeholder": "Enter new password" + }, + "change_password": { + "label": { + "default": "Change password", + "submitting": "Changing password" + } + }, + "errors": { + "match": "Passwords don't match", + "empty": "Please enter your password", + "length": "Password length should me more than 8 characters", + "strength": { + "weak": "Password is weak", + "strong": "Password is strong" + } + }, + "submit": "Set password", + "toast": { + "change_password": { + "success": { + "title": "Success!", + "message": "Password changed successfully." + }, + "error": { + "title": "Error!", + "message": "Something went wrong. Please try again." + } + } + } + }, + "unique_code": { + "label": "Unique code", + "placeholder": "gets-sets-flys", + "paste_code": "Paste the code sent to your email", + "requesting_new_code": "Requesting new code", + "sending_code": "Sending code" + }, + "already_have_an_account": "Already have an account?", + "login": "Log in", + "create_account": "Create an account", + "new_to_plane": "New to Plane?", + "back_to_sign_in": "Back to sign in", + "resend_in": "Resend in {seconds} seconds", + "sign_in_with_unique_code": "Sign in with unique code", + "forgot_password": "Forgot your password?" + }, + "sign_up": { + "header": { + "label": "Create an account to start managing work with your team.", + "step": { + "email": { + "header": "Sign up", + "sub_header": "" + }, + "password": { + "header": "Sign up", + "sub_header": "Sign up using an email-password combination." + }, + "unique_code": { + "header": "Sign up", + "sub_header": "Sign up using a unique code sent to the email address above." + } + } + }, + "errors": { + "password": { + "strength": "Try setting-up a strong password to proceed" + } + } + }, + "sign_in": { + "header": { + "label": "Log in to start managing work with your team.", + "step": { + "email": { + "header": "Log in or sign up", + "sub_header": "" + }, + "password": { + "header": "Log in or sign up", + "sub_header": "Use your email-password combination to log in." + }, + "unique_code": { + "header": "Log in or sign up", + "sub_header": "Log in using a unique code sent to the email address above." + } + } + } + }, + "forgot_password": { + "title": "Reset your password", + "description": "Enter your user account's verified email address and we will send you a password reset link.", + "email_sent": "We sent the reset link to your email address", + "send_reset_link": "Send reset link", + "errors": { + "smtp_not_enabled": "We see that your god hasn't enabled SMTP, we will not be able to send a password reset link" + }, + "toast": { + "success": { + "title": "Email sent", + "message": "Check your inbox for a link to reset your password. If it doesn't appear within a few minutes, check your spam folder." + }, + "error": { + "title": "Error!", + "message": "Something went wrong. Please try again." + } + } + }, + "reset_password": { + "title": "Set new password", + "description": "Secure your account with a strong password" + }, + "set_password": { + "title": "Secure your account", + "description": "Setting password helps you login securely" + }, + "sign_out": { + "toast": { + "error": { + "title": "Error!", + "message": "Failed to sign out. Please try again." + } + } + } + } +} diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index 728e7533842..a1705decebf 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -14,10 +14,13 @@ "description": "Description", "search": "Search", "add_member": "Add member", + "adding_members": "Adding members", "remove_member": "Remove member", "add_members": "Add members", + "adding_member": "Adding members", "remove_members": "Remove members", "add": "Add", + "adding": "Adding", "remove": "Remove", "add_new": "Add new", "remove_selected": "Remove selected", @@ -32,18 +35,16 @@ "password": "Password", "change_cover": "Change cover", "language": "Language", - "saving": "Saving...", + "saving": "Saving", "save_changes": "Save changes", "deactivate_account": "Deactivate account", "deactivate_account_description": "When deactivating an account, all of the data and resources within that account will be permanently removed and cannot be recovered.", "profile_settings": "Profile settings", "your_account": "Your account", - "profile": "Profile", "security": "Security", "activity": "Activity", "appearance": "Appearance", "notifications": "Notifications", - "inbox": "Inbox", "workspaces": "Workspaces", "create_workspace": "Create workspace", "invitations": "Invitations", @@ -52,20 +53,7 @@ "created": "Created", "subscribed": "Subscribed", "you_do_not_have_the_permission_to_access_this_page": "You do not have the permission to access this page.", - "failed_to_sign_out_please_try_again": "Failed to sign out. Please try again.", - "password_changed_successfully": "Password changed successfully.", "something_went_wrong_please_try_again": "Something went wrong. Please try again.", - "change_password": "Change password", - "passwords_dont_match": "Passwords don't match", - "current_password": "Current password", - "new_password": "New password", - "confirm_password": "Confirm password", - "this_field_is_required": "This field is required", - "changing_password": "Changing password", - "please_enter_your_password": "Please enter your password.", - "password_length_should_me_more_than_8_characters": "Password length should me more than 8 characters.", - "password_is_weak": "Password is weak.", - "password_is_strong": "Password is strong.", "load_more": "Load more", "select_or_customize_your_interface_color_scheme": "Select or customize your interface color scheme.", "theme": "Theme", @@ -93,52 +81,26 @@ "theme_updated_successfully": "Theme updated successfully", "failed_to_update_the_theme": "Failed to update the theme", "email_notifications": "Email notifications", - "stay_in_the_loop_on_issues_you_are_subscribed_to_enable_this_to_get_notified": "Stay in the loop on Issues you are subscribed to. Enable this to get notified.", + "stay_in_the_loop_on_issues_you_are_subscribed_to_enable_this_to_get_notified": "Stay in the loop on Work items you are subscribed to. Enable this to get notified.", "email_notification_setting_updated_successfully": "Email notification setting updated successfully", "failed_to_update_email_notification_setting": "Failed to update email notification setting", "notify_me_when": "Notify me when", "property_changes": "Property changes", - "property_changes_description": "Notify me when issue's properties like assignees, priority, estimates or anything else changes.", + "property_changes_description": "Notify me when work items' properties like assignees, priority, estimates or anything else changes.", "state_change": "State change", - "state_change_description": "Notify me when the issues moves to a different state", - "issue_completed": "Issue completed", - "issue_completed_description": "Notify me only when an issue is completed", + "state_change_description": "Notify me when the work items moves to a different state", + "issue_completed": "Work item completed", + "issue_completed_description": "Notify me only when a work item is completed", "comments": "Comments", - "comments_description": "Notify me when someone leaves a comment on the issue", + "comments_description": "Notify me when someone leaves a comment on the work item", "mentions": "Mentions", "mentions_description": "Notify me only when someone mentions me in the comments or description", - "create_your_workspace": "Create your workspace", - "only_your_instance_admin_can_create_workspaces": "Only your instance admin can create workspaces", - "only_your_instance_admin_can_create_workspaces_description": "If you know your instance admin's email address, click the button below to get in touch with them.", - "go_back": "Go back", - "request_instance_admin": "Request instance admin", - "plane_logo": "Plane logo", - "workspace_creation_disabled": "Workspace creation disabled", - "workspace_request_subject": "Requesting a new workspace", - "workspace_request_body": "Hi instance admin(s),\n\nPlease create a new workspace with the URL [/workspace-name] for [purpose of creating the workspace].\n\nThanks,\n{{firstName}} {{lastName}}\n{{email}}", - "creating_workspace": "Creating workspace", - "workspace_created_successfully": "Workspace created successfully", - "create_workspace_page": "Create workspace page", - "workspace_could_not_be_created_please_try_again": "Workspace could not be created. Please try again.", - "workspace_could_not_be_created_please_try_again_description": "Some error occurred while creating workspace. Please try again.", - "this_is_a_required_field": "This is a required field.", - "name_your_workspace": "Name your workspace", - "workspaces_names_can_contain_only_space_dash_and_alphanumeric_characters": "Workspaces names can contain only (' '), ('-'), ('_') and alphanumeric characters.", - "limit_your_name_to_80_characters": "Limit your name to 80 characters.", - "set_your_workspace_url": "Set your workspace's URL", - "limit_your_url_to_48_characters": "Limit your URL to 48 characters.", - "how_many_people_will_use_this_workspace": "How many people will use this workspace?", - "how_many_people_will_use_this_workspace_description": "This will help us to determine the number of seats you need to purchase.", - "select_a_range": "Select a range", - "urls_can_contain_only_dash_and_alphanumeric_characters": "URLs can contain only ('-') and alphanumeric characters.", - "something_familiar_and_recognizable_is_always_best": "Something familiar and recognizable is always best.", - "workspace_url_is_already_taken": "Workspace URL is already taken!", "old_password": "Old password", "general_settings": "General settings", "sign_out": "Sign out", "signing_out": "Signing out", "active_cycles": "Active cycles", - "active_cycles_description": "Monitor cycles across projects, track high-priority issues, and zoom in cycles that need attention.", + "active_cycles_description": "Monitor cycles across projects, track high-priority work items, and zoom in cycles that need attention.", "on_demand_snapshots_of_all_your_cycles": "On-demand snapshots of all your cycles", "upgrade": "Upgrade", "10000_feet_view": "10,000-feet view of all active cycles.", @@ -147,19 +109,17 @@ "get_snapshot_of_each_active_cycle_description": "Track high-level metrics for all active cycles, see their state of progress, and get a sense of scope against deadlines.", "compare_burndowns": "Compare burndowns.", "compare_burndowns_description": "Monitor how each of your teams are performing with a peek into each cycle's burndown report.", - "quickly_see_make_or_break_issues": "Quickly see make-or-break issues.", - "quickly_see_make_or_break_issues_description": "Preview high-priority issues for each cycle against due dates. See all of them per cycle in one click.", + "quickly_see_make_or_break_issues": "Quickly see make-or-break work items.", + "quickly_see_make_or_break_issues_description": "Preview high-priority work items for each cycle against due dates. See all of them per cycle in one click.", "zoom_into_cycles_that_need_attention": "Zoom into cycles that need attention.", "zoom_into_cycles_that_need_attention_description": "Investigate the state of any cycle that doesn't conform to expectations in one click.", "stay_ahead_of_blockers": "Stay ahead of blockers.", "stay_ahead_of_blockers_description": "Spot challenges from one project to another and see inter-cycle dependencies that aren't obvious from any other view.", "analytics": "Analytics", "workspace_invites": "Workspace invites", - "workspace_settings": "Workspace settings", "enter_god_mode": "Enter god mode", "workspace_logo": "Workspace logo", - "new_issue": "New issue", - "home": "Home", + "new_issue": "New work item", "your_work": "Your work", "drafts": "Drafts", "projects": "Projects", @@ -187,7 +147,7 @@ "create_project": "Create project", "failed_to_remove_project_from_favorites": "Couldn't remove the project from favorites. Please try again.", "project_created_successfully": "Project created successfully", - "project_created_successfully_description": "Project created successfully. You can now start adding issues to it.", + "project_created_successfully_description": "Project created successfully. You can now start adding work items to it.", "project_cover_image_alt": "Project cover image", "name_is_required": "Name is required", "title_should_be_less_than_255_characters": "Title should be less than 255 characters", @@ -195,12 +155,17 @@ "project_id_must_be_at_least_1_character": "Project ID must at least be of 1 character", "project_id_must_be_at_most_5_characters": "Project ID must at most be of 5 characters", "project_id": "Project ID", - "project_id_tooltip_content": "Helps you identify issues in the project uniquely. Max 5 characters.", - "description_placeholder": "Description...", + "project_id_tooltip_content": "Helps you identify work items in the project uniquely. Max 5 characters.", + "description_placeholder": "Description", "only_alphanumeric_non_latin_characters_allowed": "Only Alphanumeric & Non-latin characters are allowed.", "project_id_is_required": "Project ID is required", + "project_id_allowed_char": "Only Alphanumeric & Non-latin characters are allowed.", + "project_id_min_char": "Project ID must at least be of 1 character", + "project_id_max_char": "Project ID must at most be of 5 characters", + "project_description_placeholder": "Enter project description", "select_network": "Select network", "lead": "Lead", + "date_range": "Date range", "private": "Private", "public": "Public", "accessible_only_by_invite": "Accessible only by invite", @@ -222,23 +187,22 @@ "join_the_project_to_rearrange": "Join the project to rearrange", "drag_to_rearrange": "Drag to rearrange", "congrats": "Congrats!", - "project": "Project", "open_project": "Open project", - "issues": "Issues", + "issues": "Work items", "cycles": "Cycles", "modules": "Modules", "pages": "Pages", "intake": "Intake", "time_tracking": "Time Tracking", "work_management": "Work management", - "projects_and_issues": "Projects and issues", + "projects_and_issues": "Projects and work items", "projects_and_issues_description": "Toggle these on or off this project.", "cycles_description": "Timebox work as you see fit per project and change frequency from one period to the next.", "modules_description": "Group work into sub-project-like set-ups with their own leads and assignees.", "views_description": "Save sorts, filters, and display options for later or share them.", "pages_description": "Write anything like you write anything.", - "intake_description": "Stay in the loop on Issues you are subscribed to. Enable this to get notified.", - "time_tracking_description": "Track time spent on issues and projects.", + "intake_description": "Stay in the loop on Work items you are subscribed to. Enable this to get notified.", + "time_tracking_description": "Track time spent on work items and projects.", "work_management_description": "Manage your work and projects with ease.", "documentation": "Documentation", "message_support": "Message support", @@ -269,29 +233,30 @@ "back_to_home": "Back to home", "workspace_name": "workspace-name", "deactivate_your_account": "Deactivate your account", - "deactivate_your_account_description": "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.", + "deactivate_your_account_description": "Once deactivated, you can't be assigned work items and be billed for your workspace. To reactivate your account, you will need an invite to a workspace at this email address.", "deactivating": "Deactivating", "confirm": "Confirm", + "confirming": "Confirming", "draft_created": "Draft created", - "issue_created_successfully": "Issue created successfully", + "issue_created_successfully": "Work item created successfully", "draft_creation_failed": "Draft creation failed", - "issue_creation_failed": "Issue creation failed", - "draft_issue": "Draft issue", - "issue_updated_successfully": "Issue updated successfully", - "issue_could_not_be_updated": "Issue could not be updated", + "issue_creation_failed": "Work item creation failed", + "draft_issue": "Draft work item", + "issue_updated_successfully": "Work item updated successfully", + "issue_could_not_be_updated": "Work item could not be updated", "create_a_draft": "Create a draft", "save_to_drafts": "Save to Drafts", "save": "Save", "update": "Update", "updating": "Updating", - "create_new_issue": "Create new issue", + "create_new_issue": "Create new work item", "editor_is_not_ready_to_discard_changes": "Editor is not ready to discard changes", - "failed_to_move_issue_to_project": "Failed to move issue to project", + "failed_to_move_issue_to_project": "Failed to move work item to project", "create_more": "Create more", "add_to_project": "Add to project", "discard": "Discard", - "duplicate_issue_found": "Duplicate issue found", - "duplicate_issues_found": "Duplicate issues found", + "duplicate_issue_found": "Duplicate work item found", + "duplicate_issues_found": "Duplicate work items found", "no_matching_results": "No matching results", "title_is_required": "Title is required", "title": "Title", @@ -309,11 +274,1877 @@ "labels": "Labels", "create_new_label": "Create new label", "start_date": "Start date", + "end_date": "End date", "due_date": "Due date", - "cycle": "Cycle", "estimate": "Estimate", - "change_parent_issue": "Change parent issue", - "remove_parent_issue": "Remove parent issue", + "change_parent_issue": "Change parent work item", + "remove_parent_issue": "Remove parent work item", "add_parent": "Add parent", - "loading_members": "Loading members..." + "loading_members": "Loading members", + "view_link_copied_to_clipboard": "View link copied to clipboard.", + "required": "Required", + "optional": "Optional", + "Cancel": "Cancel", + "edit": "Edit", + "archive": "Archive", + "restor": "Restore", + "open_in_new_tab": "Open in new tab", + "delete": "Delete", + "deleting": "Deleting", + "make_a_copy": "Make a copy", + "move_to_project": "Move to project", + "good": "Good", + "morning": "morning", + "afternoon": "afternoon", + "evening": "evening", + "show_all": "Show all", + "show_less": "Show less", + "no_data_yet": "No Data yet", + "syncing": "Syncing", + "add_work_item": "Add work item", + "advanced_description_placeholder": "Press '/' for commands", + "create_work_item": "Create work item", + "attachments": "Attachments", + "declining": "Declining", + "declined": "Declined", + "decline": "Decline", + "unassigned": "Unassigned", + "work_items": "Work items", + "add_link": "Add link", + "points": "Points", + "no_assignee": "No assignee", + "no_assignees_yet": "No assignees yet", + "no_labels_yet": "No labels yet", + "ideal": "Ideal", + "current": "Current", + "no_matching_members": "No matching members", + "leaving": "Leaving", + "removing": "Removing", + "leave": "Leave", + "refresh": "Refresh", + "refreshing": "Refreshing", + "refresh_status": "Refresh status", + "prev": "Prev", + "next": "Next", + "re_generating": "Re-generating", + "re_generate": "Re-generate", + "re_generate_key": "Re-generate key", + "export": "Export", + "member": "{count, plural, one{# member} other{# members}}", + + "project_view": { + "sort_by": { + "created_at": "Created at", + "updated_at": "Updated at", + "name": "Name" + } + }, + + "toast": { + "success": "Success!", + "error": "Error!" + }, + + "links": { + "toasts": { + "created": { + "title": "Link created", + "message": "The link has been successfully created" + }, + "not_created": { + "title": "Link not created", + "message": "The link could not be created" + }, + "updated": { + "title": "Link updated", + "message": "The link has been successfully updated" + }, + "not_updated": { + "title": "Link not updated", + "message": "The link could not be updated" + }, + "removed": { + "title": "Link removed", + "message": "The link has been successfully removed" + }, + "not_removed": { + "title": "Link not removed", + "message": "The link could not be removed" + } + } + }, + + "home": { + "empty": { + "create_project": { + "title": "Create a project", + "description": "Most things start with a project in Plane.", + "cta": "Get started" + }, + "invite_team": { + "title": "Invite your team", + "description": "Build, ship, and manage with coworkers.", + "cta": "Get them in" + }, + "configure_workspace": { + "title": "Set up your workspace.", + "description": "Turn features on or off or go beyond that.", + "cta": "Configure this workspace" + }, + "personalize_account": { + "title": "Make Plane yours.", + "description": "Choose your picture, colors, and more.", + "cta": "Personalize now" + }, + "widgets": { + "title": "It's Quiet Without Widgets, Turn Them On", + "description": "It looks like all your widgets are turned off. Enable them\nnow to enhance your experience!", + "primary_button": { + "text": "Manage widgets" + } + } + }, + "quick_links": { + "empty": "Save links to work things that you'd like handy.", + "add": "Add quick Link", + "title": "Quicklink", + "title_plural": "Quicklinks" + }, + "recents": { + "title": "Recents", + "empty": { + "project": "Your recent projects will appear here once you visit one.", + "page": "Your recent pages will appear here once you visit one.", + "issue": "Your recent work items will appear here once you visit one.", + "default": "You don't have any recent items yet." + }, + "filters": { + "all": "All items", + "projects": "Projects", + "pages": "Pages", + "issues": "Work items" + } + }, + "new_at_plane": { + "title": "New at Plane" + }, + "quick_tutorial": { + "title": "Quick tutorial" + }, + "widget": { + "reordered_successfully": "Widget reordered successfully.", + "reordering_failed": "Error occurred while reordering widget." + }, + "manage_widgets": "Manage widgets", + "title": "Home", + "star_us_on_github": "Star us on GitHub" + }, + + "link": { + "modal": { + "url": { + "text": "URL", + "required": "URL is invalid", + "placeholder": "Type or paste a URL" + }, + "title": { + "text": "Display title", + "placeholder": "What you'd like to see this link as" + } + } + }, + + "common": { + "all": "All", + "states": "States", + "state": "State", + "state_groups": "State groups", + "priority": "Priority", + "team_project": "Team project", + "project": "Project", + "cycle": "Cycle", + "cycles": "Cycles", + "module": "Module", + "modules": "Modules", + "labels": "Labels", + "assignees": "Assignees", + "assignee": "Assignee", + "created_by": "Created by", + "none": "None", + "link": "Link", + "estimate": "Estimate", + "layout": "Layout", + "filters": "Filters", + "display": "Display", + "load_more": "Load more", + "activity": "Activity", + "analytics": "Analytics", + "dates": "Dates", + "success": "Success!", + "something_went_wrong": "Something went wrong", + "error": { + "label": "Error!", + "message": "Some error occurred. Please try again." + }, + "group_by": "Group by", + "epic": "Epic", + "epics": "Epics", + "work_item": "Work item", + "sub_work_item": "Sub-work item", + "add": "Add", + "warning": "Warning", + "updating": "Updating", + "adding": "Adding", + "update": "Update", + "creating": "Creating", + "create": "Create", + "cancel": "Cancel", + "description": "Description", + "title": "Title", + "attachment": "Attachment", + "general": "General", + "features": "Features", + "automation": "Automation", + "project_name": "Project name", + "project_id": "Project ID", + "project_timezone": "Project Timezone", + "created_on": "Created on", + "update_project": "Update project", + "identifier_already_exists": "Identifier already exists", + "add_more": "Add more", + "defaults": "Defaults", + "add_label": "Add label", + "estimates": "Estimates", + "customize_time_range": "Customize time range", + "loading": "Loading", + "attachments": "Attachments", + "properties": "Properties", + "parent": "Parent", + "remove": "Remove", + "archiving": "Archiving", + "archive": "Archive", + "access": { + "public": "Public", + "private": "Private" + }, + "done": "Done", + "sub_work_items": "Sub-work items", + "comment": "Comment", + "workspace_level": "Workspace level", + "order_by": { + "label": "Order by", + "manual": "Manual", + "last_created": "Last created", + "last_updated": "Last updated", + "start_date": "Start date", + "due_date": "Due date", + "asc": "Ascending", + "desc": "Descending", + "updated_on": "Updated on" + }, + "sort": { + "asc": "Ascending", + "desc": "Descending", + "created_on": "Created on", + "updated_on": "Updated on" + }, + "comments": "Comments", + "updates": "Updates", + "clear_all": "Clear all", + "copied": "Copied!", + "link_copied": "Link copied!", + "link_copied_to_clipboard": "Link copied to clipboard", + "copied_to_clipboard": "Work item link copied to clipboard", + "is_copied_to_clipboard": "Work item is copied to clipboard", + "no_links_added_yet": "No links added yet", + "add_link": "Add link", + "links": "Links", + "go_to_workspace": "Go to workspace", + "progress": "Progress", + "optional": "Optional", + "join": "Join", + "go_back": "Go back", + "continue": "Continue", + "resend": "Resend", + "relations": "Relations", + "errors": { + "default": { + "title": "Error!", + "message": "Something went wrong. Please try again." + }, + "required": "This field is required", + "entity_required": "{entity} is required" + }, + "update_link": "Update link", + "attach": "Attach", + "create_new": "Create new", + "add_existing": "Add existing", + "type_or_paste_a_url": "Type or paste a URL", + "url_is_invalid": "URL is invalid", + "display_title": "Display title", + "link_title_placeholder": "What you'd like to see this link as", + "url": "URL", + "side_peek": "Side Peek", + "modal": "Modal", + "full_screen": "Full Screen", + "close_peek_view": "Close the peek view", + "toggle_peek_view_layout": "Toggle peek view layout", + "options": "Options", + "duration": "Duration", + "today": "Today", + "week": "Week", + "month": "Month", + "quarter": "Quarter", + "press_for_commands": "Press '/' for commands", + "click_to_add_description": "Click to add description", + "search": { + "label": "Search", + "placeholder": "Type to search", + "no_matches_found": "No matches found", + "no_matching_results": "No matching results" + }, + "actions": { + "edit": "Edit", + "make_a_copy": "Make a copy", + "open_in_new_tab": "Open in new tab", + "copy_link": "Copy link", + "archive": "Archive", + "delete": "Delete", + "remove_relation": "Remove relation", + "subscribe": "Subscribe", + "unsubscribe": "Unsubscribe", + "clear_sorting": "Clear sorting", + "show_weekends": "Show weekends", + "enable": "Enable", + "disable": "Disable" + }, + "name": "Name", + "discard": "Discard", + "confirm": "Confirm", + "confirming": "Confirming", + "read_the_docs": "Read the docs", + "default": "Default", + "active": "Active", + "enabled": "Enabled", + "disabled": "Disabled", + "mandate": "Mandate", + "mandatory": "Mandatory", + "yes": "Yes", + "no": "No", + "please_wait": "Please wait", + "enabling": "Enabling", + "disabling": "Disabling", + "beta": "Beta", + "or": "or", + "next": "Next", + "back": "Back", + "cancelling": "Cancelling", + "configuring": "Configuring", + "clear": "Clear", + "import": "Import", + "connect": "Connect", + "authorizing": "Authorizing", + "processing": "Processing", + "no_data_available": "No data available", + "from": "from {name}", + "authenticated": "Authenticated", + "select": "Select", + "upgrade": "Upgrade", + "add_seats": "Add Seats", + "label": "Label", + "priorities": "Priorities", + "projects": "Projects", + "workspace": "Workspace", + "workspaces": "Workspaces", + "team": "Team", + "teams": "Teams", + "entity": "Entity", + "entities": "Entities", + "task": "Task", + "tasks": "Tasks", + "section": "Section", + "sections": "Sections", + "edit": "Edit", + "connecting": "Connecting", + "connected": "Connected", + "disconnect": "Disconnect", + "disconnecting": "Disconnecting", + "installing": "Installing", + "install": "Install" + }, + + "form": { + "title": { + "required": "Title is required", + "max_length": "Title should be less than {length} characters" + } + }, + + "entity": { + "grouping_title": "{entity} Grouping", + "priority": "{entity} Priority", + "all": "All {entity}", + "drop_here_to_move": "Drop here to move the {entity}", + "delete": { + "label": "Delete {entity}", + "success": "{entity} deleted successfully", + "failed": "{entity} delete failed" + }, + "update": { + "failed": "{entity} update failed", + "success": "{entity} updated successfully" + }, + "link_copied_to_clipboard": "{entity} link copied to clipboard", + "fetch": { + "failed": "Error fetching {entity}" + }, + "add": { + "success": "{entity} added successfully", + "failed": "Error adding {entity}" + } + }, + + "epic": { + "all": "All Epics", + "label": "{count, plural, one {Epic} other {Epics}}", + "new": "New Epic", + "adding": "Adding epic", + "create": { + "success": "Epic created successfully" + }, + "add": { + "press_enter": "Press 'Enter' to add another epic", + "label": "Add Epic" + }, + "title": { + "label": "Epic Title", + "required": "Epic title is required." + } + }, + + "issue": { + "label": "{count, plural, one {Work item} other {Work items}}", + "all": "All Work items", + "edit": "Edit work item", + "title": { + "label": "Work item title", + "required": "Work item title is required." + }, + "add": { + "press_enter": "Press 'Enter' to add another work item", + "label": "Add work item", + "cycle": { + "failed": "Work item could not be added to the cycle. Please try again.", + "success": "{count, plural, one {Work item} other {Work items}} added to the cycle successfully.", + "loading": "Adding {count, plural, one {work item} other {work items}} to the cycle" + }, + "assignee": "Add assignees", + "start_date": "Add start date", + "due_date": "Add due date", + "parent": "Add parent work item", + "sub_issue": "Add sub-work item", + "relation": "Add relation", + "link": "Add link", + "existing": "Add existing work item" + }, + "remove": { + "label": "Remove work item", + "cycle": { + "loading": "Removing work item from the cycle", + "success": "Work item removed from the cycle successfully.", + "failed": "Work item could not be removed from the cycle. Please try again." + }, + "module": { + "loading": "Removing work item from the module", + "success": "Work item removed from the module successfully.", + "failed": "Work item could not be removed from the module. Please try again." + }, + "parent": { + "label": "Remove parent work item" + } + }, + "new": "New Work item", + "adding": "Adding work item", + "create": { + "success": "Work item created successfully" + }, + "priority": { + "urgent": "Urgent", + "high": "High", + "medium": "Medium", + "low": "Low" + }, + "display": { + "properties": { + "label": "Display Properties", + "id": "ID", + "issue_type": "Work item Type", + "sub_issue_count": "Sub-work item count", + "attachment_count": "Attachment count", + "created_on": "Created on", + "sub_issue": "Sub-work item" + }, + "extra": { + "show_sub_issues": "Show sub-work items", + "show_empty_groups": "Show empty groups" + } + }, + "layouts": { + "ordered_by_label": "This layout is ordered by", + "list": "List", + "kanban": "Board", + "calendar": "Calendar", + "spreadsheet": "Table", + "gantt": "Timeline", + "title": { + "list": "List Layout", + "kanban": "Board Layout", + "calendar": "Calendar Layout", + "spreadsheet": "Table Layout", + "gantt": "Timeline Layout" + } + }, + "states": { + "active": "Active", + "backlog": "Backlog" + }, + "comments": { + "placeholder": "Add comment", + "switch": { + "private": "Switch to private comment", + "public": "Switch to public comment" + }, + "create": { + "success": "Comment created successfully", + "error": "Comment creation failed. Please try again later." + }, + "update": { + "success": "Comment updated successfully", + "error": "Comment update failed. Please try again later." + }, + "remove": { + "success": "Comment removed successfully", + "error": "Comment remove failed. Please try again later." + }, + "upload": { + "error": "Asset upload failed. Please try again later." + } + }, + "empty_state": { + "issue_detail": { + "title": "Work item does not exist", + "description": "The work item you are looking for does not exist, has been archived, or has been deleted.", + "primary_button": { + "text": "View other work items" + } + } + }, + "sibling": { + "label": "Sibling work items" + }, + "archive": { + "description": "Only completed or canceled\nwork items can be archived", + "label": "Archive Work item", + "confirm_message": "Are you sure you want to archive the work item? All your archived work items can be restored later.", + "success": { + "label": "Archive success", + "message": "Your archives can be found in project archives." + }, + "failed": { + "message": "Work item could not be archived. Please try again." + } + }, + "restore": { + "success": { + "title": "Restore success", + "message": "Your work item can be found in project work items." + }, + "failed": { + "message": "Work item could not be restored. Please try again." + } + }, + "relation": { + "relates_to": "Relates to", + "duplicate": "Duplicate of", + "blocked_by": "Blocked by", + "blocking": "Blocking" + }, + "copy_link": "Copy work item link", + "delete": { + "label": "Delete work item", + "error": "Error deleting work item" + }, + "subscription": { + "actions": { + "subscribed": "Work item subscribed successfully", + "unsubscribed": "Work item unsubscribed successfully" + } + }, + "select": { + "error": "Please select at least one work item", + "empty": "No work items selected", + "add_selected": "Add selected work items" + }, + "open_in_full_screen": "Open work item in full screen" + }, + + "attachment": { + "error": "File could not be attached. Try uploading again.", + "only_one_file_allowed": "Only one file can be uploaded at a time.", + "file_size_limit": "File must be of {size}MB or less in size.", + "drag_and_drop": "Drag and drop anywhere to upload", + "delete": "Delete attachment" + }, + + "label": { + "select": "Select label", + "create": { + "success": "Label created successfully", + "failed": "Label creation failed", + "already_exists": "Label already exists", + "type": "Type to add a new label" + } + }, + + "sub_work_item": { + "update": { + "success": "Sub-work item updated successfully", + "error": "Error updating sub-work item" + }, + "remove": { + "success": "Sub-work item removed successfully", + "error": "Error removing sub-work item" + } + }, + + "view": { + "label": "{count, plural, one {View} other {Views}}", + "create": { + "label": "Create View" + }, + "update": { + "label": "Update View" + } + }, + + "inbox_issue": { + "status": { + "pending": { + "title": "Pending", + "description": "Pending" + }, + "declined": { + "title": "Declined", + "description": "Declined" + }, + "snoozed": { + "title": "Snoozed", + "description": "{days, plural, one{# day} other{# days}} to go" + }, + "accepted": { + "title": "Accepted", + "description": "Accepted" + }, + "duplicate": { + "title": "Duplicate", + "description": "Duplicate" + } + }, + "modals": { + "decline": { + "title": "Decline work item", + "content": "Are you sure you want to decline work item {value}?" + }, + "delete": { + "title": "Delete work item", + "content": "Are you sure you want to delete work item {value}?", + "success": "Work item deleted successfully" + } + }, + "errors": { + "snooze_permission": "Only project admins can snooze/Un-snooze work items", + "accept_permission": "Only project admins can accept work items", + "decline_permission": "Only project admins can deny work items" + }, + "actions": { + "accept": "Accept", + "decline": "Decline", + "snooze": "Snooze", + "unsnooze": "Un snooze", + "copy": "Copy work item link", + "delete": "Delete", + "open": "Open work item", + "mark_as_duplicate": "Mark as duplicate", + "move": "Move {value} to project work items" + }, + "source": { + "in-app": "in-app" + }, + "order_by": { + "created_at": "Created at", + "updated_at": "Updated at", + "id": "ID" + }, + "label": "Intake", + "page_label": "{workspace} - Intake", + "modal": { + "title": "Create intake work item" + }, + "tabs": { + "open": "Open", + "closed": "Closed" + }, + "empty_state": { + "sidebar_open_tab": { + "title": "No open work items", + "description": "Find open work items here. Create new work item." + }, + "sidebar_closed_tab": { + "title": "No closed work items", + "description": "All the work items whether accepted or declined can be found here." + }, + "sidebar_filter": { + "title": "No matching work items", + "description": "No work item matches filter applied in intake. Create a new work item." + }, + "detail": { + "title": "Select a work item to view its details." + } + } + }, + + "workspace_creation": { + "heading": "Create your workspace", + "subheading": "To start using Plane, you need to create or join a workspace.", + "form": { + "name": { + "label": "Name your workspace", + "placeholder": "Something familiar and recognizable is always best." + }, + "url": { + "label": "Set your workspace's URL", + "placeholder": "Type or paste a URL", + "edit_slug": "You can only edit the slug of the URL" + }, + "organization_size": { + "label": "How many people will use this workspace?", + "placeholder": "Select a range" + } + }, + "errors": { + "creation_disabled": { + "title": "Only your instance admin can create workspaces", + "description": "If you know your instance admin's email address, click the button below to get in touch with them.", + "request_button": "Request instance admin" + }, + "validation": { + "name_alphanumeric": "Workspaces names can contain only (' '), ('-'), ('_') and alphanumeric characters.", + "name_length": "Limit your name to 80 characters.", + "url_alphanumeric": "URLs can contain only ('-') and alphanumeric characters.", + "url_length": "Limit your URL to 48 characters.", + "url_already_taken": "Workspace URL is already taken!" + } + }, + "request_email": { + "subject": "Requesting a new workspace", + "body": "Hi instance admin(s),\n\nPlease create a new workspace with the URL [/workspace-name] for [purpose of creating the workspace].\n\nThanks,\n{firstName} {lastName}\n{email}" + }, + "button": { + "default": "Create workspace", + "loading": "Creating workspace" + }, + "toast": { + "success": { + "title": "Success", + "message": "Workspace created successfully" + }, + "error": { + "title": "Error", + "message": "Workspace could not be created. Please try again." + } + } + }, + + "workspace_dashboard": { + "empty_state": { + "general": { + "title": "Overview of your projects, activity, and metrics", + "description": "Welcome to Plane, we are excited to have you here. Create your first project and track your work items, and this page will transform into a space that helps you progress. Admins will also see items which help their team progress.", + "primary_button": { + "text": "Build your first project", + "comic": { + "title": "Everything starts with a project in Plane", + "description": "A project could be a product's roadmap, a marketing campaign, or launching a new car." + } + } + } + } + }, + + "workspace_analytics": { + "label": "Analytics", + "page_label": "{workspace} - Analytics", + "open_tasks": "Total open tasks", + "error": "There was some error in fetching the data.", + "work_items_closed_in": "Work items closed in", + "selected_projects": "Selected projects", + "total_members": "Total members", + "total_cycles": "Total cycles", + "total_modules": "Total modules", + "pending_work_items": { + "title": "Pending work items", + "empty_state": "Analysis of pending work items by co-workers appears here." + }, + "work_items_closed_in_a_year": { + "title": "Work items closed in a year", + "empty_state": "Close work items to view analysis of the same in the form of a graph." + }, + "most_work_items_created": { + "title": "Most work items created", + "empty_state": "Co-workers and the number of work items created by them appears here." + }, + "most_work_items_closed": { + "title": "Most work items closed", + "empty_state": "Co-workers and the number of work items closed by them appears here." + }, + "tabs": { + "scope_and_demand": "Scope and Demand", + "custom": "Custom Analytics" + }, + "empty_state": { + "general": { + "title": "Track progress, workloads, and allocations. Spot trends, remove blockers, and move work faster", + "description": "See scope versus demand, estimates, and scope creep. Get performance by team members and teams, and make sure your project runs on time.", + "primary_button": { + "text": "Start your first project", + "comic": { + "title": "Analytics works best with Cycles + Modules", + "description": "First, timebox your work items into Cycles and, if you can, group work items that span more than a cycle into Modules. Check out both on the left nav." + } + } + } + } + }, + + "workspace_projects": { + "label": "{count, plural, one {Project} other {Projects}}", + "create": { + "label": "Add Project" + }, + "network": { + "label": "Network", + "private": { + "title": "Private", + "description": "Accessible only by invite" + }, + "public": { + "title": "Public", + "description": "Anyone in the workspace except Guests can join" + } + }, + "error": { + "permission": "You don't have permission to perform this action.", + "cycle_delete": "Failed to delete cycle", + "module_delete": "Failed to delete module", + "issue_delete": "Failed to delete work item" + }, + "state": { + "backlog": "Backlog", + "unstarted": "Unstarted", + "started": "Started", + "completed": "Completed", + "cancelled": "Cancelled" + }, + "sort": { + "manual": "Manual", + "name": "Name", + "created_at": "Created date", + "members_length": "Number of members" + }, + "scope": { + "my_projects": "My projects", + "archived_projects": "Archived" + }, + "common": { + "months_count": "{months, plural, one{# month} other{# months}}" + }, + "empty_state": { + "general": { + "title": "No active projects", + "description": "Think of each project as the parent for goal-oriented work. Projects are where Jobs, Cycles, and Modules live and, along with your colleagues, help you achieve that goal. Create a new project or filter for archived projects.", + "primary_button": { + "text": "Start your first project", + "comic": { + "title": "Everything starts with a project in Plane", + "description": "A project could be a product's roadmap, a marketing campaign, or launching a new car." + } + } + }, + "no_projects": { + "title": "No project", + "description": "To create work items or manage your work, you need to create a project or be a part of one.", + "primary_button": { + "text": "Start your first project", + "comic": { + "title": "Everything starts with a project in Plane", + "description": "A project could be a product's roadmap, a marketing campaign, or launching a new car." + } + } + }, + "filter": { + "title": "No matching projects", + "description": "No projects detected with the matching criteria. \n Create a new project instead." + }, + "search": { + "description": "No projects detected with the matching criteria.\nCreate a new project instead" + } + } + }, + + "workspace_views": { + "add_view": "Add view", + "empty_state": { + "all-issues": { + "title": "No work items in the project", + "description": "First project done! Now, slice your work into trackable pieces with work items. Let's go!", + "primary_button": { + "text": "Create new work item" + } + }, + "assigned": { + "title": "No work items yet", + "description": "Work items assigned to you can be tracked from here.", + "primary_button": { + "text": "Create new work item" + } + }, + "created": { + "title": "No work items yet", + "description": "All work items created by you come here, track them here directly.", + "primary_button": { + "text": "Create new work item" + } + }, + "subscribed": { + "title": "No work items yet", + "description": "Subscribe to work items you are interested in, track all of them here." + }, + "custom-view": { + "title": "No work items yet", + "description": "Work items that applies to the filters, track all of them here." + } + } + }, + + "workspace_settings": { + "label": "Workspace settings", + "page_label": "{workspace} - General settings", + "key_created": "Key created", + "copy_key": "Copy and save this secret key in Plane Pages. You can't see this key after you hit Close. A CSV file containing the key has been downloaded.", + "token_copied": "Token copied to clipboard.", + "settings": { + "general": { + "title": "General", + "upload_logo": "Upload logo", + "edit_logo": "Edit logo", + "name": "Workspace name", + "company_size": "Company size", + "url": "Workspace URL", + "update_workspace": "Update workspace", + "delete_workspace": "Delete workspace", + "delete_workspace_description": "When deleting a workspace, all of the data and resources within that workspace will be permanently removed and cannot be recovered.", + "delete_btn": "Delete my workspace", + "errors": { + "name": { + "required": "Name is required", + "max_length": "Workspace name should not exceed 80 characters" + }, + "company_size": { + "required": "Company size is required" + } + } + }, + "members": { + "title": "Members", + "add_member": "Add member", + "invitations_sent_successfully": "Invitations sent successfully", + "leave_confirmation": "Are you sure you want to leave the workspace? You will no longer have access to this workspace. This action cannot be undone.", + "details": { + "full_name": "Full name", + "display_name": "Display name", + "email_address": "Email address", + "account_type": "Account type", + "authentication": "Authentication", + "joining_date": "Joining date" + }, + "modal": { + "title": "Invite people to collaborate", + "description": "Invite people to collaborate on your workspace.", + "button": "Send invitations", + "button_loading": "Sending invitations", + "placeholder": "name@company.com", + "errors": { + "required": "We need an email address to invite them.", + "invalid": "Email is invalid" + } + } + }, + "billing_and_plans": { + "title": "Billing & Plans", + "current_plan": "Current plan", + "free_plan": "You are currently using the free plan", + "view_plans": "View plans" + }, + "exports": { + "title": "Exports", + "exporting": "Exporting", + "previous_exports": "Previous exports", + "export_separate_files": "Export the data into separate files", + "modal": { + "title": "Export to", + "toasts": { + "success": { + "title": "Export successful", + "message": "You will be able to download the exported {entity} from the previous export." + }, + "error": { + "title": "Export failed", + "message": "Export was unsuccessful. Please try again." + } + } + } + }, + "webhooks": { + "title": "Webhooks", + "add_webhook": "Add webhook", + "modal": { + "title": "Create webhook", + "details": "Webhook details", + "payload": "Payload URL", + "question": "Which events would you like to trigger this webhook?", + "error": "URL is required" + }, + "secret_key": { + "title": "Secret key", + "message": "Generate a token to sign-in to the webhook payload" + }, + "options": { + "all": "Send me everything", + "individual": "Select individual events" + }, + "toasts": { + "created": { + "title": "Webhook created", + "message": "The webhook has been successfully created" + }, + "not_created": { + "title": "Webhook not created", + "message": "The webhook could not be created" + }, + "updated": { + "title": "Webhook updated", + "message": "The webhook has been successfully updated" + }, + "not_updated": { + "title": "Webhook not updated", + "message": "The webhook could not be updated" + }, + "removed": { + "title": "Webhook removed", + "message": "The webhook has been successfully removed" + }, + "not_removed": { + "title": "Webhook not removed", + "message": "The webhook could not be removed" + }, + "secret_key_copied": { + "message": "Secret key copied to clipboard." + }, + "secret_key_not_copied": { + "message": "Error occurred while copying secret key." + } + } + }, + "api_tokens": { + "title": "API Tokens", + "add_token": "Add API token", + "create_token": "Create token", + "never_expires": "Never expires", + "generate_token": "Generate token", + "generating": "Generating", + "delete": { + "title": "Delete API token", + "description": "Any application using this token will no longer have the access to Plane data. This action cannot be undone.", + "success": { + "title": "Success!", + "message": "The API token has been successfully deleted" + }, + "error": { + "title": "Error!", + "message": "The API token could not be deleted" + } + } + } + }, + "empty_state": { + "api_tokens": { + "title": "No API tokens created", + "description": "Plane APIs can be used to integrate your data in Plane with any external system. Create a token to get started." + }, + "webhooks": { + "title": "No webhooks added", + "description": "Create webhooks to receive real-time updates and automate actions." + }, + "exports": { + "title": "No exports yet", + "description": "Anytime you export, you will also have a copy here for reference." + }, + "imports": { + "title": "No imports yet", + "description": "Find all your previous imports here and download them." + } + } + }, + + "profile": { + "label": "Profile", + "page_label": "Your work", + "work": "Work", + "details": { + "joined_on": "Joined on", + "time_zone": "Timezone" + }, + "stats": { + "workload": "Workload", + "overview": "Overview", + "created": "Work items created", + "assigned": "Work items assigned", + "subscribed": "Work items subscribed", + "state_distribution": { + "title": "Work items by state", + "empty": "Create work items to view the them by states in the graph for better analysis." + }, + "priority_distribution": { + "title": "Work items by Priority", + "empty": "Create work items to view the them by priority in the graph for better analysis." + }, + "recent_activity": { + "title": "Recent activity", + "empty": "We couldn't find data. Kindly view your inputs", + "button": "Download today's activity", + "button_loading": "Downloading" + } + }, + "actions": { + "profile": "Profile", + "security": "Security", + "activity": "Activity", + "appearance": "Appearance", + "notifications": "Notifications" + }, + "tabs": { + "summary": "Summary", + "assigned": "Assigned", + "created": "Created", + "subscribed": "Subscribed", + "activity": "Activity" + }, + "empty_state": { + "activity": { + "title": "No activities yet", + "description": "Get started by creating a new work item! Add details and properties to it. Explore more in Plane to see your activity." + }, + "assigned": { + "title": "No work items are assigned to you", + "description": "Work items assigned to you can be tracked from here." + }, + "created": { + "title": "No work items yet", + "description": "All work items created by you come here, track them here directly." + }, + "subscribed": { + "title": "No work items yet", + "description": "Subscribe to work items you are interested in, track all of them here." + } + } + }, + + "project_settings": { + "general": { + "enter_project_id": "Enter project ID", + "please_select_a_timezone": "Please select a timezone", + "archive_project": { + "title": "Archive project", + "description": "Archiving a project will unlist your project from your side navigation although you will still be able to access it from your projects page. You can restore the project or delete it whenever you want.", + "button": "Archive project" + }, + "delete_project": { + "title": "Delete project", + "description": "When deleting a project, all of the data and resources within that project will be permanently removed and cannot be recovered.", + "button": "Delete my project" + }, + "toast": { + "success": "Project updated successfully", + "error": "Project could not be updated. Please try again." + } + }, + "members": { + "label": "Members", + "project_lead": "Project lead", + "default_assignee": "Default assignee", + "guest_super_permissions": { + "title": "Grant view access to all work items for guest users:", + "sub_heading": "This will allow guests to have view access to all the project work items." + }, + "invite_members": { + "title": "Invite members", + "sub_heading": "Invite members to work on your project.", + "select_co_worker": "Select co-worker" + } + }, + "states": { + "describe_this_state_for_your_members": "Describe this state for your members." + }, + "labels": { + "label_title": "Label title", + "label_title_is_required": "Label title is required", + "label_max_char": "Label name should not exceed 255 characters", + "toast": { + "error": "Error while updating the label" + } + }, + "estimates": { + "title": "Enable estimates for my project", + "description": "They help you in communicating complexity and workload of the team." + }, + "automations": { + "label": "Automations", + "auto-archive": { + "title": "Auto-archive closed work items", + "description": "Plane will auto archive work items that have been completed or canceled.", + "duration": "Auto-archive work items that are closed for" + }, + "auto-close": { + "title": "Auto-close work items", + "description": "Plane will automatically close work items that haven't been completed or canceled.", + "duration": "Auto-close work items that are inactive for", + "auto_close_status": "Auto-close status" + } + }, + + "empty_state": { + "labels": { + "title": "No labels yet", + "description": "Create labels to help organize and filter work items in you project." + }, + "estimates": { + "title": "No estimate systems yet", + "description": "Create a set of estimates to communicate the amount of work per work item.", + "primary_button": "Add estimate system" + } + } + }, + + "project_cycles": { + "add_cycle": "Add cycle", + "more_details": "More details", + "cycle": "Cycle", + "update_cycle": "Update cycle", + "create_cycle": "Create cycle", + "no_matching_cycles": "No matching cycles", + "remove_filters_to_see_all_cycles": "Remove the filters to see all cycles", + "remove_search_criteria_to_see_all_cycles": "Remove the search criteria to see all cycles", + "only_completed_cycles_can_be_archived": "Only completed cycles can be archived", + "active_cycle": { + "label": "Active cycle", + "progress": "Progress", + "chart": "Burndown chart", + "priority_issue": "Priority work items", + "assignees": "Assignees", + "issue_burndown": "Work item burndown", + "ideal": "Ideal", + "current": "Current", + "labels": "Labels" + }, + "upcoming_cycle": { + "label": "Upcoming cycle" + }, + "completed_cycle": { + "label": "Completed cycle" + }, + "status": { + "days_left": "Days left", + "completed": "Completed", + "yet_to_start": "Yet to start", + "in_progress": "In progress", + "draft": "Draft" + }, + "action": { + "restore": { + "title": "Restore cycle", + "success": { + "title": "Cycle restored", + "description": "The cycle has been restored." + }, + "failed": { + "title": "Cycle restore failed", + "description": "The cycle could not be restored. Please try again." + } + }, + "favorite": { + "loading": "Adding cycle to favorites", + "success": { + "description": "Cycle added to favorites.", + "title": "Success!" + }, + "failed": { + "description": "Couldn't add the cycle to favorites. Please try again.", + "title": "Error!" + } + }, + "unfavorite": { + "loading": "Removing cycle from favorites", + "success": { + "description": "Cycle removed from favorites.", + "title": "Success!" + }, + "failed": { + "description": "Couldn't remove the cycle from favorites. Please try again.", + "title": "Error!" + } + }, + "update": { + "loading": "Updating cycle", + "success": { + "description": "Cycle updated successfully.", + "title": "Success!" + }, + "failed": { + "description": "Error updating the cycle. Please try again.", + "title": "Error!" + }, + "error": { + "already_exists": "You already have a cycle on the given dates, if you want to create a draft cycle, you can do that by removing both the dates." + } + } + }, + "empty_state": { + "general": { + "title": "Group and timebox your work in Cycles.", + "description": "Break work down by timeboxed chunks, work backwards from your project deadline to set dates, and make tangible progress as a team.", + "primary_button": { + "text": "Set your first cycle", + "comic": { + "title": "Cycles are repetitive time-boxes.", + "description": "A sprint, an iteration, and or any other term you use for weekly or fortnightly tracking of work is a cycle." + } + } + }, + "no_issues": { + "title": "No work items added to the cycle", + "description": "Add or create work items you wish to timebox and deliver within this cycle", + "primary_button": { + "text": "Create new work item" + }, + "secondary_button": { + "text": "Add existing work item" + } + }, + "completed_no_issues": { + "title": "No work items in the cycle", + "description": "No work items in the cycle. Work items are either transferred or hidden. To see hidden work items if any, update your display properties accordingly." + }, + "active": { + "title": "No active cycle", + "description": "An active cycle includes any period that encompasses today's date within its range. Find the progress and details of the active cycle here." + }, + "archived": { + "title": "No archived cycles yet", + "description": "To tidy up your project, archive completed cycles. Find them here once archived." + } + } + }, + + "project_issues": { + "empty_state": { + "no_issues": { + "title": "Create a work item and assign it to someone, even yourself", + "description": "Think of work items as jobs, tasks, work, or JTBD. Which we like. A work item and its sub-work items are usually time-based actionables assigned to members of your team. Your team creates, assigns, and completes work items to move your project towards its goal.", + "primary_button": { + "text": "Create your first work item", + "comic": { + "title": "Work items are building blocks in Plane.", + "description": "Redesign the Plane UI, Rebrand the company, or Launch the new fuel injection system are examples of work items that likely have sub-work items." + } + } + }, + "no_archived_issues": { + "title": "No archived work items yet", + "description": "Manually or through automation, you can archive work items that are completed or cancelled. Find them here once archived.", + "primary_button": { + "text": "Set automation" + } + }, + "issues_empty_filter": { + "title": "No work items found matching the filters applied", + "secondary_button": { + "text": "Clear all filters" + } + } + } + }, + + "project_module": { + "add_module": "Add Module", + "update_module": "Update Module", + "create_module": "Create Module", + "archive_module": "Archive Module", + "restore_module": "Restore Module", + "delete_module": "Delete module", + "empty_state": { + "general": { + "title": "Map your project milestones to Modules and track aggregated work easily.", + "description": "A group of work items that belong to a logical, hierarchical parent form a module. Think of them as a way to track work by project milestones. They have their own periods and deadlines as well as analytics to help you see how close or far you are from a milestone.", + "primary_button": { + "text": "Build your first module", + "comic": { + "title": "Modules help group work by hierarchy.", + "description": "A cart module, a chassis module, and a warehouse module are all good example of this grouping." + } + } + }, + "no_issues": { + "title": "No work items in the module", + "description": "Create or add work items which you want to accomplish as part of this module", + "primary_button": { + "text": "Create new work items" + }, + "secondary_button": { + "text": "Add an existing work item" + } + }, + "archived": { + "title": "No archived Modules yet", + "description": "To tidy up your project, archive completed or cancelled modules. Find them here once archived." + }, + "sidebar": { + "in_active": "This module isn't active yet.", + "invalid_date": "Invalid date. Please enter valid date." + } + }, + "quick_actions": { + "archive_module": "Archive module", + "archive_module_description": "Only completed or canceled\nmodule can be archived.", + "delete_module": "Delete module" + }, + "toast": { + "copy": { + "success": "Module link copied to clipboard" + }, + "delete": { + "success": "Module deleted successfully", + "error": "Failed to delete module" + } + } + }, + + "project_views": { + "empty_state": { + "general": { + "title": "Save filtered views for your project. Create as many as you need", + "description": "Views are a set of saved filters that you use frequently or want easy access to. All your colleagues in a project can see everyone’s views and choose whichever suits their needs best.", + "primary_button": { + "text": "Create your first view", + "comic": { + "title": "Views work atop Work item properties.", + "description": "You can create a view from here with as many properties as filters as you see fit." + } + } + }, + "filter": { + "title": "No matching views", + "description": "No views match the search criteria. \n Create a new view instead." + } + } + }, + + "project_page": { + "empty_state": { + "general": { + "title": "Write a note, a doc, or a full knowledge base. Get Galileo, Plane's AI assistant, to help you get started", + "description": "Pages are thoughts potting space in Plane. Take down meeting notes, format them easily, embed work items, lay them out using a library of components, and keep them all in your project's context. To make short work of any doc, invoke Galileo, Plane's AI, with a shortcut or the click of a button.", + "primary_button": { + "text": "Create your first page" + } + }, + "private": { + "title": "No private pages yet", + "description": "Keep your private thoughts here. When you're ready to share, the team's just a click away.", + "primary_button": { + "text": "Create your first page" + } + }, + "public": { + "title": "No public pages yet", + "description": "See pages shared with everyone in your project right here.", + "primary_button": { + "text": "Create your first page" + } + }, + "archived": { + "title": "No archived pages yet", + "description": "Archive pages not on your radar. Access them here when needed." + } + } + }, + + "command_k": { + "empty_state": { + "search": { + "title": "No results found" + } + } + }, + + "issue_relation": { + "empty_state": { + "search": { + "title": "No matching work items found" + }, + "no_issues": { + "title": "No work items found" + } + } + }, + + "issue_comment": { + "empty_state": { + "general": { + "title": "No comments yet", + "description": "Comments can be used as a discussion and follow-up space for the work items" + } + } + }, + + "notification": { + "label": "Inbox", + "page_label": "{workspace} - Inbox", + "options": { + "mark_all_as_read": "Mark all as read", + "mark_read": "Mark as read", + "mark_unread": "Mark as unread", + "refresh": "Refresh", + "filters": "Inbox Filters", + "show_unread": "Show unread", + "show_snoozed": "Show snoozed", + "show_archived": "Show archived", + "mark_archive": "Archive", + "mark_unarchive": "Un archive", + "mark_snooze": "Snooze", + "mark_unsnooze": "Un snooze" + }, + "toasts": { + "read": "Notification marked as read", + "unread": "Notification marked as unread", + "archived": "Notification marked as archived", + "unarchived": "Notification marked as un archived", + "snoozed": "Notification snoozed", + "unsnoozed": "Notification un snoozed" + }, + "empty_state": { + "detail": { + "title": "Select to view details." + }, + "all": { + "title": "No work items assigned", + "description": "Updates for work items assigned to you can be \n seen here" + }, + "mentions": { + "title": "No work items assigned", + "description": "Updates for work items assigned to you can be \n seen here" + } + }, + "tabs": { + "all": "All", + "mentions": "Mentions" + }, + "filter": { + "assigned": "Assigned to me", + "created": "Created by me", + "subscribed": "Subscribed by me" + }, + "snooze": { + "1_day": "1 day", + "3_days": "3 days", + "5_days": "5 days", + "1_week": "1 week", + "2_weeks": "2 weeks", + "custom": "Custom" + } + }, + + "active_cycle": { + "empty_state": { + "progress": { + "title": "Add work items to the cycle to view it's progress" + }, + "chart": { + "title": "Add work items to the cycle to view the burndown chart." + }, + "priority_issue": { + "title": "Observe high priority work items tackled in the cycle at a glance." + }, + "assignee": { + "title": "Add assignees to work items to see a breakdown of work by assignees." + }, + "label": { + "title": "Add labels to work items to see the breakdown of work by labels." + } + } + }, + + "disabled_project": { + "empty_state": { + "inbox": { + "title": "Intake is not enabled for the project.", + "description": "Intake helps you manage incoming requests to your project and add them as work items in your workflow. Enable intake from project settings to manage requests.", + "primary_button": { + "text": "Manage features" + } + }, + "cycle": { + "title": "Cycles is not enabled for this project.", + "description": "Break work down by timeboxed chunks, work backwards from your project deadline to set dates, and make tangible progress as a team. Enable the cycles feature for your project to start using them.", + "primary_button": { + "text": "Manage features" + } + }, + "module": { + "title": "Modules are not enabled for the project.", + "description": "Modules are the building blocks of your project. Enable modules from project settings to start using them.", + "primary_button": { + "text": "Manage features" + } + }, + "page": { + "title": "Pages are not enabled for the project.", + "description": "Pages are the building blocks of your project. Enable pages from project settings to start using them.", + "primary_button": { + "text": "Manage features" + } + }, + "view": { + "title": "Views are not enabled for the project.", + "description": "Views are the building blocks of your project. Enable views from project settings to start using them.", + "primary_button": { + "text": "Manage features" + } + } + } + }, + "workspace_draft_issues": { + "draft_an_issue": "Draft a work item", + "empty_state": { + "title": "Half-written work items, and soon, comments will show up here.", + "description": "To try this out, start adding a work item and leave it mid-way or create your first draft below. 😉", + "primary_button": { + "text": "Create your first draft" + } + }, + "delete_modal": { + "title": "Delete draft", + "description": "Are you sure you want to delete this draft? This can't be undone." + }, + "toasts": { + "created": { + "success": "Draft created", + "error": "Work item could not be created. Please try again." + }, + "deleted": { + "success": "Draft deleted" + } + } + }, + + "stickies": { + "title": "Your stickies", + "placeholder": "click to type here", + "all": "All stickies", + "no-data": "Jot down an idea, capture an aha, or record a brainwave. Add a sticky to get started.", + "add": "Add sticky", + "search_placeholder": "Search by title", + "delete": "Delete sticky", + "delete_confirmation": "Are you sure you want to delete this sticky?", + "empty_state": { + "simple": "Jot down an idea, capture an aha, or record a brainwave. Add a sticky to get started.", + "general": { + "title": "Stickies are quick notes and to-dos you take down on the fly.", + "description": "Capture your thoughts and ideas effortlessly by creating stickies that you can access anytime and from anywhere.", + "primary_button": { + "text": "Add sticky" + } + }, + "search": { + "title": "That doesn't match any of your stickies.", + "description": "Try a different term or let us know\nif you are sure your search is right. ", + "primary_button": { + "text": "Add sticky" + } + } + }, + "toasts": { + "errors": { + "wrong_name": "The sticky name cannot be longer than 100 characters.", + "already_exists": "There already exists a sticky with no description" + }, + "created": { + "title": "Sticky created", + "message": "The sticky has been successfully created" + }, + "not_created": { + "title": "Sticky not created", + "message": "The sticky could not be created" + }, + "updated": { + "title": "Sticky updated", + "message": "The sticky has been successfully updated" + }, + "not_updated": { + "title": "Sticky not updated", + "message": "The sticky could not be updated" + }, + "removed": { + "title": "Sticky removed", + "message": "The sticky has been successfully removed" + }, + "not_removed": { + "title": "Sticky not removed", + "message": "The sticky could not be removed" + } + } + }, + + "role_details": { + "guest": { + "title": "Guest", + "description": "External members of organizations can be invited as guests." + }, + "member": { + "title": "Member", + "description": "Ability to read, write, edit, and delete entities inside projects, cycles, and modules" + }, + "admin": { + "title": "Admin", + "description": "All permissions set to true within the workspace." + } + }, + + "user_roles": { + "product_or_project_manager": "Product / Project Manager", + "development_or_engineering": "Development / Engineering", + "founder_or_executive": "Founder / Executive", + "freelancer_or_consultant": "Freelancer / Consultant", + "marketing_or_growth": "Marketing / Growth", + "sales_or_business_development": "Sales / Business Development", + "support_or_operations": "Support / Operations", + "student_or_professor": "Student / Professor", + "human_resources": "Human / Resources", + "other": "Other" + }, + + "importer": { + "github": { + "title": "Github", + "description": "Import work items from GitHub repositories and sync them." + }, + "jira": { + "title": "Jira", + "description": "Import work items and epics from Jira projects and epics." + } + }, + + "exporter": { + "csv": { + "title": "CSV", + "description": "Export work items to a CSV file.", + "short_description": "Export as csv" + }, + "excel": { + "title": "Excel", + "description": "Export work items to a Excel file.", + "short_description": "Export as excel" + }, + "xlsx": { + "title": "Excel", + "description": "Export work items to a Excel file.", + "short_description": "Export as excel" + }, + "json": { + "title": "JSON", + "description": "Export work items to a JSON file.", + "short_description": "Export as json" + } + }, + "default_global_view": { + "all_issues": "All work items", + "assigned": "Assigned", + "created": "Created", + "subscribed": "Subscribed" + }, + + "themes": { + "theme_options": { + "system_preference": { + "label": "System preference" + }, + "light": { + "label": "Light" + }, + "dark": { + "label": "Dark" + }, + "light_contrast": { + "label": "Light high contrast" + }, + "dark_contrast": { + "label": "Dark high contrast" + }, + "custom": { + "label": "Custom theme" + } + } + }, + "project_modules": { + "status": { + "backlog": "Backlog", + "planned": "Planned", + "in_progress": "In Progress", + "paused": "Paused", + "completed": "Completed", + "cancelled": "Cancelled" + }, + "layout": { + "list": "List layout", + "board": "Gallery layout", + "timeline": "Timeline layout" + }, + "order_by": { + "name": "Name", + "progress": "Progress", + "issues": "Number of work items", + "due_date": "Due date", + "created_at": "Created date", + "manual": "Manual" + } + }, + + "cycle": { + "label": "{count, plural, one {Cycle} other {Cycles}}", + "no_cycle": "No cycle" + }, + + "module": { + "label": "{count, plural, one {Module} other {Modules}}", + "no_module": "No module" + } } diff --git a/packages/i18n/src/locales/es/translations.json b/packages/i18n/src/locales/es/translations.json index 55f6dc2bed2..1b38b1cbe0a 100644 --- a/packages/i18n/src/locales/es/translations.json +++ b/packages/i18n/src/locales/es/translations.json @@ -1,4 +1,174 @@ { + "sidebar": { + "projects": "Proyectos", + "pages": "Páginas", + "new_work_item": "Nuevo elemento de trabajo", + "home": "Inicio", + "your_work": "Tu trabajo", + "inbox": "Bandeja de entrada", + "workspace": "Espacio de trabajo", + "views": "Vistas", + "analytics": "Análisis", + "work_items": "Elementos de trabajo", + "cycles": "Ciclos", + "modules": "Módulos", + "intake": "Entrada", + "drafts": "Borradores", + "favorites": "Favoritos", + "pro": "Pro", + "upgrade": "Mejorar" + }, + + "auth": { + "common": { + "email": { + "label": "Correo electrónico", + "placeholder": "nombre@empresa.com", + "errors": { + "required": "El correo electrónico es obligatorio", + "invalid": "El correo electrónico no es válido" + } + }, + "password": { + "label": "Contraseña", + "set_password": "Establecer una contraseña", + "placeholder": "Ingresa la contraseña", + "confirm_password": { + "label": "Confirmar contraseña", + "placeholder": "Confirmar contraseña" + }, + "current_password": { + "label": "Contraseña actual" + }, + "new_password": { + "label": "Nueva contraseña", + "placeholder": "Ingresa nueva contraseña" + }, + "change_password": { + "label": { + "default": "Cambiar contraseña", + "submitting": "Cambiando contraseña" + } + }, + "errors": { + "match": "Las contraseñas no coinciden", + "empty": "Por favor ingresa tu contraseña", + "length": "La contraseña debe tener más de 8 caracteres", + "strength": { + "weak": "La contraseña es débil", + "strong": "La contraseña es fuerte" + } + }, + "submit": "Establecer contraseña", + "toast": { + "change_password": { + "success": { + "title": "¡Éxito!", + "message": "Contraseña cambiada exitosamente." + }, + "error": { + "title": "¡Error!", + "message": "Algo salió mal. Por favor intenta de nuevo." + } + } + } + }, + "unique_code": { + "label": "Código único", + "placeholder": "obtiene-establece-vuela", + "paste_code": "Pega el código enviado a tu correo electrónico", + "requesting_new_code": "Solicitando nuevo código", + "sending_code": "Enviando código" + }, + "already_have_an_account": "¿Ya tienes una cuenta?", + "login": "Iniciar sesión", + "create_account": "Crear una cuenta", + "new_to_plane": "¿Nuevo en Plane?", + "back_to_sign_in": "Volver a iniciar sesión", + "resend_in": "Reenviar en {seconds} segundos", + "sign_in_with_unique_code": "Iniciar sesión con código único", + "forgot_password": "¿Olvidaste tu contraseña?" + }, + "sign_up": { + "header": { + "label": "Crea una cuenta para comenzar a gestionar el trabajo con tu equipo.", + "step": { + "email": { + "header": "Registrarse", + "sub_header": "" + }, + "password": { + "header": "Registrarse", + "sub_header": "Regístrate usando una combinación de correo electrónico y contraseña." + }, + "unique_code": { + "header": "Registrarse", + "sub_header": "Regístrate usando un código único enviado a la dirección de correo electrónico anterior." + } + } + }, + "errors": { + "password": { + "strength": "Intenta establecer una contraseña fuerte para continuar" + } + } + }, + "sign_in": { + "header": { + "label": "Inicia sesión para comenzar a gestionar el trabajo con tu equipo.", + "step": { + "email": { + "header": "Iniciar sesión o registrarse", + "sub_header": "" + }, + "password": { + "header": "Iniciar sesión o registrarse", + "sub_header": "Usa tu combinación de correo electrónico y contraseña para iniciar sesión." + }, + "unique_code": { + "header": "Iniciar sesión o registrarse", + "sub_header": "Inicia sesión usando un código único enviado a la dirección de correo electrónico anterior." + } + } + } + }, + "forgot_password": { + "title": "Restablecer tu contraseña", + "description": "Ingresa la dirección de correo electrónico verificada de tu cuenta de usuario y te enviaremos un enlace para restablecer la contraseña.", + "email_sent": "Enviamos el enlace de restablecimiento a tu dirección de correo electrónico", + "send_reset_link": "Enviar enlace de restablecimiento", + "errors": { + "smtp_not_enabled": "Vemos que tu administrador no ha habilitado SMTP, no podremos enviar un enlace para restablecer la contraseña" + }, + "toast": { + "success": { + "title": "Correo enviado", + "message": "Revisa tu bandeja de entrada para encontrar un enlace para restablecer tu contraseña. Si no aparece en unos minutos, revisa tu carpeta de spam." + }, + "error": { + "title": "¡Error!", + "message": "Algo salió mal. Por favor intenta de nuevo." + } + } + }, + "reset_password": { + "title": "Establecer nueva contraseña", + "description": "Asegura tu cuenta con una contraseña fuerte" + }, + "set_password": { + "title": "Asegura tu cuenta", + "description": "Establecer una contraseña te ayuda a iniciar sesión de forma segura" + }, + "sign_out": { + "toast": { + "error": { + "title": "¡Error!", + "message": "Error al cerrar sesión. Por favor intenta de nuevo." + } + } + } + }, + "submit": "Enviar", "cancel": "Cancelar", "loading": "Cargando", @@ -9,15 +179,18 @@ "close": "Cerrar", "yes": "Sí", "no": "No", - "ok": "OK", + "ok": "Aceptar", "name": "Nombre", "description": "Descripción", "search": "Buscar", "add_member": "Agregar miembro", + "adding_members": "Agregando miembros", "remove_member": "Eliminar miembro", "add_members": "Agregar miembros", + "adding_member": "Agregando miembros", "remove_members": "Eliminar miembros", "add": "Agregar", + "adding": "Agregando", "remove": "Eliminar", "add_new": "Agregar nuevo", "remove_selected": "Eliminar seleccionados", @@ -32,13 +205,12 @@ "password": "Contraseña", "change_cover": "Cambiar portada", "language": "Idioma", - "saving": "Guardando...", + "saving": "Guardando", "save_changes": "Guardar cambios", "deactivate_account": "Desactivar cuenta", "deactivate_account_description": "Al desactivar una cuenta, todos los datos y recursos dentro de esa cuenta se eliminarán permanentemente y no se podrán recuperar.", - "profile_settings": "Configuración de perfil", + "profile_settings": "Configuración del perfil", "your_account": "Tu cuenta", - "profile": "Perfil", "security": "Seguridad", "activity": "Actividad", "appearance": "Apariencia", @@ -51,22 +223,9 @@ "created": "Creado", "subscribed": "Suscrito", "you_do_not_have_the_permission_to_access_this_page": "No tienes permiso para acceder a esta página.", - "failed_to_sign_out_please_try_again": "Error al cerrar sesión. Por favor, inténtalo de nuevo.", - "password_changed_successfully": "Contraseña cambiada con éxito.", "something_went_wrong_please_try_again": "Algo salió mal. Por favor, inténtalo de nuevo.", - "change_password": "Cambiar contraseña", - "passwords_dont_match": "Las contraseñas no coinciden", - "current_password": "Contraseña actual", - "new_password": "Nueva contraseña", - "confirm_password": "Confirmar contraseña", - "this_field_is_required": "Este campo es obligatorio", - "changing_password": "Cambiando contraseña", - "please_enter_your_password": "Por favor, introduce tu contraseña.", - "password_length_should_me_more_than_8_characters": "La longitud de la contraseña debe ser más de 8 caracteres.", - "password_is_weak": "La contraseña es débil.", - "password_is_strong": "La contraseña es fuerte.", "load_more": "Cargar más", - "select_or_customize_your_interface_color_scheme": "Selecciona o personaliza el esquema de color de tu interfaz.", + "select_or_customize_your_interface_color_scheme": "Selecciona o personaliza el esquema de colores de tu interfaz.", "theme": "Tema", "system_preference": "Preferencia del sistema", "light": "Claro", @@ -82,83 +241,55 @@ "sidebar_background_color": "Color de fondo de la barra lateral", "sidebar_text_color": "Color del texto de la barra lateral", "set_theme": "Establecer tema", - "enter_a_valid_hex_code_of_6_characters": "Introduce un código hexadecimal válido de 6 caracteres", - "background_color_is_required": "El color de fondo es obligatorio", - "text_color_is_required": "El color del texto es obligatorio", - "primary_color_is_required": "El color primario es obligatorio", - "sidebar_background_color_is_required": "El color de fondo de la barra lateral es obligatorio", - "sidebar_text_color_is_required": "El color del texto de la barra lateral es obligatorio", + "enter_a_valid_hex_code_of_6_characters": "Ingresa un código hexadecimal válido de 6 caracteres", + "background_color_is_required": "El color de fondo es requerido", + "text_color_is_required": "El color del texto es requerido", + "primary_color_is_required": "El color primario es requerido", + "sidebar_background_color_is_required": "El color de fondo de la barra lateral es requerido", + "sidebar_text_color_is_required": "El color del texto de la barra lateral es requerido", "updating_theme": "Actualizando tema", - "theme_updated_successfully": "Tema actualizado con éxito", + "theme_updated_successfully": "Tema actualizado exitosamente", "failed_to_update_the_theme": "Error al actualizar el tema", "email_notifications": "Notificaciones por correo electrónico", - "stay_in_the_loop_on_issues_you_are_subscribed_to_enable_this_to_get_notified": "Mantente al tanto de los problemas a los que estás suscrito. Activa esto para recibir notificaciones.", - "email_notification_setting_updated_successfully": "Configuración de notificaciones por correo electrónico actualizada con éxito", + "stay_in_the_loop_on_issues_you_are_subscribed_to_enable_this_to_get_notified": "Mantente al tanto de los elementos de trabajo a los que estás suscrito. Activa esto para recibir notificaciones.", + "email_notification_setting_updated_successfully": "Configuración de notificaciones por correo electrónico actualizada exitosamente", "failed_to_update_email_notification_setting": "Error al actualizar la configuración de notificaciones por correo electrónico", - "notify_me_when": "Notifícame cuando", - "property_changes": "Cambios de propiedad", - "property_changes_description": "Notifícame cuando cambien las propiedades del problema como asignados, prioridad, estimaciones o cualquier otra cosa.", + "notify_me_when": "Notificarme cuando", + "property_changes": "Cambios de propiedades", + "property_changes_description": "Notificarme cuando cambien las propiedades de los elementos de trabajo como asignados, prioridad, estimaciones o cualquier otra cosa.", "state_change": "Cambio de estado", - "state_change_description": "Notifícame cuando el problema se mueva a un estado diferente", - "issue_completed": "Problema completado", - "issue_completed_description": "Notifícame solo cuando un problema esté completado", + "state_change_description": "Notificarme cuando los elementos de trabajo se muevan a un estado diferente", + "issue_completed": "Elemento de trabajo completado", + "issue_completed_description": "Notificarme solo cuando se complete un elemento de trabajo", "comments": "Comentarios", - "comments_description": "Notifícame cuando alguien deje un comentario en el problema", + "comments_description": "Notificarme cuando alguien deje un comentario en el elemento de trabajo", "mentions": "Menciones", - "mentions_description": "Notifícame solo cuando alguien me mencione en los comentarios o en la descripción", - "create_your_workspace": "Crea tu espacio de trabajo", - "only_your_instance_admin_can_create_workspaces": "Solo tu administrador de instancia puede crear espacios de trabajo", - "only_your_instance_admin_can_create_workspaces_description": "Si conoces el correo electrónico de tu administrador de instancia, haz clic en el botón de abajo para ponerte en contacto con él.", - "go_back": "Regresar", - "request_instance_admin": "Solicitar administrador de instancia", - "plane_logo": "Logo de Plane", - "workspace_creation_disabled": "Creación de espacio de trabajo deshabilitada", - "workspace_request_subject": "Solicitando un nuevo espacio de trabajo", - "workspace_request_body": "Hola administrador(es) de instancia,\n\nPor favor, crea un nuevo espacio de trabajo con la URL [/nombre-del-espacio-de-trabajo] para [propósito de crear el espacio de trabajo].\n\nGracias,\n{{firstName}} {{lastName}}\n{{email}}", - "creating_workspace": "Creando espacio de trabajo", - "workspace_created_successfully": "Espacio de trabajo creado con éxito", - "create_workspace_page": "Página de creación de espacio de trabajo", - "workspace_could_not_be_created_please_try_again": "No se pudo crear el espacio de trabajo. Por favor, inténtalo de nuevo.", - "workspace_could_not_be_created_please_try_again_description": "Ocurrió un error al crear el espacio de trabajo. Por favor, inténtalo de nuevo.", - "this_is_a_required_field": "Este es un campo obligatorio.", - "name_your_workspace": "Nombra tu espacio de trabajo", - "workspaces_names_can_contain_only_space_dash_and_alphanumeric_characters": "Los nombres de los espacios de trabajo solo pueden contener (' '), ('-'), ('_') y caracteres alfanuméricos.", - "limit_your_name_to_80_characters": "Limita tu nombre a 80 caracteres.", - "set_your_workspace_url": "Establece la URL de tu espacio de trabajo", - "limit_your_url_to_48_characters": "Limita tu URL a 48 caracteres.", - "how_many_people_will_use_this_workspace": "¿Cuántas personas usarán este espacio de trabajo?", - "how_many_people_will_use_this_workspace_description": "Esto nos ayudará a determinar el número de asientos que necesitas comprar.", - "select_a_range": "Selecciona un rango", - "urls_can_contain_only_dash_and_alphanumeric_characters": "Las URLs solo pueden contener ('-') y caracteres alfanuméricos.", - "something_familiar_and_recognizable_is_always_best": "Algo familiar y reconocible siempre es mejor.", - "workspace_url_is_already_taken": "¡La URL del espacio de trabajo ya está tomada!", - "old_password": "Contraseña antigua", + "mentions_description": "Notificarme solo cuando alguien me mencione en los comentarios o descripción", + "old_password": "Contraseña anterior", "general_settings": "Configuración general", "sign_out": "Cerrar sesión", "signing_out": "Cerrando sesión", "active_cycles": "Ciclos activos", - "active_cycles_description": "Monitorea ciclos a través de proyectos, rastrea problemas de alta prioridad y enfócate en ciclos que necesitan atención.", + "active_cycles_description": "Monitorea ciclos en todos los proyectos, rastrea elementos de trabajo de alta prioridad y enfócate en los ciclos que necesitan atención.", "on_demand_snapshots_of_all_your_cycles": "Instantáneas bajo demanda de todos tus ciclos", "upgrade": "Actualizar", - "10000_feet_view": "Vista de 10,000 pies de todos los ciclos activos.", - "10000_feet_view_description": "Amplía para ver ciclos en ejecución en todos tus proyectos a la vez en lugar de ir de ciclo en ciclo en cada proyecto.", + "10000_feet_view": "Vista panorámica de todos los ciclos activos.", + "10000_feet_view_description": "Aléjate para ver los ciclos en ejecución en todos tus proyectos a la vez en lugar de ir de Ciclo en Ciclo en cada proyecto.", "get_snapshot_of_each_active_cycle": "Obtén una instantánea de cada ciclo activo.", - "get_snapshot_of_each_active_cycle_description": "Rastrea métricas de alto nivel para todos los ciclos activos, ve su estado de progreso y obtén una idea del alcance frente a los plazos.", - "compare_burndowns": "Compara burndowns.", - "compare_burndowns_description": "Monitorea cómo se están desempeñando cada uno de tus equipos con un vistazo al informe de burndown de cada ciclo.", - "quickly_see_make_or_break_issues": "Ve rápidamente problemas críticos.", - "quickly_see_make_or_break_issues_description": "Previsualiza problemas de alta prioridad para cada ciclo contra fechas de vencimiento. Vélos todos por ciclo en un clic.", - "zoom_into_cycles_that_need_attention": "Enfócate en ciclos que necesitan atención.", - "zoom_into_cycles_that_need_attention_description": "Investiga el estado de cualquier ciclo que no cumpla con las expectativas en un clic.", - "stay_ahead_of_blockers": "Anticípate a los bloqueadores.", + "get_snapshot_of_each_active_cycle_description": "Rastrea métricas de alto nivel para todos los ciclos activos, ve su estado de progreso y obtén una idea del alcance contra los plazos.", + "compare_burndowns": "Compara los burndowns.", + "compare_burndowns_description": "Monitorea cómo se está desempeñando cada uno de tus equipos con un vistazo al informe de burndown de cada ciclo.", + "quickly_see_make_or_break_issues": "Ve rápidamente los elementos de trabajo críticos.", + "quickly_see_make_or_break_issues_description": "Previsualiza elementos de trabajo de alta prioridad para cada ciclo contra fechas de vencimiento. Vélos todos por ciclo con un clic.", + "zoom_into_cycles_that_need_attention": "Enfócate en los ciclos que necesitan atención.", + "zoom_into_cycles_that_need_attention_description": "Investiga el estado de cualquier ciclo que no se ajuste a las expectativas con un clic.", + "stay_ahead_of_blockers": "Mantente adelante de los bloqueadores.", "stay_ahead_of_blockers_description": "Detecta desafíos de un proyecto a otro y ve dependencias entre ciclos que no son obvias desde ninguna otra vista.", - "analytics": "Analítica", + "analytics": "Análisis", "workspace_invites": "Invitaciones al espacio de trabajo", - "workspace_settings": "Configuración del espacio de trabajo", "enter_god_mode": "Entrar en modo dios", "workspace_logo": "Logo del espacio de trabajo", - "new_issue": "Nuevo problema", - "home": "Inicio", + "new_issue": "Nuevo elemento de trabajo", "your_work": "Tu trabajo", "drafts": "Borradores", "projects": "Proyectos", @@ -171,13 +302,13 @@ "no_favorites_yet": "Aún no hay favoritos", "create_folder": "Crear carpeta", "new_folder": "Nueva carpeta", - "favorite_updated_successfully": "Favorito actualizado con éxito", - "favorite_created_successfully": "Favorito creado con éxito", + "favorite_updated_successfully": "Favorito actualizado exitosamente", + "favorite_created_successfully": "Favorito creado exitosamente", "folder_already_exists": "La carpeta ya existe", "folder_name_cannot_be_empty": "El nombre de la carpeta no puede estar vacío", "something_went_wrong": "Algo salió mal", "failed_to_reorder_favorite": "Error al reordenar favorito", - "favorite_removed_successfully": "Favorito eliminado con éxito", + "favorite_removed_successfully": "Favorito eliminado exitosamente", "failed_to_create_favorite": "Error al crear favorito", "failed_to_rename_favorite": "Error al renombrar favorito", "project_link_copied_to_clipboard": "Enlace del proyecto copiado al portapapeles", @@ -185,21 +316,26 @@ "add_project": "Agregar proyecto", "create_project": "Crear proyecto", "failed_to_remove_project_from_favorites": "No se pudo eliminar el proyecto de favoritos. Por favor, inténtalo de nuevo.", - "project_created_successfully": "Proyecto creado con éxito", - "project_created_successfully_description": "Proyecto creado con éxito. Ahora puedes comenzar a agregar problemas a él.", + "project_created_successfully": "Proyecto creado exitosamente", + "project_created_successfully_description": "Proyecto creado exitosamente. Ahora puedes comenzar a agregar elementos de trabajo.", "project_cover_image_alt": "Imagen de portada del proyecto", - "name_is_required": "El nombre es obligatorio", + "name_is_required": "El nombre es requerido", "title_should_be_less_than_255_characters": "El título debe tener menos de 255 caracteres", "project_name": "Nombre del proyecto", "project_id_must_be_at_least_1_character": "El ID del proyecto debe tener al menos 1 carácter", "project_id_must_be_at_most_5_characters": "El ID del proyecto debe tener como máximo 5 caracteres", "project_id": "ID del proyecto", - "project_id_tooltip_content": "Te ayuda a identificar problemas en el proyecto de manera única. Máximo 5 caracteres.", - "description_placeholder": "Descripción...", + "project_id_tooltip_content": "Te ayuda a identificar elementos de trabajo en el proyecto de manera única. Máximo 5 caracteres.", + "description_placeholder": "Descripción", "only_alphanumeric_non_latin_characters_allowed": "Solo se permiten caracteres alfanuméricos y no latinos.", - "project_id_is_required": "El ID del proyecto es obligatorio", + "project_id_is_required": "El ID del proyecto es requerido", + "project_id_allowed_char": "Solo se permiten caracteres alfanuméricos y no latinos.", + "project_id_min_char": "El ID del proyecto debe tener al menos 1 carácter", + "project_id_max_char": "El ID del proyecto debe tener como máximo 5 caracteres", + "project_description_placeholder": "Ingresa la descripción del proyecto", "select_network": "Seleccionar red", "lead": "Líder", + "date_range": "Rango de fechas", "private": "Privado", "public": "Público", "accessible_only_by_invite": "Accesible solo por invitación", @@ -218,26 +354,25 @@ "publish": "Publicar", "copy_link": "Copiar enlace", "leave_project": "Abandonar proyecto", - "join_the_project_to_rearrange": "Únete al proyecto para reordenar", - "drag_to_rearrange": "Arrastra para reordenar", + "join_the_project_to_rearrange": "Únete al proyecto para reorganizar", + "drag_to_rearrange": "Arrastra para reorganizar", "congrats": "¡Felicitaciones!", - "project": "Proyecto", "open_project": "Abrir proyecto", - "issues": "Problemas", + "issues": "Elementos de trabajo", "cycles": "Ciclos", "modules": "Módulos", "pages": "Páginas", "intake": "Entrada", "time_tracking": "Seguimiento de tiempo", "work_management": "Gestión del trabajo", - "projects_and_issues": "Proyectos y problemas", + "projects_and_issues": "Proyectos y elementos de trabajo", "projects_and_issues_description": "Activa o desactiva estos en este proyecto.", - "cycles_description": "Organiza el trabajo como mejor te parezca por proyecto y cambia la frecuencia de un período a otro.", + "cycles_description": "Organiza el trabajo en períodos de tiempo según lo consideres conveniente por proyecto y cambia la frecuencia de un período a otro.", "modules_description": "Agrupa el trabajo en configuraciones similares a subproyectos con sus propios líderes y asignados.", "views_description": "Guarda ordenamientos, filtros y opciones de visualización para más tarde o compártelos.", - "pages_description": "Escribe cualquier cosa como escribes cualquier cosa.", - "intake_description": "Mantente al tanto de los problemas a los que estás suscrito. Activa esto para recibir notificaciones.", - "time_tracking_description": "Rastrea el tiempo dedicado a problemas y proyectos.", + "pages_description": "Escribe cualquier cosa como escribirías cualquier cosa.", + "intake_description": "Mantente al tanto de los elementos de trabajo a los que estás suscrito. Activa esto para recibir notificaciones.", + "time_tracking_description": "Rastrea el tiempo dedicado a elementos de trabajo y proyectos.", "work_management_description": "Gestiona tu trabajo y proyectos con facilidad.", "documentation": "Documentación", "message_support": "Mensaje al soporte", @@ -249,50 +384,51 @@ "we_are_having_trouble_fetching_the_updates": "Estamos teniendo problemas para obtener las actualizaciones.", "our_changelogs": "nuestros registros de cambios", "for_the_latest_updates": "para las últimas actualizaciones.", - "please_visit": "Por favor, visita", - "docs": "Documentos", + "please_visit": "Por favor visita", + "docs": "Documentación", "full_changelog": "Registro de cambios completo", "support": "Soporte", "discord": "Discord", "powered_by_plane_pages": "Desarrollado por Plane Pages", - "please_select_at_least_one_invitation": "Por favor, selecciona al menos una invitación.", - "please_select_at_least_one_invitation_description": "Por favor, selecciona al menos una invitación para unirte al espacio de trabajo.", + "please_select_at_least_one_invitation": "Por favor selecciona al menos una invitación.", + "please_select_at_least_one_invitation_description": "Por favor selecciona al menos una invitación para unirte al espacio de trabajo.", "we_see_that_someone_has_invited_you_to_join_a_workspace": "Vemos que alguien te ha invitado a unirte a un espacio de trabajo", - "join_a_workspace": "Unirse a un espacio de trabajo", + "join_a_workspace": "Únete a un espacio de trabajo", "we_see_that_someone_has_invited_you_to_join_a_workspace_description": "Vemos que alguien te ha invitado a unirte a un espacio de trabajo", - "join_a_workspace_description": "Unirse a un espacio de trabajo", + "join_a_workspace_description": "Únete a un espacio de trabajo", "accept_and_join": "Aceptar y unirse", "go_home": "Ir a inicio", "no_pending_invites": "No hay invitaciones pendientes", "you_can_see_here_if_someone_invites_you_to_a_workspace": "Puedes ver aquí si alguien te invita a un espacio de trabajo", - "back_to_home": "Volver al inicio", + "back_to_home": "Volver a inicio", "workspace_name": "nombre-del-espacio-de-trabajo", "deactivate_your_account": "Desactivar tu cuenta", - "deactivate_your_account_description": "Una vez desactivada, no podrás ser asignado a problemas ni se te facturará por tu espacio de trabajo. Para reactivar tu cuenta, necesitarás una invitación a un espacio de trabajo con esta dirección de correo electrónico.", + "deactivate_your_account_description": "Una vez desactivada, no se te podrán asignar elementos de trabajo ni se te facturará por tu espacio de trabajo. Para reactivar tu cuenta, necesitarás una invitación a un espacio de trabajo con esta dirección de correo electrónico.", "deactivating": "Desactivando", "confirm": "Confirmar", + "confirming": "Confirmando", "draft_created": "Borrador creado", - "issue_created_successfully": "Problema creado con éxito", - "draft_creation_failed": "Creación del borrador fallida", - "issue_creation_failed": "Creación del problema fallida", - "draft_issue": "Borrador de problema", - "issue_updated_successfully": "Problema actualizado con éxito", - "issue_could_not_be_updated": "No se pudo actualizar el problema", + "issue_created_successfully": "Elemento de trabajo creado exitosamente", + "draft_creation_failed": "Error al crear borrador", + "issue_creation_failed": "Error al crear elemento de trabajo", + "draft_issue": "Borrador de elemento de trabajo", + "issue_updated_successfully": "Elemento de trabajo actualizado exitosamente", + "issue_could_not_be_updated": "El elemento de trabajo no pudo ser actualizado", "create_a_draft": "Crear un borrador", "save_to_drafts": "Guardar en borradores", "save": "Guardar", "update": "Actualizar", "updating": "Actualizando", - "create_new_issue": "Crear nuevo problema", - "editor_is_not_ready_to_discard_changes": "El editor no está listo para descartar los cambios", - "failed_to_move_issue_to_project": "Error al mover el problema al proyecto", + "create_new_issue": "Crear nuevo elemento de trabajo", + "editor_is_not_ready_to_discard_changes": "El editor no está listo para descartar cambios", + "failed_to_move_issue_to_project": "Error al mover elemento de trabajo al proyecto", "create_more": "Crear más", "add_to_project": "Agregar al proyecto", "discard": "Descartar", - "duplicate_issue_found": "Problema duplicado encontrado", - "duplicate_issues_found": "Problemas duplicados encontrados", + "duplicate_issue_found": "Se encontró un elemento de trabajo duplicado", + "duplicate_issues_found": "Se encontraron elementos de trabajo duplicados", "no_matching_results": "No hay resultados coincidentes", - "title_is_required": "El título es obligatorio", + "title_is_required": "El título es requerido", "title": "Título", "state": "Estado", "priority": "Prioridad", @@ -308,12 +444,1876 @@ "labels": "Etiquetas", "create_new_label": "Crear nueva etiqueta", "start_date": "Fecha de inicio", + "end_date": "Fecha de fin", "due_date": "Fecha de vencimiento", - "cycle": "Ciclo", "estimate": "Estimación", - "change_parent_issue": "Cambiar problema padre", - "remove_parent_issue": "Eliminar problema padre", + "change_parent_issue": "Cambiar elemento de trabajo padre", + "remove_parent_issue": "Eliminar elemento de trabajo padre", "add_parent": "Agregar padre", - "loading_members": "Cargando miembros...", - "inbox": "bandeja de entrada" + "loading_members": "Cargando miembros", + "view_link_copied_to_clipboard": "Enlace de vista copiado al portapapeles.", + "required": "Requerido", + "optional": "Opcional", + "Cancel": "Cancelar", + "edit": "Editar", + "archive": "Archivar", + "restor": "Restaurar", + "open_in_new_tab": "Abrir en nueva pestaña", + "delete": "Eliminar", + "deleting": "Eliminando", + "make_a_copy": "Hacer una copia", + "move_to_project": "Mover al proyecto", + "good": "Buenos", + "morning": "días", + "afternoon": "tardes", + "evening": "noches", + "show_all": "Mostrar todo", + "show_less": "Mostrar menos", + "no_data_yet": "Aún no hay datos", + "syncing": "Sincronizando", + "add_work_item": "Agregar elemento de trabajo", + "advanced_description_placeholder": "Presiona '/' para comandos", + "create_work_item": "Crear elemento de trabajo", + "attachments": "Archivos adjuntos", + "declining": "Rechazando", + "declined": "Rechazado", + "decline": "Rechazar", + "unassigned": "Sin asignar", + "work_items": "Elementos de trabajo", + "add_link": "Agregar enlace", + "points": "Puntos", + "no_assignee": "Sin asignado", + "no_assignees_yet": "Aún no hay asignados", + "no_labels_yet": "Aún no hay etiquetas", + "ideal": "Ideal", + "current": "Actual", + "no_matching_members": "No hay miembros coincidentes", + "leaving": "Abandonando", + "removing": "Eliminando", + "leave": "Abandonar", + "refresh": "Actualizar", + "refreshing": "Actualizando", + "refresh_status": "Actualizar estado", + "prev": "Anterior", + "next": "Siguiente", + "re_generating": "Regenerando", + "re_generate": "Regenerar", + "re_generate_key": "Regenerar clave", + "export": "Exportar", + "member": "{count, plural, one{# miembro} other{# miembros}}", + + "project_view": { + "sort_by": { + "created_at": "Creado el", + "updated_at": "Actualizado el", + "name": "Nombre" + } + }, + + "toast": { + "success": "¡Éxito!", + "error": "¡Error!" + }, + + "links": { + "toasts": { + "created": { + "title": "Enlace creado", + "message": "El enlace se ha creado correctamente" + }, + "not_created": { + "title": "Enlace no creado", + "message": "No se pudo crear el enlace" + }, + "updated": { + "title": "Enlace actualizado", + "message": "El enlace se ha actualizado correctamente" + }, + "not_updated": { + "title": "Enlace no actualizado", + "message": "No se pudo actualizar el enlace" + }, + "removed": { + "title": "Enlace eliminado", + "message": "El enlace se ha eliminado correctamente" + }, + "not_removed": { + "title": "Enlace no eliminado", + "message": "No se pudo eliminar el enlace" + } + } + }, + + "home": { + "empty": { + "create_project": { + "title": "Crear un proyecto", + "description": "La mayoría de las cosas comienzan con un proyecto en Plane.", + "cta": "Comenzar" + }, + "invite_team": { + "title": "Invita a tu equipo", + "description": "Construye, implementa y gestiona con compañeros de trabajo.", + "cta": "Hazlos entrar" + }, + "configure_workspace": { + "title": "Configura tu espacio de trabajo.", + "description": "Activa o desactiva funciones o ve más allá.", + "cta": "Configurar este espacio de trabajo" + }, + "personalize_account": { + "title": "Haz Plane tuyo.", + "description": "Elige tu foto, colores y más.", + "cta": "Personalizar ahora" + }, + "widgets": { + "title": "Está Silencioso Sin Widgets, Actívalos", + "description": "Parece que todos tus widgets están desactivados. ¡Actívalos\nahora para mejorar tu experiencia!", + "primary_button": { + "text": "Gestionar widgets" + } + } + }, + "quick_links": { + "empty": "Guarda enlaces a cosas de trabajo que te gustaría tener a mano.", + "add": "Agregar enlace rápido", + "title": "Enlace rápido", + "title_plural": "Enlaces rápidos" + }, + "recents": { + "title": "Recientes", + "empty": { + "project": "Tus proyectos recientes aparecerán aquí una vez que visites uno.", + "page": "Tus páginas recientes aparecerán aquí una vez que visites una.", + "issue": "Tus elementos de trabajo recientes aparecerán aquí una vez que visites uno.", + "default": "Aún no tienes elementos recientes." + }, + "filters": { + "all": "Todos los elementos", + "projects": "Proyectos", + "pages": "Páginas", + "issues": "Elementos de trabajo" + } + }, + "new_at_plane": { + "title": "Nuevo en Plane" + }, + "quick_tutorial": { + "title": "Tutorial rápido" + }, + "widget": { + "reordered_successfully": "Widget reordenado correctamente.", + "reordering_failed": "Ocurrió un error al reordenar el widget." + }, + "manage_widgets": "Gestionar widgets", + "title": "Inicio", + "star_us_on_github": "Danos una estrella en GitHub" + }, + + "link": { + "modal": { + "url": { + "text": "URL", + "required": "La URL no es válida", + "placeholder": "Escribe o pega una URL" + }, + "title": { + "text": "Título a mostrar", + "placeholder": "Cómo te gustaría ver este enlace" + } + } + }, + + "common": { + "all": "Todo", + "states": "Estados", + "state": "Estado", + "state_groups": "Grupos de estados", + "priority": "Prioridad", + "team_project": "Proyecto de equipo", + "project": "Proyecto", + "cycle": "Ciclo", + "cycles": "Ciclos", + "module": "Módulo", + "modules": "Módulos", + "labels": "Etiquetas", + "assignees": "Asignados", + "assignee": "Asignado", + "created_by": "Creado por", + "none": "Ninguno", + "link": "Enlace", + "estimate": "Estimación", + "layout": "Diseño", + "filters": "Filtros", + "display": "Mostrar", + "load_more": "Cargar más", + "activity": "Actividad", + "analytics": "Análisis", + "dates": "Fechas", + "success": "¡Éxito!", + "something_went_wrong": "Algo salió mal", + "error": { + "label": "¡Error!", + "message": "Ocurrió un error. Por favor, inténtalo de nuevo." + }, + "group_by": "Agrupar por", + "epic": "Epic", + "epics": "Epics", + "work_item": "Elemento de trabajo", + "sub_work_item": "Sub-elemento de trabajo", + "add": "Agregar", + "warning": "Advertencia", + "updating": "Actualizando", + "adding": "Agregando", + "update": "Actualizar", + "creating": "Creando", + "create": "Crear", + "cancel": "Cancelar", + "description": "Descripción", + "title": "Título", + "attachment": "Archivo adjunto", + "general": "General", + "features": "Características", + "automation": "Automatización", + "project_name": "Nombre del proyecto", + "project_id": "ID del proyecto", + "project_timezone": "Zona horaria del proyecto", + "created_on": "Creado el", + "update_project": "Actualizar proyecto", + "identifier_already_exists": "El identificador ya existe", + "add_more": "Agregar más", + "defaults": "Valores predeterminados", + "add_label": "Agregar etiqueta", + "estimates": "Estimaciones", + "customize_time_range": "Personalizar rango de tiempo", + "loading": "Cargando", + "attachments": "Archivos adjuntos", + "properties": "Propiedades", + "parent": "Padre", + "remove": "Eliminar", + "archiving": "Archivando", + "archive": "Archivar", + "access": { + "public": "Público", + "private": "Privado" + }, + "done": "Hecho", + "sub_work_items": "Sub-elementos de trabajo", + "comment": "Comentario", + "workspace_level": "Nivel de espacio de trabajo", + "order_by": { + "label": "Ordenar por", + "manual": "Manual", + "last_created": "Último creado", + "last_updated": "Última actualización", + "start_date": "Fecha de inicio", + "due_date": "Fecha de vencimiento", + "asc": "Ascendente", + "desc": "Descendente", + "updated_on": "Actualizado el" + }, + "sort": { + "asc": "Ascendente", + "desc": "Descendente", + "created_on": "Creado el", + "updated_on": "Actualizado el" + }, + "comments": "Comentarios", + "updates": "Actualizaciones", + "clear_all": "Limpiar todo", + "copied": "¡Copiado!", + "link_copied": "¡Enlace copiado!", + "link_copied_to_clipboard": "Enlace copiado al portapapeles", + "copied_to_clipboard": "Enlace del elemento de trabajo copiado al portapapeles", + "is_copied_to_clipboard": "El elemento de trabajo está copiado al portapapeles", + "no_links_added_yet": "Aún no se han agregado enlaces", + "add_link": "Agregar enlace", + "links": "Enlaces", + "go_to_workspace": "Ir al espacio de trabajo", + "progress": "Progreso", + "optional": "Opcional", + "join": "Unirse", + "go_back": "Volver", + "continue": "Continuar", + "resend": "Reenviar", + "relations": "Relaciones", + "errors": { + "default": { + "title": "¡Error!", + "message": "Algo salió mal. Por favor, inténtalo de nuevo." + }, + "required": "Este campo es obligatorio", + "entity_required": "{entity} es obligatorio" + }, + "update_link": "Actualizar enlace", + "attach": "Adjuntar", + "create_new": "Crear nuevo", + "add_existing": "Agregar existente", + "type_or_paste_a_url": "Escribe o pega una URL", + "url_is_invalid": "La URL no es válida", + "display_title": "Título a mostrar", + "link_title_placeholder": "Cómo te gustaría ver este enlace", + "url": "URL", + "side_peek": "Vista lateral", + "modal": "Modal", + "full_screen": "Pantalla completa", + "close_peek_view": "Cerrar la vista previa", + "toggle_peek_view_layout": "Alternar diseño de vista previa", + "options": "Opciones", + "duration": "Duración", + "today": "Hoy", + "week": "Semana", + "month": "Mes", + "quarter": "Trimestre", + "press_for_commands": "Presiona '/' para comandos", + "click_to_add_description": "Haz clic para agregar descripción", + "search": { + "label": "Buscar", + "placeholder": "Escribe para buscar", + "no_matches_found": "No se encontraron coincidencias", + "no_matching_results": "No hay resultados coincidentes" + }, + "actions": { + "edit": "Editar", + "make_a_copy": "Hacer una copia", + "open_in_new_tab": "Abrir en nueva pestaña", + "copy_link": "Copiar enlace", + "archive": "Archivar", + "delete": "Eliminar", + "remove_relation": "Eliminar relación", + "subscribe": "Suscribirse", + "unsubscribe": "Cancelar suscripción", + "clear_sorting": "Limpiar ordenamiento", + "show_weekends": "Mostrar fines de semana", + "enable": "Habilitar", + "disable": "Deshabilitar" + }, + "name": "Nombre", + "discard": "Descartar", + "confirm": "Confirmar", + "confirming": "Confirmando", + "read_the_docs": "Leer la documentación", + "default": "Predeterminado", + "active": "Activo", + "enabled": "Habilitado", + "disabled": "Deshabilitado", + "mandate": "Mandato", + "mandatory": "Obligatorio", + "yes": "Sí", + "no": "No", + "please_wait": "Por favor espera", + "enabling": "Habilitando", + "disabling": "Deshabilitando", + "beta": "Beta", + "or": "o", + "next": "Siguiente", + "back": "Atrás", + "cancelling": "Cancelando", + "configuring": "Configurando", + "clear": "Limpiar", + "import": "Importar", + "connect": "Conectar", + "authorizing": "Autorizando", + "processing": "Procesando", + "no_data_available": "No hay datos disponibles", + "from": "de {name}", + "authenticated": "Autenticado", + "select": "Seleccionar", + "upgrade": "Mejorar", + "add_seats": "Agregar asientos", + "label": "Etiqueta", + "priorities": "Prioridades", + "projects": "Proyectos", + "workspace": "Espacio de trabajo", + "workspaces": "Espacios de trabajo", + "team": "Equipo", + "teams": "Equipos", + "entity": "Entidad", + "entities": "Entidades", + "task": "Tarea", + "tasks": "Tareas", + "section": "Sección", + "sections": "Secciones", + "edit": "Editar", + "connecting": "Conectando", + "connected": "Conectado", + "disconnect": "Desconectar", + "disconnecting": "Desconectando", + "installing": "Instalando", + "install": "Instalar" + }, + + "form": { + "title": { + "required": "El título es obligatorio", + "max_length": "El título debe tener menos de {length} caracteres" + } + }, + + "entity": { + "grouping_title": "Agrupación de {entity}", + "priority": "Prioridad de {entity}", + "all": "Todos los {entity}", + "drop_here_to_move": "Suelta aquí para mover el {entity}", + "delete": { + "label": "Eliminar {entity}", + "success": "{entity} eliminado correctamente", + "failed": "Error al eliminar {entity}" + }, + "update": { + "failed": "Error al actualizar {entity}", + "success": "{entity} actualizado correctamente" + }, + "link_copied_to_clipboard": "Enlace de {entity} copiado al portapapeles", + "fetch": { + "failed": "Error al obtener {entity}" + }, + "add": { + "success": "{entity} agregado correctamente", + "failed": "Error al agregar {entity}" + } + }, + + "epic": { + "all": "Todos los Epics", + "label": "{count, plural, one {Epic} other {Epics}}", + "new": "Nuevo Epic", + "adding": "Agregando epic", + "create": { + "success": "Epic creado correctamente" + }, + "add": { + "press_enter": "Presiona 'Enter' para agregar otro epic", + "label": "Agregar Epic" + }, + "title": { + "label": "Título del Epic", + "required": "El título del epic es obligatorio." + } + }, + + "issue": { + "label": "{count, plural, one {Elemento de trabajo} other {Elementos de trabajo}}", + "all": "Todos los elementos de trabajo", + "edit": "Editar elemento de trabajo", + "title": { + "label": "Título del elemento de trabajo", + "required": "El título del elemento de trabajo es obligatorio." + }, + "add": { + "press_enter": "Presiona 'Enter' para agregar otro elemento de trabajo", + "label": "Agregar elemento de trabajo", + "cycle": { + "failed": "No se pudo agregar el elemento de trabajo al ciclo. Por favor, inténtalo de nuevo.", + "success": "{count, plural, one {Elemento de trabajo agregado} other {Elementos de trabajo agregados}} al ciclo correctamente.", + "loading": "Agregando {count, plural, one {elemento de trabajo} other {elementos de trabajo}} al ciclo" + }, + "assignee": "Agregar asignados", + "start_date": "Agregar fecha de inicio", + "due_date": "Agregar fecha de vencimiento", + "parent": "Agregar elemento de trabajo padre", + "sub_issue": "Agregar sub-elemento de trabajo", + "relation": "Agregar relación", + "link": "Agregar enlace", + "existing": "Agregar elemento de trabajo existente" + }, + "remove": { + "label": "Eliminar elemento de trabajo", + "cycle": { + "loading": "Eliminando elemento de trabajo del ciclo", + "success": "Elemento de trabajo eliminado del ciclo correctamente.", + "failed": "No se pudo eliminar el elemento de trabajo del ciclo. Por favor, inténtalo de nuevo." + }, + "module": { + "loading": "Eliminando elemento de trabajo del módulo", + "success": "Elemento de trabajo eliminado del módulo correctamente.", + "failed": "No se pudo eliminar el elemento de trabajo del módulo. Por favor, inténtalo de nuevo." + }, + "parent": { + "label": "Eliminar elemento de trabajo padre" + } + }, + "new": "Nuevo elemento de trabajo", + "adding": "Agregando elemento de trabajo", + "create": { + "success": "Elemento de trabajo creado correctamente" + }, + "priority": { + "urgent": "Urgente", + "high": "Alta", + "medium": "Media", + "low": "Baja" + }, + "display": { + "properties": { + "label": "Mostrar propiedades", + "id": "ID", + "issue_type": "Tipo de elemento de trabajo", + "sub_issue_count": "Cantidad de sub-elementos", + "attachment_count": "Cantidad de archivos adjuntos", + "created_on": "Creado el", + "sub_issue": "Sub-elemento de trabajo" + }, + "extra": { + "show_sub_issues": "Mostrar sub-elementos", + "show_empty_groups": "Mostrar grupos vacíos" + } + }, + "layouts": { + "ordered_by_label": "Este diseño está ordenado por", + "list": "Lista", + "kanban": "Tablero", + "calendar": "Calendario", + "spreadsheet": "Tabla", + "gantt": "Línea de tiempo", + "title": { + "list": "Diseño de lista", + "kanban": "Diseño de tablero", + "calendar": "Diseño de calendario", + "spreadsheet": "Diseño de tabla", + "gantt": "Diseño de línea de tiempo" + } + }, + "states": { + "active": "Activo", + "backlog": "Pendientes" + }, + "comments": { + "placeholder": "Agregar comentario", + "switch": { + "private": "Cambiar a comentario privado", + "public": "Cambiar a comentario público" + }, + "create": { + "success": "Comentario creado correctamente", + "error": "Error al crear el comentario. Por favor, inténtalo más tarde." + }, + "update": { + "success": "Comentario actualizado correctamente", + "error": "Error al actualizar el comentario. Por favor, inténtalo más tarde." + }, + "remove": { + "success": "Comentario eliminado correctamente", + "error": "Error al eliminar el comentario. Por favor, inténtalo más tarde." + }, + "upload": { + "error": "Error al subir el archivo. Por favor, inténtalo más tarde." + } + }, + "empty_state": { + "issue_detail": { + "title": "El elemento de trabajo no existe", + "description": "El elemento de trabajo que buscas no existe, ha sido archivado o ha sido eliminado.", + "primary_button": { + "text": "Ver otros elementos de trabajo" + } + } + }, + "sibling": { + "label": "Elementos de trabajo hermanos" + }, + "archive": { + "description": "Solo los elementos de trabajo completados\no cancelados pueden ser archivados", + "label": "Archivar elemento de trabajo", + "confirm_message": "¿Estás seguro de que quieres archivar el elemento de trabajo? Todos tus elementos archivados pueden ser restaurados más tarde.", + "success": { + "label": "Archivo exitoso", + "message": "Tus archivos se pueden encontrar en los archivos del proyecto." + }, + "failed": { + "message": "No se pudo archivar el elemento de trabajo. Por favor, inténtalo de nuevo." + } + }, + "restore": { + "success": { + "title": "Restauración exitosa", + "message": "Tu elemento de trabajo se puede encontrar en los elementos de trabajo del proyecto." + }, + "failed": { + "message": "No se pudo restaurar el elemento de trabajo. Por favor, inténtalo de nuevo." + } + }, + "relation": { + "relates_to": "Se relaciona con", + "duplicate": "Duplicado de", + "blocked_by": "Bloqueado por", + "blocking": "Bloqueando" + }, + "copy_link": "Copiar enlace del elemento de trabajo", + "delete": { + "label": "Eliminar elemento de trabajo", + "error": "Error al eliminar el elemento de trabajo" + }, + "subscription": { + "actions": { + "subscribed": "Suscrito al elemento de trabajo correctamente", + "unsubscribed": "Desuscrito del elemento de trabajo correctamente" + } + }, + "select": { + "error": "Por favor selecciona al menos un elemento de trabajo", + "empty": "No hay elementos de trabajo seleccionados", + "add_selected": "Agregar elementos seleccionados" + }, + "open_in_full_screen": "Abrir elemento de trabajo en pantalla completa" + }, + + "attachment": { + "error": "No se pudo adjuntar el archivo. Intenta subirlo de nuevo.", + "only_one_file_allowed": "Solo se puede subir un archivo a la vez.", + "file_size_limit": "El archivo debe tener {size}MB o menos de tamaño.", + "drag_and_drop": "Arrastra y suelta en cualquier lugar para subir", + "delete": "Eliminar archivo adjunto" + }, + + "label": { + "select": "Seleccionar etiqueta", + "create": { + "success": "Etiqueta creada correctamente", + "failed": "Error al crear la etiqueta", + "already_exists": "La etiqueta ya existe", + "type": "Escribe para agregar una nueva etiqueta" + } + }, + + "sub_work_item": { + "update": { + "success": "Sub-elemento actualizado correctamente", + "error": "Error al actualizar el sub-elemento" + }, + "remove": { + "success": "Sub-elemento eliminado correctamente", + "error": "Error al eliminar el sub-elemento" + } + }, + + "view": { + "label": "{count, plural, one {Vista} other {Vistas}}", + "create": { + "label": "Crear vista" + }, + "update": { + "label": "Actualizar vista" + } + }, + + "inbox_issue": { + "status": { + "pending": { + "title": "Pendiente", + "description": "Pendiente" + }, + "declined": { + "title": "Rechazado", + "description": "Rechazado" + }, + "snoozed": { + "title": "Pospuesto", + "description": "Faltan {days, plural, one{# día} other{# días}}" + }, + "accepted": { + "title": "Aceptado", + "description": "Aceptado" + }, + "duplicate": { + "title": "Duplicado", + "description": "Duplicado" + } + }, + "modals": { + "decline": { + "title": "Rechazar elemento de trabajo", + "content": "¿Estás seguro de que quieres rechazar el elemento de trabajo {value}?" + }, + "delete": { + "title": "Eliminar elemento de trabajo", + "content": "¿Estás seguro de que quieres eliminar el elemento de trabajo {value}?", + "success": "Elemento de trabajo eliminado correctamente" + } + }, + "errors": { + "snooze_permission": "Solo los administradores del proyecto pueden posponer/desposponer elementos de trabajo", + "accept_permission": "Solo los administradores del proyecto pueden aceptar elementos de trabajo", + "decline_permission": "Solo los administradores del proyecto pueden rechazar elementos de trabajo" + }, + "actions": { + "accept": "Aceptar", + "decline": "Rechazar", + "snooze": "Posponer", + "unsnooze": "Desposponer", + "copy": "Copiar enlace del elemento de trabajo", + "delete": "Eliminar", + "open": "Abrir elemento de trabajo", + "mark_as_duplicate": "Marcar como duplicado", + "move": "Mover {value} a elementos de trabajo del proyecto" + }, + "source": { + "in-app": "en-app" + }, + "order_by": { + "created_at": "Creado el", + "updated_at": "Actualizado el", + "id": "ID" + }, + "label": "Intake", + "page_label": "{workspace} - Intake", + "modal": { + "title": "Crear elemento de trabajo de intake" + }, + "tabs": { + "open": "Abiertos", + "closed": "Cerrados" + }, + "empty_state": { + "sidebar_open_tab": { + "title": "No hay elementos de trabajo abiertos", + "description": "Encuentra elementos de trabajo abiertos aquí. Crea un nuevo elemento de trabajo." + }, + "sidebar_closed_tab": { + "title": "No hay elementos de trabajo cerrados", + "description": "Todos los elementos de trabajo, ya sean aceptados o rechazados, se pueden encontrar aquí." + }, + "sidebar_filter": { + "title": "No hay elementos de trabajo coincidentes", + "description": "Ningún elemento de trabajo coincide con el filtro aplicado en intake. Crea un nuevo elemento de trabajo." + }, + "detail": { + "title": "Selecciona un elemento de trabajo para ver sus detalles." + } + } + }, + + "workspace_creation": { + "heading": "Crea tu espacio de trabajo", + "subheading": "Para comenzar a usar Plane, necesitas crear o unirte a un espacio de trabajo.", + "form": { + "name": { + "label": "Nombra tu espacio de trabajo", + "placeholder": "Algo familiar y reconocible es siempre lo mejor." + }, + "url": { + "label": "Establece la URL de tu espacio de trabajo", + "placeholder": "Escribe o pega una URL", + "edit_slug": "Solo puedes editar el slug de la URL" + }, + "organization_size": { + "label": "¿Cuántas personas usarán este espacio de trabajo?", + "placeholder": "Selecciona un rango" + } + }, + "errors": { + "creation_disabled": { + "title": "Solo el administrador de tu instancia puede crear espacios de trabajo", + "description": "Si conoces la dirección de correo electrónico del administrador de tu instancia, haz clic en el botón de abajo para ponerte en contacto con él.", + "request_button": "Solicitar administrador de instancia" + }, + "validation": { + "name_alphanumeric": "Los nombres de espacios de trabajo solo pueden contener (' '), ('-'), ('_') y caracteres alfanuméricos.", + "name_length": "Limita tu nombre a 80 caracteres.", + "url_alphanumeric": "Las URLs solo pueden contener ('-') y caracteres alfanuméricos.", + "url_length": "Limita tu URL a 48 caracteres.", + "url_already_taken": "¡La URL del espacio de trabajo ya está en uso!" + } + }, + "request_email": { + "subject": "Solicitando un nuevo espacio de trabajo", + "body": "Hola administrador(es) de instancia,\n\nPor favor, crea un nuevo espacio de trabajo con la URL [/nombre-espacio-trabajo] para [propósito de crear el espacio de trabajo].\n\nGracias,\n{firstName} {lastName}\n{email}" + }, + "button": { + "default": "Crear espacio de trabajo", + "loading": "Creando espacio de trabajo" + }, + "toast": { + "success": { + "title": "Éxito", + "message": "Espacio de trabajo creado correctamente" + }, + "error": { + "title": "Error", + "message": "No se pudo crear el espacio de trabajo. Por favor, inténtalo de nuevo." + } + } + }, + + "workspace_dashboard": { + "empty_state": { + "general": { + "title": "Resumen de tus proyectos, actividad y métricas", + "description": "Bienvenido a Plane, estamos emocionados de tenerte aquí. Crea tu primer proyecto y rastrea tus elementos de trabajo, y esta página se transformará en un espacio que te ayuda a progresar. Los administradores también verán elementos que ayudan a su equipo a progresar.", + "primary_button": { + "text": "Construye tu primer proyecto", + "comic": { + "title": "Todo comienza con un proyecto en Plane", + "description": "Un proyecto podría ser la hoja de ruta de un producto, una campaña de marketing o el lanzamiento de un nuevo automóvil." + } + } + } + } + }, + + "workspace_analytics": { + "label": "Análisis", + "page_label": "{workspace} - Análisis", + "open_tasks": "Total de tareas abiertas", + "error": "Hubo un error al obtener los datos.", + "work_items_closed_in": "Elementos de trabajo cerrados en", + "selected_projects": "Proyectos seleccionados", + "total_members": "Total de miembros", + "total_cycles": "Total de Ciclos", + "total_modules": "Total de Módulos", + "pending_work_items": { + "title": "Elementos de trabajo pendientes", + "empty_state": "El análisis de elementos de trabajo pendientes por compañeros aparece aquí." + }, + "work_items_closed_in_a_year": { + "title": "Elementos de trabajo cerrados en un año", + "empty_state": "Cierra elementos de trabajo para ver su análisis en forma de gráfico." + }, + "most_work_items_created": { + "title": "Más elementos de trabajo creados", + "empty_state": "Los compañeros y el número de elementos de trabajo creados por ellos aparecen aquí." + }, + "most_work_items_closed": { + "title": "Más elementos de trabajo cerrados", + "empty_state": "Los compañeros y el número de elementos de trabajo cerrados por ellos aparecen aquí." + }, + "tabs": { + "scope_and_demand": "Alcance y Demanda", + "custom": "Análisis Personalizado" + }, + "empty_state": { + "general": { + "title": "Rastrea el progreso, cargas de trabajo y asignaciones. Identifica tendencias, elimina bloqueos y mueve el trabajo más rápido", + "description": "Observa el alcance versus la demanda, estimaciones y el aumento del alcance. Obtén el rendimiento por miembros del equipo y equipos, y asegúrate de que tu proyecto se ejecute a tiempo.", + "primary_button": { + "text": "Inicia tu primer proyecto", + "comic": { + "title": "El análisis funciona mejor con Ciclos + Módulos", + "description": "Primero, organiza tus elementos de trabajo en Ciclos y, si puedes, agrupa los elementos de trabajo que abarcan más de un ciclo en Módulos. Revisa ambos en la navegación izquierda." + } + } + } + } + }, + + "workspace_projects": { + "label": "{count, plural, one {Proyecto} other {Proyectos}}", + "create": { + "label": "Agregar Proyecto" + }, + "network": { + "private": { + "title": "Privado", + "description": "Accesible solo por invitación" + }, + "public": { + "title": "Público", + "description": "Cualquiera en el espacio de trabajo excepto Invitados puede unirse" + } + }, + "error": { + "permission": "No tienes permiso para realizar esta acción.", + "cycle_delete": "Error al eliminar el ciclo", + "module_delete": "Error al eliminar el módulo", + "issue_delete": "Error al eliminar el elemento de trabajo" + }, + "state": { + "backlog": "Pendiente", + "unstarted": "Sin iniciar", + "started": "Iniciado", + "completed": "Completado", + "cancelled": "Cancelado" + }, + "sort": { + "manual": "Manual", + "name": "Nombre", + "created_at": "Fecha de creación", + "members_length": "Número de miembros" + }, + "scope": { + "my_projects": "Mis proyectos", + "archived_projects": "Archivados" + }, + "common": { + "months_count": "{months, plural, one{# mes} other{# meses}}" + }, + "empty_state": { + "general": { + "title": "No hay proyectos activos", + "description": "Piensa en cada proyecto como el padre para el trabajo orientado a objetivos. Los proyectos son donde viven las Tareas, Ciclos y Módulos y, junto con tus colegas, te ayudan a alcanzar ese objetivo. Crea un nuevo proyecto o filtra por proyectos archivados.", + "primary_button": { + "text": "Inicia tu primer proyecto", + "comic": { + "title": "Todo comienza con un proyecto en Plane", + "description": "Un proyecto podría ser la hoja de ruta de un producto, una campaña de marketing o el lanzamiento de un nuevo automóvil." + } + } + }, + "no_projects": { + "title": "Sin proyecto", + "description": "Para crear elementos de trabajo o gestionar tu trabajo, necesitas crear un proyecto o ser parte de uno.", + "primary_button": { + "text": "Inicia tu primer proyecto", + "comic": { + "title": "Todo comienza con un proyecto en Plane", + "description": "Un proyecto podría ser la hoja de ruta de un producto, una campaña de marketing o el lanzamiento de un nuevo automóvil." + } + } + }, + "filter": { + "title": "No hay proyectos coincidentes", + "description": "No se detectaron proyectos con los criterios coincidentes. \n Crea un nuevo proyecto en su lugar." + }, + "search": { + "description": "No se detectaron proyectos con los criterios coincidentes.\nCrea un nuevo proyecto en su lugar" + } + } + }, + + "workspace_views": { + "add_view": "Agregar vista", + "empty_state": { + "all-issues": { + "title": "No hay elementos de trabajo en el proyecto", + "description": "¡Primer proyecto completado! Ahora, divide tu trabajo en piezas rastreables con elementos de trabajo. ¡Vamos!", + "primary_button": { + "text": "Crear nuevo elemento de trabajo" + } + }, + "assigned": { + "title": "No hay elementos de trabajo aún", + "description": "Los elementos de trabajo asignados a ti se pueden rastrear desde aquí.", + "primary_button": { + "text": "Crear nuevo elemento de trabajo" + } + }, + "created": { + "title": "No hay elementos de trabajo aún", + "description": "Todos los elementos de trabajo creados por ti vienen aquí, rastréalos aquí directamente.", + "primary_button": { + "text": "Crear nuevo elemento de trabajo" + } + }, + "subscribed": { + "title": "No hay elementos de trabajo aún", + "description": "Suscríbete a los elementos de trabajo que te interesan, rastréalos todos aquí." + }, + "custom-view": { + "title": "No hay elementos de trabajo aún", + "description": "Elementos de trabajo que aplican a los filtros, rastréalos todos aquí." + } + } + }, + + "workspace_settings": { + "label": "Configuración del espacio de trabajo", + "page_label": "{workspace} - Configuración general", + "key_created": "Clave creada", + "copy_key": "Copia y guarda esta clave secreta en Plane Pages. No podrás ver esta clave después de hacer clic en Cerrar. Se ha descargado un archivo CSV que contiene la clave.", + "token_copied": "Token copiado al portapapeles.", + "settings": { + "general": { + "title": "General", + "upload_logo": "Subir logo", + "edit_logo": "Editar logo", + "name": "Nombre del espacio de trabajo", + "company_size": "Tamaño de la empresa", + "url": "URL del espacio de trabajo", + "update_workspace": "Actualizar espacio de trabajo", + "delete_workspace": "Eliminar espacio de trabajo", + "delete_workspace_description": "Al eliminar un espacio de trabajo, todos los datos y recursos dentro de ese espacio de trabajo se eliminarán permanentemente y no se podrán recuperar.", + "delete_btn": "Eliminar mi espacio de trabajo", + "errors": { + "name": { + "required": "El nombre es obligatorio", + "max_length": "El nombre del espacio de trabajo no debe exceder los 80 caracteres" + }, + "company_size": { + "required": "El tamaño de la empresa es obligatorio" + } + } + }, + "members": { + "title": "Miembros", + "add_member": "Agregar miembro", + "invitations_sent_successfully": "Invitaciones enviadas exitosamente", + "leave_confirmation": "¿Estás seguro de que quieres abandonar el espacio de trabajo? Ya no tendrás acceso a este espacio de trabajo. Esta acción no se puede deshacer.", + "details": { + "full_name": "Nombre completo", + "display_name": "Nombre para mostrar", + "email_address": "Dirección de correo electrónico", + "account_type": "Tipo de cuenta", + "authentication": "Autenticación", + "joining_date": "Fecha de incorporación" + }, + "modal": { + "title": "Invitar personas a colaborar", + "description": "Invita personas a colaborar en tu espacio de trabajo.", + "button": "Enviar invitaciones", + "button_loading": "Enviando invitaciones", + "placeholder": "nombre@empresa.com", + "errors": { + "required": "Necesitamos una dirección de correo electrónico para invitarlos.", + "invalid": "El correo electrónico no es válido" + } + } + }, + "billing_and_plans": { + "title": "Facturación y Planes", + "current_plan": "Plan actual", + "free_plan": "Actualmente estás usando el plan gratuito", + "view_plans": "Ver planes" + }, + "exports": { + "title": "Exportaciones", + "exporting": "Exportando", + "previous_exports": "Exportaciones anteriores", + "export_separate_files": "Exportar los datos en archivos separados", + "modal": { + "title": "Exportar a", + "toasts": { + "success": { + "title": "Exportación exitosa", + "message": "Podrás descargar el {entity} exportado desde la exportación anterior." + }, + "error": { + "title": "Exportación fallida", + "message": "La exportación no tuvo éxito. Por favor, inténtalo de nuevo." + } + } + } + }, + "webhooks": { + "title": "Webhooks", + "add_webhook": "Agregar webhook", + "modal": { + "title": "Crear webhook", + "details": "Detalles del webhook", + "payload": "URL del payload", + "question": "¿Qué eventos te gustaría que activaran este webhook?", + "error": "La URL es obligatoria" + }, + "secret_key": { + "title": "Clave secreta", + "message": "Genera un token para iniciar sesión en el payload del webhook" + }, + "options": { + "all": "Envíame todo", + "individual": "Seleccionar eventos individuales" + }, + "toasts": { + "created": { + "title": "Webhook creado", + "message": "El webhook se ha creado exitosamente" + }, + "not_created": { + "title": "Webhook no creado", + "message": "No se pudo crear el webhook" + }, + "updated": { + "title": "Webhook actualizado", + "message": "El webhook se ha actualizado exitosamente" + }, + "not_updated": { + "title": "Webhook no actualizado", + "message": "No se pudo actualizar el webhook" + }, + "removed": { + "title": "Webhook eliminado", + "message": "El webhook se ha eliminado exitosamente" + }, + "not_removed": { + "title": "Webhook no eliminado", + "message": "No se pudo eliminar el webhook" + }, + "secret_key_copied": { + "message": "Clave secreta copiada al portapapeles." + }, + "secret_key_not_copied": { + "message": "Ocurrió un error al copiar la clave secreta." + } + } + }, + "api_tokens": { + "title": "Tokens de API", + "add_token": "Agregar token de API", + "create_token": "Crear token", + "never_expires": "Nunca expira", + "generate_token": "Generar token", + "generating": "Generando", + "delete": { + "title": "Eliminar token de API", + "description": "Cualquier aplicación que use este token ya no tendrá acceso a los datos de Plane. Esta acción no se puede deshacer.", + "success": { + "title": "¡Éxito!", + "message": "El token de API se ha eliminado exitosamente" + }, + "error": { + "title": "¡Error!", + "message": "No se pudo eliminar el token de API" + } + } + } + }, + "empty_state": { + "api_tokens": { + "title": "No se han creado tokens de API", + "description": "Las APIs de Plane se pueden usar para integrar tus datos en Plane con cualquier sistema externo. Crea un token para comenzar." + }, + "webhooks": { + "title": "No se han agregado webhooks", + "description": "Crea webhooks para recibir actualizaciones en tiempo real y automatizar acciones." + }, + "exports": { + "title": "No hay exportaciones aún", + "description": "Cada vez que exportes, también tendrás una copia aquí para referencia." + }, + "imports": { + "title": "No hay importaciones aún", + "description": "Encuentra todas tus importaciones anteriores aquí y descárgalas." + } + } + }, + + "profile": { + "label": "Perfil", + "page_label": "Tu trabajo", + "work": "Trabajo", + "details": { + "joined_on": "Se unió el", + "time_zone": "Zona horaria" + }, + "stats": { + "workload": "Carga de trabajo", + "overview": "Resumen", + "created": "Elementos de trabajo creados", + "assigned": "Elementos de trabajo asignados", + "subscribed": "Elementos de trabajo suscritos", + "state_distribution": { + "title": "Elementos de trabajo por estado", + "empty": "Crea elementos de trabajo para verlos por estados en el gráfico para un mejor análisis." + }, + "priority_distribution": { + "title": "Elementos de trabajo por Prioridad", + "empty": "Crea elementos de trabajo para verlos por prioridad en el gráfico para un mejor análisis." + }, + "recent_activity": { + "title": "Actividad reciente", + "empty": "No pudimos encontrar datos. Por favor revisa tus entradas", + "button": "Descargar actividad de hoy", + "button_loading": "Descargando" + } + }, + "actions": { + "profile": "Perfil", + "security": "Seguridad", + "activity": "Actividad", + "appearance": "Apariencia", + "notifications": "Notificaciones" + }, + "tabs": { + "summary": "Resumen", + "assigned": "Asignado", + "created": "Creado", + "subscribed": "Suscrito", + "activity": "Actividad" + }, + "empty_state": { + "activity": { + "title": "Aún no hay actividades", + "description": "¡Comienza creando un nuevo elemento de trabajo! Agrégale detalles y propiedades. Explora más en Plane para ver tu actividad." + }, + "assigned": { + "title": "No hay elementos de trabajo asignados a ti", + "description": "Los elementos de trabajo asignados a ti se pueden rastrear desde aquí." + }, + "created": { + "title": "Aún no hay elementos de trabajo", + "description": "Todos los elementos de trabajo creados por ti aparecen aquí, rastréalos directamente aquí." + }, + "subscribed": { + "title": "Aún no hay elementos de trabajo", + "description": "Suscríbete a los elementos de trabajo que te interesen, rastréalos todos aquí." + } + } + }, + + "project_settings": { + "general": { + "enter_project_id": "Ingresa el ID del proyecto", + "please_select_a_timezone": "Por favor selecciona una zona horaria", + "archive_project": { + "title": "Archivar proyecto", + "description": "Archivar un proyecto lo eliminará de tu navegación lateral aunque aún podrás acceder a él desde tu página de proyectos. Puedes restaurar el proyecto o eliminarlo cuando quieras.", + "button": "Archivar proyecto" + }, + "delete_project": { + "title": "Eliminar proyecto", + "description": "Al eliminar un proyecto, todos los datos y recursos dentro de ese proyecto se eliminarán permanentemente y no podrán recuperarse.", + "button": "Eliminar mi proyecto" + }, + "toast": { + "success": "Proyecto actualizado exitosamente", + "error": "No se pudo actualizar el proyecto. Por favor intenta de nuevo." + } + }, + "members": { + "label": "Miembros", + "project_lead": "Líder del proyecto", + "default_assignee": "Asignado por defecto", + "guest_super_permissions": { + "title": "Otorgar acceso de visualización a todos los elementos de trabajo para usuarios invitados:", + "sub_heading": "Esto permitirá a los invitados tener acceso de visualización a todos los elementos de trabajo del proyecto." + }, + "invite_members": { + "title": "Invitar miembros", + "sub_heading": "Invita miembros para trabajar en tu proyecto.", + "select_co_worker": "Seleccionar compañero de trabajo" + } + }, + "states": { + "describe_this_state_for_your_members": "Describe este estado para tus miembros." + }, + "labels": { + "label_title": "Título de la etiqueta", + "label_title_is_required": "El título de la etiqueta es requerido", + "label_max_char": "El nombre de la etiqueta no debe exceder 255 caracteres", + "toast": { + "error": "Error al actualizar la etiqueta" + } + }, + "estimates": { + "title": "Habilitar estimaciones para mi proyecto", + "description": "Ayudan a comunicar la complejidad y la carga de trabajo del equipo." + }, + "automations": { + "label": "Automatizaciones", + "auto-archive": { + "title": "Auto-archivar elementos de trabajo cerrados", + "description": "Plane archivará automáticamente los elementos de trabajo que se hayan completado o cancelado.", + "duration": "Auto-archivar elementos de trabajo que están cerrados por" + }, + "auto-close": { + "title": "Auto-cerrar elementos de trabajo", + "description": "Plane cerrará automáticamente los elementos de trabajo que no se hayan completado o cancelado.", + "duration": "Auto-cerrar elementos de trabajo que están inactivos por", + "auto_close_status": "Estado de auto-cierre" + } + }, + + "empty_state": { + "labels": { + "title": "Aún no hay etiquetas", + "description": "Crea etiquetas para ayudar a organizar y filtrar elementos de trabajo en tu proyecto." + }, + "estimates": { + "title": "Aún no hay sistemas de estimación", + "description": "Crea un conjunto de estimaciones para comunicar la cantidad de trabajo por elemento de trabajo.", + "primary_button": "Agregar sistema de estimación" + } + } + }, + + "project_cycles": { + "add_cycle": "Agregar ciclo", + "more_details": "Más detalles", + "cycle": "Ciclo", + "update_cycle": "Actualizar ciclo", + "create_cycle": "Crear ciclo", + "no_matching_cycles": "No hay ciclos coincidentes", + "remove_filters_to_see_all_cycles": "Elimina los filtros para ver todos los ciclos", + "remove_search_criteria_to_see_all_cycles": "Elimina los criterios de búsqueda para ver todos los ciclos", + "only_completed_cycles_can_be_archived": "Solo los ciclos completados pueden ser archivados", + "active_cycle": { + "label": "Ciclo activo", + "progress": "Progreso", + "chart": "Gráfico de avance", + "priority_issue": "Elementos de trabajo prioritarios", + "assignees": "Asignados", + "issue_burndown": "Avance de elementos de trabajo", + "ideal": "Ideal", + "current": "Actual", + "labels": "Etiquetas" + }, + "upcoming_cycle": { + "label": "Ciclo próximo" + }, + "completed_cycle": { + "label": "Ciclo completado" + }, + "status": { + "days_left": "Días restantes", + "completed": "Completado", + "yet_to_start": "Por comenzar", + "in_progress": "En progreso", + "draft": "Borrador" + }, + "action": { + "restore": { + "title": "Restaurar ciclo", + "success": { + "title": "Ciclo restaurado", + "description": "El ciclo ha sido restaurado." + }, + "failed": { + "title": "Falló la restauración del ciclo", + "description": "No se pudo restaurar el ciclo. Por favor intenta de nuevo." + } + }, + "favorite": { + "loading": "Agregando ciclo a favoritos", + "success": { + "description": "Ciclo agregado a favoritos.", + "title": "¡Éxito!" + }, + "failed": { + "description": "No se pudo agregar el ciclo a favoritos. Por favor intenta de nuevo.", + "title": "¡Error!" + } + }, + "unfavorite": { + "loading": "Eliminando ciclo de favoritos", + "success": { + "description": "Ciclo eliminado de favoritos.", + "title": "¡Éxito!" + }, + "failed": { + "description": "No se pudo eliminar el ciclo de favoritos. Por favor intenta de nuevo.", + "title": "¡Error!" + } + }, + "update": { + "loading": "Actualizando ciclo", + "success": { + "description": "Ciclo actualizado exitosamente.", + "title": "¡Éxito!" + }, + "failed": { + "description": "Error al actualizar el ciclo. Por favor intenta de nuevo.", + "title": "¡Error!" + }, + "error": { + "already_exists": "Ya tienes un ciclo en las fechas dadas, si quieres crear un ciclo en borrador, puedes hacerlo eliminando ambas fechas." + } + } + }, + "empty_state": { + "general": { + "title": "Agrupa y delimita tu trabajo en Ciclos.", + "description": "Divide el trabajo en bloques de tiempo, trabaja hacia atrás desde la fecha límite de tu proyecto para establecer fechas, y haz un progreso tangible como equipo.", + "primary_button": { + "text": "Establece tu primer ciclo", + "comic": { + "title": "Los ciclos son bloques de tiempo repetitivos.", + "description": "Un sprint, una iteración, o cualquier otro término que uses para el seguimiento semanal o quincenal del trabajo es un ciclo." + } + } + }, + "no_issues": { + "title": "No hay elementos de trabajo agregados al ciclo", + "description": "Agrega o crea elementos de trabajo que desees delimitar y entregar dentro de este ciclo", + "primary_button": { + "text": "Crear nuevo elemento de trabajo" + }, + "secondary_button": { + "text": "Agregar elemento de trabajo existente" + } + }, + "completed_no_issues": { + "title": "No hay elementos de trabajo en el ciclo", + "description": "No hay elementos de trabajo en el ciclo. Los elementos de trabajo están transferidos u ocultos. Para ver elementos de trabajo ocultos si los hay, actualiza tus propiedades de visualización según corresponda." + }, + "active": { + "title": "No hay ciclo activo", + "description": "Un ciclo activo incluye cualquier período que abarque la fecha de hoy dentro de su rango. Encuentra el progreso y los detalles del ciclo activo aquí." + }, + "archived": { + "title": "Aún no hay ciclos archivados", + "description": "Para mantener ordenado tu proyecto, archiva los ciclos completados. Encuéntralos aquí una vez archivados." + } + } + }, + + "project_issues": { + "empty_state": { + "no_issues": { + "title": "Crea un elemento de trabajo y asígnalo a alguien, incluso a ti mismo", + "description": "Piensa en los elementos de trabajo como trabajos, tareas, trabajo o JTBD. Los cuales nos gustan. Un elemento de trabajo y sus sub-elementos de trabajo son generalmente acciones basadas en tiempo asignadas a miembros de tu equipo. Tu equipo crea, asigna y completa elementos de trabajo para mover tu proyecto hacia su objetivo.", + "primary_button": { + "text": "Crea tu primer elemento de trabajo", + "comic": { + "title": "Los elementos de trabajo son bloques de construcción en Plane.", + "description": "Rediseñar la interfaz de Plane, Cambiar la marca de la empresa o Lanzar el nuevo sistema de inyección de combustible son ejemplos de elementos de trabajo que probablemente tienen sub-elementos de trabajo." + } + } + }, + "no_archived_issues": { + "title": "Aún no hay elementos de trabajo archivados", + "description": "Manualmente o a través de automatización, puedes archivar elementos de trabajo que estén completados o cancelados. Encuéntralos aquí una vez archivados.", + "primary_button": { + "text": "Establecer automatización" + } + }, + "issues_empty_filter": { + "title": "No se encontraron elementos de trabajo que coincidan con los filtros aplicados", + "secondary_button": { + "text": "Limpiar todos los filtros" + } + } + } + }, + + "project_module": { + "add_module": "Agregar Módulo", + "update_module": "Actualizar Módulo", + "create_module": "Crear Módulo", + "archive_module": "Archivar Módulo", + "restore_module": "Restaurar Módulo", + "delete_module": "Eliminar módulo", + "empty_state": { + "general": { + "title": "Mapea los hitos de tu proyecto a Módulos y rastrea el trabajo agregado fácilmente.", + "description": "Un grupo de elementos de trabajo que pertenecen a un padre lógico y jerárquico forman un módulo. Piensa en ellos como una forma de rastrear el trabajo por hitos del proyecto. Tienen sus propios períodos y fechas límite, así como análisis para ayudarte a ver qué tan cerca o lejos estás de un hito.", + "primary_button": { + "text": "Construye tu primer módulo", + "comic": { + "title": "Los módulos ayudan a agrupar el trabajo por jerarquía.", + "description": "Un módulo de carrito, un módulo de chasis y un módulo de almacén son buenos ejemplos de esta agrupación." + } + } + }, + "no_issues": { + "title": "No hay elementos de trabajo en el módulo", + "description": "Crea o agrega elementos de trabajo que quieras lograr como parte de este módulo", + "primary_button": { + "text": "Crear nuevos elementos de trabajo" + }, + "secondary_button": { + "text": "Agregar un elemento de trabajo existente" + } + }, + "archived": { + "title": "Aún no hay Módulos archivados", + "description": "Para mantener ordenado tu proyecto, archiva los módulos completados o cancelados. Encuéntralos aquí una vez archivados." + }, + "sidebar": { + "in_active": "Este módulo aún no está activo.", + "invalid_date": "Fecha inválida. Por favor ingresa una fecha válida." + } + }, + "quick_actions": { + "archive_module": "Archivar módulo", + "archive_module_description": "Solo los módulos completados o\ncancelados pueden ser archivados.", + "delete_module": "Eliminar módulo" + }, + "toast": { + "copy": { + "success": "Enlace del módulo copiado al portapapeles" + }, + "delete": { + "success": "Módulo eliminado exitosamente", + "error": "Error al eliminar el módulo" + } + } + }, + + "project_views": { + "empty_state": { + "general": { + "title": "Guarda vistas filtradas para tu proyecto. Crea tantas como necesites", + "description": "Las vistas son un conjunto de filtros guardados que usas frecuentemente o a los que quieres tener fácil acceso. Todos tus colegas en un proyecto pueden ver las vistas de todos y elegir la que mejor se adapte a sus necesidades.", + "primary_button": { + "text": "Crea tu primera vista", + "comic": { + "title": "Las vistas funcionan sobre las propiedades de los Elementos de trabajo.", + "description": "Puedes crear una vista desde aquí con tantas propiedades como filtros como consideres apropiado." + } + } + }, + "filter": { + "title": "No hay vistas coincidentes", + "description": "Ninguna vista coincide con los criterios de búsqueda. \n Crea una nueva vista en su lugar." + } + } + }, + + "project_page": { + "empty_state": { + "general": { + "title": "Escribe una nota, un documento o una base de conocimiento completa. Obtén ayuda de Galileo, el asistente de IA de Plane, para comenzar", + "description": "Las páginas son espacios para pensamientos en Plane. Toma notas de reuniones, fórmalas fácilmente, integra elementos de trabajo, organízalas usando una biblioteca de componentes y mantenlas todas en el contexto de tu proyecto. Para hacer cualquier documento rápidamente, invoca a Galileo, la IA de Plane, con un atajo o haciendo clic en un botón.", + "primary_button": { + "text": "Crea tu primera página" + } + }, + "private": { + "title": "Aún no hay páginas privadas", + "description": "Mantén tus pensamientos privados aquí. Cuando estés listo para compartir, el equipo está a solo un clic de distancia.", + "primary_button": { + "text": "Crea tu primera página" + } + }, + "public": { + "title": "Aún no hay páginas públicas", + "description": "Ve las páginas compartidas con todos en tu proyecto aquí mismo.", + "primary_button": { + "text": "Crea tu primera página" + } + }, + "archived": { + "title": "Aún no hay páginas archivadas", + "description": "Archiva las páginas que no estén en tu radar. Accede a ellas aquí cuando las necesites." + } + } + }, + + "command_k": { + "empty_state": { + "search": { + "title": "No se encontraron resultados" + } + } + }, + + "issue_relation": { + "empty_state": { + "search": { + "title": "No se encontraron elementos de trabajo coincidentes" + }, + "no_issues": { + "title": "No se encontraron elementos de trabajo" + } + } + }, + + "issue_comment": { + "empty_state": { + "general": { + "title": "Aún no hay comentarios", + "description": "Los comentarios pueden usarse como un espacio de discusión y seguimiento para los elementos de trabajo" + } + } + }, + + "notification": { + "label": "Bandeja de entrada", + "page_label": "{workspace} - Bandeja de entrada", + "options": { + "mark_all_as_read": "Marcar todo como leído", + "mark_read": "Marcar como leído", + "mark_unread": "Marcar como no leído", + "refresh": "Actualizar", + "filters": "Filtros de bandeja de entrada", + "show_unread": "Mostrar no leídos", + "show_snoozed": "Mostrar pospuestos", + "show_archived": "Mostrar archivados", + "mark_archive": "Archivar", + "mark_unarchive": "Desarchivar", + "mark_snooze": "Posponer", + "mark_unsnooze": "Quitar posposición" + }, + "toasts": { + "read": "Notificación marcada como leída", + "unread": "Notificación marcada como no leída", + "archived": "Notificación marcada como archivada", + "unarchived": "Notificación marcada como no archivada", + "snoozed": "Notificación pospuesta", + "unsnoozed": "Notificación posposición cancelada" + }, + "empty_state": { + "detail": { + "title": "Selecciona para ver detalles." + }, + "all": { + "title": "No hay elementos de trabajo asignados", + "description": "Las actualizaciones de elementos de trabajo asignados a ti se pueden \n ver aquí" + }, + "mentions": { + "title": "No hay elementos de trabajo asignados", + "description": "Las actualizaciones de elementos de trabajo asignados a ti se pueden \n ver aquí" + } + }, + "tabs": { + "all": "Todo", + "mentions": "Menciones" + }, + "filter": { + "assigned": "Asignado a mí", + "created": "Creado por mí", + "subscribed": "Suscrito por mí" + }, + "snooze": { + "1_day": "1 día", + "3_days": "3 días", + "5_days": "5 días", + "1_week": "1 semana", + "2_weeks": "2 semanas", + "custom": "Personalizado" + } + }, + + "active_cycle": { + "empty_state": { + "progress": { + "title": "Agrega elementos de trabajo al ciclo para ver su progreso" + }, + "chart": { + "title": "Agrega elementos de trabajo al ciclo para ver el gráfico de avance." + }, + "priority_issue": { + "title": "Observa los elementos de trabajo de alta prioridad abordados en el ciclo de un vistazo." + }, + "assignee": { + "title": "Agrega asignados a los elementos de trabajo para ver un desglose del trabajo por asignados." + }, + "label": { + "title": "Agrega etiquetas a los elementos de trabajo para ver el desglose del trabajo por etiquetas." + } + } + }, + + "disabled_project": { + "empty_state": { + "inbox": { + "title": "Intake no está habilitado para el proyecto.", + "description": "Intake te ayuda a gestionar las solicitudes entrantes a tu proyecto y agregarlas como elementos de trabajo en tu flujo de trabajo. Habilita Intake desde la configuración del proyecto para gestionar las solicitudes.", + "primary_button": { + "text": "Gestionar funciones" + } + }, + "cycle": { + "title": "Los Ciclos no están habilitados para este proyecto.", + "description": "Divide el trabajo en fragmentos limitados por tiempo, trabaja hacia atrás desde la fecha límite de tu proyecto para establecer fechas y haz un progreso tangible como equipo. Habilita la función de ciclos para tu proyecto para comenzar a usarlos.", + "primary_button": { + "text": "Gestionar funciones" + } + }, + "module": { + "title": "Los Módulos no están habilitados para el proyecto.", + "description": "Los Módulos son los componentes básicos de tu proyecto. Habilita los módulos desde la configuración del proyecto para comenzar a usarlos.", + "primary_button": { + "text": "Gestionar funciones" + } + }, + "page": { + "title": "Las Páginas no están habilitadas para el proyecto.", + "description": "Las Páginas son los componentes básicos de tu proyecto. Habilita las páginas desde la configuración del proyecto para comenzar a usarlas.", + "primary_button": { + "text": "Gestionar funciones" + } + }, + "view": { + "title": "Las Vistas no están habilitadas para el proyecto.", + "description": "Las Vistas son los componentes básicos de tu proyecto. Habilita las vistas desde la configuración del proyecto para comenzar a usarlas.", + "primary_button": { + "text": "Gestionar funciones" + } + } + } + }, + "workspace_draft_issues": { + "draft_an_issue": "Borrador de elemento de trabajo", + "empty_state": { + "title": "Los elementos de trabajo a medio escribir y pronto los comentarios aparecerán aquí.", + "description": "Para probar esto, comienza a agregar un elemento de trabajo y déjalo a medias o crea tu primer borrador a continuación. 😉", + "primary_button": { + "text": "Crea tu primer borrador" + } + }, + "delete_modal": { + "title": "Eliminar borrador", + "description": "¿Estás seguro de que quieres eliminar este borrador? Esto no se puede deshacer." + }, + "toasts": { + "created": { + "success": "Borrador creado", + "error": "No se pudo crear el elemento de trabajo. Por favor, inténtalo de nuevo." + }, + "deleted": { + "success": "Borrador eliminado" + } + } + }, + + "stickies": { + "title": "Tus notas adhesivas", + "placeholder": "haz clic para escribir aquí", + "all": "Todas las notas adhesivas", + "no-data": "Anota una idea, captura un momento eureka o registra una inspiración. Agrega una nota adhesiva para comenzar.", + "add": "Agregar nota adhesiva", + "search_placeholder": "Buscar por título", + "delete": "Eliminar nota adhesiva", + "delete_confirmation": "¿Estás seguro de que quieres eliminar esta nota adhesiva?", + "empty_state": { + "simple": "Anota una idea, captura un momento eureka o registra una inspiración. Agrega una nota adhesiva para comenzar.", + "general": { + "title": "Las notas adhesivas son notas rápidas y tareas pendientes que anotas al vuelo.", + "description": "Captura tus pensamientos e ideas sin esfuerzo creando notas adhesivas a las que puedes acceder en cualquier momento y desde cualquier lugar.", + "primary_button": { + "text": "Agregar nota adhesiva" + } + }, + "search": { + "title": "Eso no coincide con ninguna de tus notas adhesivas.", + "description": "Prueba un término diferente o háznoslo saber\nsi estás seguro de que tu búsqueda es correcta.", + "primary_button": { + "text": "Agregar nota adhesiva" + } + } + }, + "toasts": { + "errors": { + "wrong_name": "El nombre de la nota adhesiva no puede tener más de 100 caracteres.", + "already_exists": "Ya existe una nota adhesiva sin descripción" + }, + "created": { + "title": "Nota adhesiva creada", + "message": "La nota adhesiva se ha creado exitosamente" + }, + "not_created": { + "title": "Nota adhesiva no creada", + "message": "No se pudo crear la nota adhesiva" + }, + "updated": { + "title": "Nota adhesiva actualizada", + "message": "La nota adhesiva se ha actualizado exitosamente" + }, + "not_updated": { + "title": "Nota adhesiva no actualizada", + "message": "No se pudo actualizar la nota adhesiva" + }, + "removed": { + "title": "Nota adhesiva eliminada", + "message": "La nota adhesiva se ha eliminado exitosamente" + }, + "not_removed": { + "title": "Nota adhesiva no eliminada", + "message": "No se pudo eliminar la nota adhesiva" + } + } + }, + + "role_details": { + "guest": { + "title": "Invitado", + "description": "Los miembros externos de las organizaciones pueden ser invitados como invitados." + }, + "member": { + "title": "Miembro", + "description": "Capacidad para leer, escribir, editar y eliminar entidades dentro de proyectos, ciclos y módulos" + }, + "admin": { + "title": "Administrador", + "description": "Todos los permisos establecidos como verdaderos dentro del espacio de trabajo." + } + }, + + "user_roles": { + "product_or_project_manager": "Gerente de Producto / Proyecto", + "development_or_engineering": "Desarrollo / Ingeniería", + "founder_or_executive": "Fundador / Ejecutivo", + "freelancer_or_consultant": "Freelancer / Consultor", + "marketing_or_growth": "Marketing / Crecimiento", + "sales_or_business_development": "Ventas / Desarrollo de Negocios", + "support_or_operations": "Soporte / Operaciones", + "student_or_professor": "Estudiante / Profesor", + "human_resources": "Recursos Humanos", + "other": "Otro" + }, + + "importer": { + "github": { + "title": "GitHub", + "description": "Importa elementos de trabajo desde repositorios de GitHub y sincronízalos." + }, + "jira": { + "title": "Jira", + "description": "Importa elementos de trabajo y epics desde proyectos y epics de Jira." + } + }, + + "exporter": { + "csv": { + "title": "CSV", + "description": "Exporta elementos de trabajo a un archivo CSV.", + "short_description": "Exportar como csv" + }, + "excel": { + "title": "Excel", + "description": "Exporta elementos de trabajo a un archivo Excel.", + "short_description": "Exportar como excel" + }, + "xlsx": { + "title": "Excel", + "description": "Exporta elementos de trabajo a un archivo Excel.", + "short_description": "Exportar como excel" + }, + "json": { + "title": "JSON", + "description": "Exporta elementos de trabajo a un archivo JSON.", + "short_description": "Exportar como json" + } + }, + "default_global_view": { + "all_issues": "Todos los elementos de trabajo", + "assigned": "Asignados", + "created": "Creados", + "subscribed": "Suscritos" + }, + + "themes": { + "theme_options": { + "system_preference": { + "label": "Preferencia del sistema" + }, + "light": { + "label": "Claro" + }, + "dark": { + "label": "Oscuro" + }, + "light_contrast": { + "label": "Claro de alto contraste" + }, + "dark_contrast": { + "label": "Oscuro de alto contraste" + }, + "custom": { + "label": "Tema personalizado" + } + } + }, + "project_modules": { + "status": { + "backlog": "Pendientes", + "planned": "Planificado", + "in_progress": "En progreso", + "paused": "Pausado", + "completed": "Completado", + "cancelled": "Cancelado" + }, + "layout": { + "list": "Vista de lista", + "board": "Vista de galería", + "timeline": "Vista de línea de tiempo" + }, + "order_by": { + "name": "Nombre", + "progress": "Progreso", + "issues": "Número de elementos de trabajo", + "due_date": "Fecha de vencimiento", + "created_at": "Fecha de creación", + "manual": "Manual" + } + }, + + "cycle": { + "label": "{count, plural, one {Ciclo} other {Ciclos}}", + "no_cycle": "Sin ciclo" + }, + + "module": { + "label": "{count, plural, one {Módulo} other {Módulos}}", + "no_module": "Sin módulo" + } } diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json index 164451981f6..c67ceceaf2e 100644 --- a/packages/i18n/src/locales/fr/translations.json +++ b/packages/i18n/src/locales/fr/translations.json @@ -1,4 +1,174 @@ { + "sidebar": { + "projects": "Projets", + "pages": "Pages", + "new_work_item": "Nouvel élément de travail", + "home": "Accueil", + "your_work": "Votre travail", + "inbox": "Boîte de réception", + "workspace": "Espace de travail", + "views": "Vues", + "analytics": "Analyses", + "work_items": "Éléments de travail", + "cycles": "Cycles", + "modules": "Modules", + "intake": "Intake", + "drafts": "Brouillons", + "favorites": "Favoris", + "pro": "Pro", + "upgrade": "Mettre à niveau" + }, + + "auth": { + "common": { + "email": { + "label": "E-mail", + "placeholder": "nom@entreprise.com", + "errors": { + "required": "L'e-mail est requis", + "invalid": "L'e-mail est invalide" + } + }, + "password": { + "label": "Mot de passe", + "set_password": "Définir un mot de passe", + "placeholder": "Entrer le mot de passe", + "confirm_password": { + "label": "Confirmer le mot de passe", + "placeholder": "Confirmer le mot de passe" + }, + "current_password": { + "label": "Mot de passe actuel" + }, + "new_password": { + "label": "Nouveau mot de passe", + "placeholder": "Entrer le nouveau mot de passe" + }, + "change_password": { + "label": { + "default": "Changer le mot de passe", + "submitting": "Changement du mot de passe" + } + }, + "errors": { + "match": "Les mots de passe ne correspondent pas", + "empty": "Veuillez entrer votre mot de passe", + "length": "Le mot de passe doit contenir plus de 8 caractères", + "strength": { + "weak": "Le mot de passe est faible", + "strong": "Le mot de passe est fort" + } + }, + "submit": "Définir le mot de passe", + "toast": { + "change_password": { + "success": { + "title": "Succès !", + "message": "Mot de passe changé avec succès." + }, + "error": { + "title": "Erreur !", + "message": "Une erreur s'est produite. Veuillez réessayer." + } + } + } + }, + "unique_code": { + "label": "Code unique", + "placeholder": "obtient-définit-vole", + "paste_code": "Collez le code envoyé à votre e-mail", + "requesting_new_code": "Demande d'un nouveau code", + "sending_code": "Envoi du code" + }, + "already_have_an_account": "Vous avez déjà un compte ?", + "login": "Se connecter", + "create_account": "Créer un compte", + "new_to_plane": "Nouveau sur Plane ?", + "back_to_sign_in": "Retour à la connexion", + "resend_in": "Renvoyer dans {seconds} secondes", + "sign_in_with_unique_code": "Se connecter avec un code unique", + "forgot_password": "Mot de passe oublié ?" + }, + "sign_up": { + "header": { + "label": "Créez un compte pour commencer à gérer le travail avec votre équipe.", + "step": { + "email": { + "header": "S'inscrire", + "sub_header": "" + }, + "password": { + "header": "S'inscrire", + "sub_header": "Inscrivez-vous en utilisant une combinaison e-mail-mot de passe." + }, + "unique_code": { + "header": "S'inscrire", + "sub_header": "Inscrivez-vous en utilisant un code unique envoyé à l'adresse e-mail ci-dessus." + } + } + }, + "errors": { + "password": { + "strength": "Essayez de définir un mot de passe fort pour continuer" + } + } + }, + "sign_in": { + "header": { + "label": "Connectez-vous pour commencer à gérer le travail avec votre équipe.", + "step": { + "email": { + "header": "Se connecter ou s'inscrire", + "sub_header": "" + }, + "password": { + "header": "Se connecter ou s'inscrire", + "sub_header": "Utilisez votre combinaison e-mail-mot de passe pour vous connecter." + }, + "unique_code": { + "header": "Se connecter ou s'inscrire", + "sub_header": "Connectez-vous en utilisant un code unique envoyé à l'adresse e-mail ci-dessus." + } + } + } + }, + "forgot_password": { + "title": "Réinitialiser votre mot de passe", + "description": "Entrez l'adresse e-mail vérifiée de votre compte utilisateur et nous vous enverrons un lien de réinitialisation du mot de passe.", + "email_sent": "Nous avons envoyé le lien de réinitialisation à votre adresse e-mail", + "send_reset_link": "Envoyer le lien de réinitialisation", + "errors": { + "smtp_not_enabled": "Nous constatons que votre administrateur n'a pas activé SMTP, nous ne pourrons pas envoyer de lien de réinitialisation du mot de passe" + }, + "toast": { + "success": { + "title": "E-mail envoyé", + "message": "Vérifiez votre boîte de réception pour un lien de réinitialisation de votre mot de passe. S'il n'apparaît pas dans quelques minutes, vérifiez votre dossier spam." + }, + "error": { + "title": "Erreur !", + "message": "Une erreur s'est produite. Veuillez réessayer." + } + } + }, + "reset_password": { + "title": "Définir un nouveau mot de passe", + "description": "Sécurisez votre compte avec un mot de passe fort" + }, + "set_password": { + "title": "Sécurisez votre compte", + "description": "La définition d'un mot de passe vous permet de vous connecter en toute sécurité" + }, + "sign_out": { + "toast": { + "error": { + "title": "Erreur !", + "message": "Échec de la déconnexion. Veuillez réessayer." + } + } + } + }, + "submit": "Soumettre", "cancel": "Annuler", "loading": "Chargement", @@ -14,32 +184,34 @@ "description": "Description", "search": "Rechercher", "add_member": "Ajouter un membre", - "remove_member": "Supprimer un membre", + "adding_members": "Ajout de membres", + "remove_member": "Supprimer le membre", "add_members": "Ajouter des membres", + "adding_member": "Ajout de membres", "remove_members": "Supprimer des membres", "add": "Ajouter", + "adding": "Ajout", "remove": "Supprimer", - "add_new": "Ajouter un nouveau", + "add_new": "Ajouter nouveau", "remove_selected": "Supprimer la sélection", "first_name": "Prénom", - "last_name": "Nom de famille", - "email": "Email", - "display_name": "Pseudonyme", + "last_name": "Nom", + "email": "E-mail", + "display_name": "Nom d'affichage", "role": "Rôle", "timezone": "Fuseau horaire", "avatar": "Avatar", "cover_image": "Image de couverture", "password": "Mot de passe", - "change_cover": "Modifier la couverture", + "change_cover": "Changer la couverture", "language": "Langue", - "saving": "Enregistrement en cours...", + "saving": "Enregistrement", "save_changes": "Enregistrer les modifications", "deactivate_account": "Désactiver le compte", "deactivate_account_description": "Lors de la désactivation d'un compte, toutes les données et ressources de ce compte seront définitivement supprimées et ne pourront pas être récupérées.", "profile_settings": "Paramètres du profil", "your_account": "Votre compte", - "profile": "Profil", - "security": " Sécurité", + "security": "Sécurité", "activity": "Activité", "appearance": "Apparence", "notifications": "Notifications", @@ -49,116 +221,75 @@ "summary": "Résumé", "assigned": "Assigné", "created": "Créé", - "subscribed": "Souscrit", - "you_do_not_have_the_permission_to_access_this_page": "Vous n'avez pas les permissions d'accéder à cette page.", - "failed_to_sign_out_please_try_again": "Impossible de se déconnecter. Veuillez réessayer.", - "password_changed_successfully": "Mot de passe changé avec succès.", - "something_went_wrong_please_try_again": "Quelque chose s'est mal passé. Veuillez réessayer.", - "change_password": "Changer le mot de passe", - "changing_password": "Changement de mot de passe", - "current_password": "Mot de passe actuel", - "new_password": "Nouveau mot de passe", - "confirm_password": "Confirmer le mot de passe", - "this_field_is_required": "Ce champ est requis", - "passwords_dont_match": "Les mots de passe ne correspondent pas", - "please_enter_your_password": "Veuillez entrer votre mot de passe.", - "password_length_should_me_more_than_8_characters": "La longueur du mot de passe doit faire au moins 8 caractères.", - "password_is_weak": "Le mot de passe est faible.", - "password_is_strong": "Le mot de passe est fort.", + "subscribed": "Abonné", + "you_do_not_have_the_permission_to_access_this_page": "Vous n'avez pas la permission d'accéder à cette page.", + "something_went_wrong_please_try_again": "Une erreur s'est produite. Veuillez réessayer.", "load_more": "Charger plus", - "select_or_customize_your_interface_color_scheme": "Sélectionnez ou personnalisez votre schéma de couleurs de l'interface.", + "select_or_customize_your_interface_color_scheme": "Sélectionnez ou personnalisez votre schéma de couleurs d'interface.", "theme": "Thème", - "system_preference": "Préférence du système", + "system_preference": "Préférence système", "light": "Clair", - "dark": "Foncé", - "light_contrast": "Clair de haut contraste", - "dark_contrast": "Foncé de haut contraste", + "dark": "Sombre", + "light_contrast": "Contraste élevé clair", + "dark_contrast": "Contraste élevé sombre", "custom": "Thème personnalisé", "select_your_theme": "Sélectionnez votre thème", "customize_your_theme": "Personnalisez votre thème", "background_color": "Couleur de fond", - "text_color": "Couleur de texte", - "primary_color": "Couleur primaire (thème)", - "sidebar_background_color": "Couleur de fond du sidebar", - "sidebar_text_color": "Couleur de texte du sidebar", + "text_color": "Couleur du texte", + "primary_color": "Couleur principale (Thème)", + "sidebar_background_color": "Couleur de fond de la barre latérale", + "sidebar_text_color": "Couleur du texte de la barre latérale", "set_theme": "Définir le thème", "enter_a_valid_hex_code_of_6_characters": "Entrez un code hexadécimal valide de 6 caractères", "background_color_is_required": "La couleur de fond est requise", - "text_color_is_required": "La couleur de texte est requise", - "primary_color_is_required": "La couleur primaire est requise", - "sidebar_background_color_is_required": "La couleur de fond du sidebar est requise", - "sidebar_text_color_is_required": "La couleur de texte du sidebar est requise", + "text_color_is_required": "La couleur du texte est requise", + "primary_color_is_required": "La couleur principale est requise", + "sidebar_background_color_is_required": "La couleur de fond de la barre latérale est requise", + "sidebar_text_color_is_required": "La couleur du texte de la barre latérale est requise", "updating_theme": "Mise à jour du thème", "theme_updated_successfully": "Thème mis à jour avec succès", - "failed_to_update_the_theme": "Impossible de mettre à jour le thème", - "email_notifications": "Notifications par email", - "stay_in_the_loop_on_issues_you_are_subscribed_to_enable_this_to_get_notified": "Restez dans la boucle sur les problèmes auxquels vous êtes abonné. Activez cela pour être notifié.", - "email_notification_setting_updated_successfully": "Paramètres de notification par email mis à jour avec succès", - "failed_to_update_email_notification_setting": "Impossible de mettre à jour les paramètres de notification par email", - "notify_me_when": "Me notifier lorsque", - "property_changes": "Changements de propriété", - "property_changes_description": "Me notifier lorsque les propriétés du problème comme les assignés, la priorité, les estimations ou tout autre chose changent.", + "failed_to_update_the_theme": "Échec de la mise à jour du thème", + "email_notifications": "Notifications par e-mail", + "stay_in_the_loop_on_issues_you_are_subscribed_to_enable_this_to_get_notified": "Restez informé des éléments de travail auxquels vous êtes abonné. Activez ceci pour être notifié.", + "email_notification_setting_updated_successfully": "Paramètre de notification par e-mail mis à jour avec succès", + "failed_to_update_email_notification_setting": "Échec de la mise à jour du paramètre de notification par e-mail", + "notify_me_when": "Me notifier quand", + "property_changes": "Modifications des propriétés", + "property_changes_description": "Me notifier lorsque les propriétés des éléments de travail comme les assignés, la priorité, les estimations ou autre changent.", "state_change": "Changement d'état", - "state_change_description": "Me notifier lorsque le problème passe à un autre état", - "issue_completed": "Problème terminé", - "issue_completed_description": "Me notifier uniquement lorsqu'un problème est terminé", + "state_change_description": "Me notifier lorsque les éléments de travail passent à un état différent", + "issue_completed": "Élément de travail terminé", + "issue_completed_description": "Me notifier uniquement lorsqu'un élément de travail est terminé", "comments": "Commentaires", - "comments_description": "Me notifier lorsqu'un utilisateur commente un problème", - "mentions": "Mention", - "mentions_description": "Me notifier uniquement lorsqu'un utilisateur mentionne un problème", - "create_your_workspace": "Créer votre espace de travail", - "only_your_instance_admin_can_create_workspaces": "Seuls les administrateurs de votre instance peuvent créer des espaces de travail", - "only_your_instance_admin_can_create_workspaces_description": "Si vous connaissez l'adresse email de votre administrateur d'instance, cliquez sur le bouton ci-dessous pour les contacter.", - "go_back": "Retour", - "request_instance_admin": "Demander à l'administrateur de l'instance", - "plane_logo": "Logo de Plane", - "workspace_creation_disabled": "Création d'espace de travail désactivée", - "workspace_request_subject": "Demande de création d'un espace de travail", - "workspace_request_body": "Bonjour administrateur(s) de l'instance,\n\nVeuillez créer un nouveau espace de travail avec l'URL [/workspace-name] pour [raison de la création de l'espace de travail].\n\nMerci,\n{{firstName}} {{lastName}}\n{{email}}", - "creating_workspace": "Création de l'espace de travail", - "workspace_created_successfully": "Espace de travail créé avec succès", - "create_workspace_page": "Page de création d'espace de travail", - "workspace_could_not_be_created_please_try_again": "L'espace de travail ne peut pas être créé. Veuillez réessayer.", - "workspace_could_not_be_created_please_try_again_description": "Une erreur est survenue lors de la création de l'espace de travail. Veuillez réessayer.", - "this_is_a_required_field": "Ce champ est requis.", - "name_your_workspace": "Nommez votre espace de travail", - "workspaces_names_can_contain_only_space_dash_and_alphanumeric_characters": "Les noms des espaces de travail peuvent contenir uniquement des espaces, des tirets et des caractères alphanumériques.", - "limit_your_name_to_80_characters": "Limitez votre nom à 80 caractères.", - "set_your_workspace_url": "Définir l'URL de votre espace de travail", - "limit_your_url_to_48_characters": "Limitez votre URL à 48 caractères.", - "how_many_people_will_use_this_workspace": "Combien de personnes utiliseront cet espace de travail ?", - "how_many_people_will_use_this_workspace_description": "Cela nous aidera à déterminer le nombre de sièges que vous devez acheter.", - "select_a_range": "Sélectionner une plage", - "urls_can_contain_only_dash_and_alphanumeric_characters": "Les URLs peuvent contenir uniquement des tirets et des caractères alphanumériques.", - "something_familiar_and_recognizable_is_always_best": "Ce qui est familier et reconnaissable est toujours le meilleur.", - "workspace_url_is_already_taken": "L'URL de l'espace de travail est déjà utilisée !", - "old_password": "Mot de passe actuel", + "comments_description": "Me notifier lorsque quelqu'un laisse un commentaire sur l'élément de travail", + "mentions": "Mentions", + "mentions_description": "Me notifier uniquement lorsque quelqu'un me mentionne dans les commentaires ou la description", + "old_password": "Ancien mot de passe", "general_settings": "Paramètres généraux", - "sign_out": "Déconnexion", - "signing_out": "Déconnexion en cours", + "sign_out": "Se déconnecter", + "signing_out": "Déconnexion", "active_cycles": "Cycles actifs", - "active_cycles_description": "Surveillez les cycles dans les projets, suivez les issues de haute priorité et zoomez sur les cycles qui nécessitent attention.", - "on_demand_snapshots_of_all_your_cycles": "Captures instantanées sur demande de tous vos cycles", + "active_cycles_description": "Surveillez les cycles à travers les projets, suivez les éléments de travail prioritaires et zoomez sur les cycles qui nécessitent de l'attention.", + "on_demand_snapshots_of_all_your_cycles": "Instantanés à la demande de tous vos cycles", "upgrade": "Mettre à niveau", - "10000_feet_view": "Vue d'ensemble de tous les cycles actifs", - "10000_feet_view_description": "Prenez du recul pour voir les cycles en cours dans tous vos projets en même temps au lieu de passer d'un cycle à l'autre dans chaque projet.", - "get_snapshot_of_each_active_cycle": "Obtenez un aperçu de chaque cycle actif", - "get_snapshot_of_each_active_cycle_description": "Suivez les métriques de haut niveau pour tous les cycles actifs, observez leur état d'avancement et évaluez leur portée par rapport aux échéances.", - "compare_burndowns": "Comparez les graphiques d'avancement", - "compare_burndowns_description": "Surveillez les performances de chacune de vos équipes en consultant le rapport d'avancement de chaque cycle.", - "quickly_see_make_or_break_issues": "Identifiez rapidement les problèmes critiques", - "quickly_see_make_or_break_issues_description": "Visualisez les problèmes hautement prioritaires de chaque cycle par rapport aux dates d'échéance. Consultez-les tous par cycle en un seul clic.", - "zoom_into_cycles_that_need_attention": "Concentrez-vous sur les cycles nécessitant attention", - "zoom_into_cycles_that_need_attention_description": "Examinez en un clic l'état de tout cycle qui ne répond pas aux attentes.", - "stay_ahead_of_blockers": "Anticipez les blocages", - "stay_ahead_of_blockers_description": "Repérez les défis d'un projet à l'autre et identifiez les dépendances entre cycles qui ne sont pas évidentes depuis d'autres vues.", - "analytics": "Analyse", - "workspace_invites": "Invitations de l'espace de travail", - "workspace_settings": "Paramètres de l'espace de travail", + "10000_feet_view": "Vue à 10 000 pieds de tous les cycles actifs.", + "10000_feet_view_description": "Dézoomez pour voir les cycles en cours dans tous vos projets en même temps au lieu de passer d'un cycle à l'autre dans chaque projet.", + "get_snapshot_of_each_active_cycle": "Obtenez un aperçu de chaque cycle actif.", + "get_snapshot_of_each_active_cycle_description": "Suivez les métriques de haut niveau pour tous les cycles actifs, voyez leur état d'avancement et obtenez une idée de la portée par rapport aux échéances.", + "compare_burndowns": "Comparez les burndowns.", + "compare_burndowns_description": "Surveillez les performances de chacune de vos équipes en jetant un coup d'œil au rapport burndown de chaque cycle.", + "quickly_see_make_or_break_issues": "Voyez rapidement les éléments de travail critiques.", + "quickly_see_make_or_break_issues_description": "Prévisualisez les éléments de travail hautement prioritaires pour chaque cycle par rapport aux dates d'échéance. Voyez-les tous par cycle en un clic.", + "zoom_into_cycles_that_need_attention": "Zoomez sur les cycles qui nécessitent de l'attention.", + "zoom_into_cycles_that_need_attention_description": "Examinez l'état de tout cycle qui ne correspond pas aux attentes en un clic.", + "stay_ahead_of_blockers": "Anticipez les blocages.", + "stay_ahead_of_blockers_description": "Repérez les défis d'un projet à l'autre et voyez les dépendances inter-cycles qui ne sont pas évidentes depuis une autre vue.", + "analytics": "Analyses", + "workspace_invites": "Invitations à l'espace de travail", "enter_god_mode": "Entrer en mode dieu", "workspace_logo": "Logo de l'espace de travail", - "new_issue": "Nouveau problème", - "home": "Accueil", + "new_issue": "Nouvel élément de travail", "your_work": "Votre travail", "drafts": "Brouillons", "projects": "Projets", @@ -166,44 +297,49 @@ "workspace": "Espace de travail", "archives": "Archives", "settings": "Paramètres", - "failed_to_move_favorite": "Impossible de déplacer le favori", + "failed_to_move_favorite": "Échec du déplacement du favori", "favorites": "Favoris", - "no_favorites_yet": "Aucun favori pour le moment", + "no_favorites_yet": "Pas encore de favoris", "create_folder": "Créer un dossier", "new_folder": "Nouveau dossier", "favorite_updated_successfully": "Favori mis à jour avec succès", "favorite_created_successfully": "Favori créé avec succès", "folder_already_exists": "Le dossier existe déjà", "folder_name_cannot_be_empty": "Le nom du dossier ne peut pas être vide", - "something_went_wrong": "Quelque chose s'est mal passé", - "failed_to_reorder_favorite": "Impossible de réordonner le favori", + "something_went_wrong": "Une erreur s'est produite", + "failed_to_reorder_favorite": "Échec de la réorganisation du favori", "favorite_removed_successfully": "Favori supprimé avec succès", - "failed_to_create_favorite": "Impossible de créer le favori", - "failed_to_rename_favorite": "Impossible de renommer le favori", + "failed_to_create_favorite": "Échec de la création du favori", + "failed_to_rename_favorite": "Échec du renommage du favori", "project_link_copied_to_clipboard": "Lien du projet copié dans le presse-papiers", "link_copied": "Lien copié", "add_project": "Ajouter un projet", "create_project": "Créer un projet", "failed_to_remove_project_from_favorites": "Impossible de supprimer le projet des favoris. Veuillez réessayer.", "project_created_successfully": "Projet créé avec succès", - "project_created_successfully_description": "Projet créé avec succès. Vous pouvez maintenant ajouter des issues à ce projet.", + "project_created_successfully_description": "Projet créé avec succès. Vous pouvez maintenant commencer à ajouter des éléments de travail.", "project_cover_image_alt": "Image de couverture du projet", "name_is_required": "Le nom est requis", - "title_should_be_less_than_255_characters": "Le titre ne doit pas dépasser 255 caractères", + "title_should_be_less_than_255_characters": "Le titre doit faire moins de 255 caractères", "project_name": "Nom du projet", - "project_id_must_be_at_least_1_character": "L'ID du projet doit être au moins de 1 caractère", - "project_id_must_be_at_most_5_characters": "L'ID du projet doit être au plus de 5 caractères", + "project_id_must_be_at_least_1_character": "L'ID du projet doit comporter au moins 1 caractère", + "project_id_must_be_at_most_5_characters": "L'ID du projet doit comporter au plus 5 caractères", "project_id": "ID du projet", - "project_id_tooltip_content": "Aide à identifier les issues du projet de manière unique. Max 5 caractères.", - "description_placeholder": "Description...", + "project_id_tooltip_content": "Vous aide à identifier uniquement les éléments de travail dans le projet. Maximum 5 caractères.", + "description_placeholder": "Description", "only_alphanumeric_non_latin_characters_allowed": "Seuls les caractères alphanumériques et non latins sont autorisés.", "project_id_is_required": "L'ID du projet est requis", + "project_id_allowed_char": "Seuls les caractères alphanumériques et non latins sont autorisés.", + "project_id_min_char": "L'ID du projet doit comporter au moins 1 caractère", + "project_id_max_char": "L'ID du projet doit comporter au plus 5 caractères", + "project_description_placeholder": "Entrez la description du projet", "select_network": "Sélectionner le réseau", "lead": "Responsable", + "date_range": "Plage de dates", "private": "Privé", "public": "Public", - "accessible_only_by_invite": "Accessible uniquement par invitation", - "anyone_in_the_workspace_except_guests_can_join": "Tout le monde dans l'espace de travail, sauf les invités, peut rejoindre", + "accessible_only_by_invite": "Accessible uniquement sur invitation", + "anyone_in_the_workspace_except_guests_can_join": "Tout le monde dans l'espace de travail sauf les invités peut rejoindre", "creating": "Création", "creating_project": "Création du projet", "adding_project_to_favorites": "Ajout du projet aux favoris", @@ -218,40 +354,39 @@ "publish": "Publier", "copy_link": "Copier le lien", "leave_project": "Quitter le projet", - "join_the_project_to_rearrange": "Rejoindre le projet pour réorganiser", + "join_the_project_to_rearrange": "Rejoignez le projet pour réorganiser", "drag_to_rearrange": "Glisser pour réorganiser", "congrats": "Félicitations !", - "project": "Projet", "open_project": "Ouvrir le projet", - "issues": "Problèmes", + "issues": "Éléments de travail", "cycles": "Cycles", "modules": "Modules", "pages": "Pages", "intake": "Intake", "time_tracking": "Suivi du temps", "work_management": "Gestion du travail", - "projects_and_issues": "Projets et problèmes", - "projects_and_issues_description": "Activer ou désactiver ces fonctionnalités pour ce projet.", - "cycles_description": "Organisez votre travail en périodes définies selon vos besoins par projet et modifiez la fréquence d'une période à l'autre.", - "modules_description": "Regroupez le travail en sous-projets avec leurs propres responsables et assignés.", - "views_description": "Enregistrez vos tris, filtres et options d'affichage pour plus tard ou partagez-les.", - "pages_description": "Rédigez tout type de contenu librement.", - "intake_description": "Restez informé des tickets auxquels vous êtes abonné. Activez cette option pour recevoir des notifications.", - "time_tracking_description": "Suivez le temps passé sur les tickets et les projets.", - "work_management_description": "Gérez votre travail et vos projets en toute simplicité.", + "projects_and_issues": "Projets et éléments de travail", + "projects_and_issues_description": "Activez ou désactivez ces éléments pour ce projet.", + "cycles_description": "Planifiez le travail comme vous le souhaitez par projet et changez la fréquence d'une période à l'autre.", + "modules_description": "Regroupez le travail en configurations de type sous-projet avec leurs propres responsables et assignés.", + "views_description": "Enregistrez les tris, filtres et options d'affichage pour plus tard ou partagez-les.", + "pages_description": "Écrivez n'importe quoi comme vous écrivez n'importe quoi.", + "intake_description": "Restez informé des éléments de travail auxquels vous êtes abonné. Activez ceci pour être notifié.", + "time_tracking_description": "Suivez le temps passé sur les éléments de travail et les projets.", + "work_management_description": "Gérez votre travail et vos projets facilement.", "documentation": "Documentation", "message_support": "Contacter le support", "contact_sales": "Contacter les ventes", - "hyper_mode": "Mode hyper", + "hyper_mode": "Mode Hyper", "keyboard_shortcuts": "Raccourcis clavier", - "whats_new": "Nouveautés ?", + "whats_new": "Quoi de neuf ?", "version": "Version", "we_are_having_trouble_fetching_the_updates": "Nous avons des difficultés à récupérer les mises à jour.", - "our_changelogs": "Notre journal de modifications", - "for_the_latest_updates": "Pour les dernières mises à jour.", + "our_changelogs": "nos journaux des modifications", + "for_the_latest_updates": "pour les dernières mises à jour.", "please_visit": "Veuillez visiter", "docs": "Documentation", - "full_changelog": "Journal complet", + "full_changelog": "Journal des modifications complet", "support": "Support", "discord": "Discord", "powered_by_plane_pages": "Propulsé par Plane Pages", @@ -262,45 +397,46 @@ "we_see_that_someone_has_invited_you_to_join_a_workspace_description": "Nous voyons que quelqu'un vous a invité à rejoindre un espace de travail", "join_a_workspace_description": "Rejoindre un espace de travail", "accept_and_join": "Accepter et rejoindre", - "go_home": "Retour à l'accueil", + "go_home": "Aller à l'accueil", "no_pending_invites": "Aucune invitation en attente", - "you_can_see_here_if_someone_invites_you_to_a_workspace": "Vous pouvez voir ici si quelqu'un vous invite à rejoindre un espace de travail", + "you_can_see_here_if_someone_invites_you_to_a_workspace": "Vous pouvez voir ici si quelqu'un vous invite à un espace de travail", "back_to_home": "Retour à l'accueil", - "workspace_name": "espace-de-travail", + "workspace_name": "nom-espace-de-travail", "deactivate_your_account": "Désactiver votre compte", - "deactivate_your_account_description": "Une fois désactivé, vous ne pourrez pas être assigné à des problèmes et être facturé pour votre espace de travail. Pour réactiver votre compte, vous aurez besoin d'une invitation à un espace de travail à cette adresse email.", + "deactivate_your_account_description": "Une fois désactivé, vous ne pourrez plus être assigné à des éléments de travail ni être facturé pour votre espace de travail. Pour réactiver votre compte, vous aurez besoin d'une invitation à un espace de travail avec cette adresse e-mail.", "deactivating": "Désactivation", "confirm": "Confirmer", + "confirming": "Confirmation", "draft_created": "Brouillon créé", - "issue_created_successfully": "Problème créé avec succès", - "draft_creation_failed": "Création du brouillon échouée", - "issue_creation_failed": "Création du problème échouée", - "draft_issue": "Problème en brouillon", - "issue_updated_successfully": "Problème mis à jour avec succès", - "issue_could_not_be_updated": "Le problème n'a pas pu être mis à jour", + "issue_created_successfully": "Élément de travail créé avec succès", + "draft_creation_failed": "Échec de la création du brouillon", + "issue_creation_failed": "Échec de la création de l'élément de travail", + "draft_issue": "Élément de travail en brouillon", + "issue_updated_successfully": "Élément de travail mis à jour avec succès", + "issue_could_not_be_updated": "L'élément de travail n'a pas pu être mis à jour", "create_a_draft": "Créer un brouillon", "save_to_drafts": "Enregistrer dans les brouillons", "save": "Enregistrer", "update": "Mettre à jour", "updating": "Mise à jour", - "create_new_issue": "Créer un nouveau problème", + "create_new_issue": "Créer un nouvel élément de travail", "editor_is_not_ready_to_discard_changes": "L'éditeur n'est pas prêt à annuler les modifications", - "failed_to_move_issue_to_project": "Impossible de déplacer le problème vers le projet", + "failed_to_move_issue_to_project": "Échec du déplacement de l'élément de travail vers le projet", "create_more": "Créer plus", "add_to_project": "Ajouter au projet", "discard": "Annuler", - "duplicate_issue_found": "Problème en double trouvé", - "duplicate_issues_found": "Problèmes en double trouvés", + "duplicate_issue_found": "Élément de travail en double trouvé", + "duplicate_issues_found": "Éléments de travail en double trouvés", "no_matching_results": "Aucun résultat correspondant", "title_is_required": "Le titre est requis", "title": "Titre", "state": "État", "priority": "Priorité", - "none": "Aucune", + "none": "Aucun", "urgent": "Urgent", - "high": "Haute", - "medium": "Moyenne", - "low": "Basse", + "high": "Élevé", + "medium": "Moyen", + "low": "Faible", "members": "Membres", "assignee": "Assigné", "assignees": "Assignés", @@ -308,12 +444,1876 @@ "labels": "Étiquettes", "create_new_label": "Créer une nouvelle étiquette", "start_date": "Date de début", + "end_date": "Date de fin", "due_date": "Date d'échéance", - "cycle": "Cycle", "estimate": "Estimation", - "change_parent_issue": "Modifier le problème parent", - "remove_parent_issue": "Supprimer le problème parent", + "change_parent_issue": "Changer l'élément de travail parent", + "remove_parent_issue": "Supprimer l'élément de travail parent", "add_parent": "Ajouter un parent", - "loading_members": "Chargement des membres...", - "inbox": "Boîte de réception" + "loading_members": "Chargement des membres", + "view_link_copied_to_clipboard": "Lien de la vue copié dans le presse-papiers.", + "required": "Requis", + "optional": "Optionnel", + "Cancel": "Annuler", + "edit": "Modifier", + "archive": "Archiver", + "restor": "Restaurer", + "open_in_new_tab": "Ouvrir dans un nouvel onglet", + "delete": "Supprimer", + "deleting": "Suppression", + "make_a_copy": "Faire une copie", + "move_to_project": "Déplacer vers le projet", + "good": "Bonjour", + "morning": "matin", + "afternoon": "après-midi", + "evening": "soir", + "show_all": "Tout afficher", + "show_less": "Afficher moins", + "no_data_yet": "Pas encore de données", + "syncing": "Synchronisation", + "add_work_item": "Ajouter un élément de travail", + "advanced_description_placeholder": "Appuyez sur '/' pour les commandes", + "create_work_item": "Créer un élément de travail", + "attachments": "Pièces jointes", + "declining": "Refus", + "declined": "Refusé", + "decline": "Refuser", + "unassigned": "Non assigné", + "work_items": "Éléments de travail", + "add_link": "Ajouter un lien", + "points": "Points", + "no_assignee": "Pas d'assigné", + "no_assignees_yet": "Pas encore d'assignés", + "no_labels_yet": "Pas encore d'étiquettes", + "ideal": "Idéal", + "current": "Actuel", + "no_matching_members": "Aucun membre correspondant", + "leaving": "Départ", + "removing": "Suppression", + "leave": "Quitter", + "refresh": "Actualiser", + "refreshing": "Actualisation", + "refresh_status": "Actualiser l'état", + "prev": "Précédent", + "next": "Suivant", + "re_generating": "Régénération", + "re_generate": "Régénérer", + "re_generate_key": "Régénérer la clé", + "export": "Exporter", + "member": "{count, plural, one{# membre} other{# membres}}", + + "project_view": { + "sort_by": { + "created_at": "Créé le", + "updated_at": "Mis à jour le", + "name": "Nom" + } + }, + + "toast": { + "success": "Succès !", + "error": "Erreur !" + }, + + "links": { + "toasts": { + "created": { + "title": "Lien créé", + "message": "Le lien a été créé avec succès" + }, + "not_created": { + "title": "Lien non créé", + "message": "Le lien n'a pas pu être créé" + }, + "updated": { + "title": "Lien mis à jour", + "message": "Le lien a été mis à jour avec succès" + }, + "not_updated": { + "title": "Lien non mis à jour", + "message": "Le lien n'a pas pu être mis à jour" + }, + "removed": { + "title": "Lien supprimé", + "message": "Le lien a été supprimé avec succès" + }, + "not_removed": { + "title": "Lien non supprimé", + "message": "Le lien n'a pas pu être supprimé" + } + } + }, + + "home": { + "empty": { + "create_project": { + "title": "Créer un projet", + "description": "La plupart des choses commencent par un projet dans Plane.", + "cta": "Commencer" + }, + "invite_team": { + "title": "Inviter votre équipe", + "description": "Construisez, déployez et gérez avec vos collègues.", + "cta": "Les faire entrer" + }, + "configure_workspace": { + "title": "Configurez votre espace de travail.", + "description": "Activez ou désactivez des fonctionnalités ou allez plus loin.", + "cta": "Configurer cet espace de travail" + }, + "personalize_account": { + "title": "Faites de Plane le vôtre.", + "description": "Choisissez votre photo, vos couleurs et plus encore.", + "cta": "Personnaliser maintenant" + }, + "widgets": { + "title": "C'est calme sans widgets, activez-les", + "description": "Il semble que tous vos widgets soient désactivés. Activez-les\nmaintenant pour améliorer votre expérience !", + "primary_button": { + "text": "Gérer les widgets" + } + } + }, + "quick_links": { + "empty": "Enregistrez des liens vers des éléments de travail que vous souhaitez avoir à portée de main.", + "add": "Ajouter un lien rapide", + "title": "Lien rapide", + "title_plural": "Liens rapides" + }, + "recents": { + "title": "Récents", + "empty": { + "project": "Vos projets récents apparaîtront ici une fois que vous en aurez visité un.", + "page": "Vos pages récentes apparaîtront ici une fois que vous en aurez visité une.", + "issue": "Vos éléments de travail récents apparaîtront ici une fois que vous en aurez visité un.", + "default": "Vous n'avez pas encore d'éléments récents." + }, + "filters": { + "all": "Tous les éléments", + "projects": "Projets", + "pages": "Pages", + "issues": "Éléments de travail" + } + }, + "new_at_plane": { + "title": "Nouveau sur Plane" + }, + "quick_tutorial": { + "title": "Tutoriel rapide" + }, + "widget": { + "reordered_successfully": "Widget réorganisé avec succès.", + "reordering_failed": "Une erreur s'est produite lors de la réorganisation du widget." + }, + "manage_widgets": "Gérer les widgets", + "title": "Accueil", + "star_us_on_github": "Donnez-nous une étoile sur GitHub" + }, + + "link": { + "modal": { + "url": { + "text": "URL", + "required": "L'URL n'est pas valide", + "placeholder": "Tapez ou collez une URL" + }, + "title": { + "text": "Titre d'affichage", + "placeholder": "Comment souhaitez-vous voir ce lien" + } + } + }, + + "common": { + "all": "Tout", + "states": "États", + "state": "État", + "state_groups": "Groupes d'états", + "priority": "Priorité", + "team_project": "Projet d'équipe", + "project": "Projet", + "cycle": "Cycle", + "cycles": "Cycles", + "module": "Module", + "modules": "Modules", + "labels": "Étiquettes", + "assignees": "Assignés", + "assignee": "Assigné", + "created_by": "Créé par", + "none": "Aucun", + "link": "Lien", + "estimate": "Estimation", + "layout": "Disposition", + "filters": "Filtres", + "display": "Affichage", + "load_more": "Charger plus", + "activity": "Activité", + "analytics": "Analyses", + "dates": "Dates", + "success": "Succès !", + "something_went_wrong": "Quelque chose s'est mal passé", + "error": { + "label": "Erreur !", + "message": "Une erreur s'est produite. Veuillez réessayer." + }, + "group_by": "Grouper par", + "epic": "Epic", + "epics": "Epics", + "work_item": "Élément de travail", + "sub_work_item": "Sous-élément de travail", + "add": "Ajouter", + "warning": "Avertissement", + "updating": "Mise à jour", + "adding": "Ajout", + "update": "Mettre à jour", + "creating": "Création", + "create": "Créer", + "cancel": "Annuler", + "description": "Description", + "title": "Titre", + "attachment": "Pièce jointe", + "general": "Général", + "features": "Fonctionnalités", + "automation": "Automatisation", + "project_name": "Nom du projet", + "project_id": "ID du projet", + "project_timezone": "Fuseau horaire du projet", + "created_on": "Créé le", + "update_project": "Mettre à jour le projet", + "identifier_already_exists": "L'identifiant existe déjà", + "add_more": "Ajouter plus", + "defaults": "Par défaut", + "add_label": "Ajouter une étiquette", + "estimates": "Estimations", + "customize_time_range": "Personnaliser la plage de temps", + "loading": "Chargement", + "attachments": "Pièces jointes", + "properties": "Propriétés", + "parent": "Parent", + "remove": "Supprimer", + "archiving": "Archivage", + "archive": "Archiver", + "access": { + "public": "Public", + "private": "Privé" + }, + "done": "Terminé", + "sub_work_items": "Sous-éléments de travail", + "comment": "Commentaire", + "workspace_level": "Niveau espace de travail", + "order_by": { + "label": "Trier par", + "manual": "Manuel", + "last_created": "Dernier créé", + "last_updated": "Dernière mise à jour", + "start_date": "Date de début", + "due_date": "Date d'échéance", + "asc": "Croissant", + "desc": "Décroissant", + "updated_on": "Mis à jour le" + }, + "sort": { + "asc": "Croissant", + "desc": "Décroissant", + "created_on": "Créé le", + "updated_on": "Mis à jour le" + }, + "comments": "Commentaires", + "updates": "Mises à jour", + "clear_all": "Tout effacer", + "copied": "Copié !", + "link_copied": "Lien copié !", + "link_copied_to_clipboard": "Lien copié dans le presse-papiers", + "copied_to_clipboard": "Lien de l'élément de travail copié dans le presse-papiers", + "is_copied_to_clipboard": "L'élément de travail est copié dans le presse-papiers", + "no_links_added_yet": "Aucun lien ajouté pour l'instant", + "add_link": "Ajouter un lien", + "links": "Liens", + "go_to_workspace": "Aller à l'espace de travail", + "progress": "Progression", + "optional": "Optionnel", + "join": "Rejoindre", + "go_back": "Retour", + "continue": "Continuer", + "resend": "Renvoyer", + "relations": "Relations", + "errors": { + "default": { + "title": "Erreur !", + "message": "Quelque chose s'est mal passé. Veuillez réessayer." + }, + "required": "Ce champ est obligatoire", + "entity_required": "{entity} est requis" + }, + "update_link": "Mettre à jour le lien", + "attach": "Joindre", + "create_new": "Créer nouveau", + "add_existing": "Ajouter existant", + "type_or_paste_a_url": "Tapez ou collez une URL", + "url_is_invalid": "L'URL n'est pas valide", + "display_title": "Titre d'affichage", + "link_title_placeholder": "Comment souhaitez-vous voir ce lien", + "url": "URL", + "side_peek": "Aperçu latéral", + "modal": "Modal", + "full_screen": "Plein écran", + "close_peek_view": "Fermer l'aperçu", + "toggle_peek_view_layout": "Basculer la disposition de l'aperçu", + "options": "Options", + "duration": "Durée", + "today": "Aujourd'hui", + "week": "Semaine", + "month": "Mois", + "quarter": "Trimestre", + "press_for_commands": "Appuyez sur '/' pour les commandes", + "click_to_add_description": "Cliquez pour ajouter une description", + "search": { + "label": "Rechercher", + "placeholder": "Tapez pour rechercher", + "no_matches_found": "Aucune correspondance trouvée", + "no_matching_results": "Aucun résultat correspondant" + }, + "actions": { + "edit": "Modifier", + "make_a_copy": "Faire une copie", + "open_in_new_tab": "Ouvrir dans un nouvel onglet", + "copy_link": "Copier le lien", + "archive": "Archiver", + "delete": "Supprimer", + "remove_relation": "Supprimer la relation", + "subscribe": "S'abonner", + "unsubscribe": "Se désabonner", + "clear_sorting": "Effacer le tri", + "show_weekends": "Afficher les week-ends", + "enable": "Activer", + "disable": "Désactiver" + }, + "name": "Nom", + "discard": "Abandonner", + "confirm": "Confirmer", + "confirming": "Confirmation", + "read_the_docs": "Lire la documentation", + "default": "Par défaut", + "active": "Actif", + "enabled": "Activé", + "disabled": "Désactivé", + "mandate": "Mandat", + "mandatory": "Obligatoire", + "yes": "Oui", + "no": "Non", + "please_wait": "Veuillez patienter", + "enabling": "Activation", + "disabling": "Désactivation", + "beta": "Bêta", + "or": "ou", + "next": "Suivant", + "back": "Retour", + "cancelling": "Annulation", + "configuring": "Configuration", + "clear": "Effacer", + "import": "Importer", + "connect": "Connecter", + "authorizing": "Autorisation", + "processing": "Traitement", + "no_data_available": "Aucune donnée disponible", + "from": "de {name}", + "authenticated": "Authentifié", + "select": "Sélectionner", + "upgrade": "Mettre à niveau", + "add_seats": "Ajouter des sièges", + "label": "Étiquette", + "priorities": "Priorités", + "projects": "Projets", + "workspace": "Espace de travail", + "workspaces": "Espaces de travail", + "team": "Équipe", + "teams": "Équipes", + "entity": "Entité", + "entities": "Entités", + "task": "Tâche", + "tasks": "Tâches", + "section": "Section", + "sections": "Sections", + "edit": "Modifier", + "connecting": "Connexion", + "connected": "Connecté", + "disconnect": "Déconnecter", + "disconnecting": "Déconnexion", + "installing": "Installation", + "install": "Installer" + }, + + "form": { + "title": { + "required": "Le titre est requis", + "max_length": "Le titre doit contenir moins de {length} caractères" + } + }, + + "entity": { + "grouping_title": "Regroupement {entity}", + "priority": "Priorité {entity}", + "all": "Tous les {entity}", + "drop_here_to_move": "Déposez ici pour déplacer le {entity}", + "delete": { + "label": "Supprimer {entity}", + "success": "{entity} supprimé avec succès", + "failed": "Échec de la suppression de {entity}" + }, + "update": { + "failed": "Échec de la mise à jour de {entity}", + "success": "{entity} mis à jour avec succès" + }, + "link_copied_to_clipboard": "Lien {entity} copié dans le presse-papiers", + "fetch": { + "failed": "Erreur lors de la récupération de {entity}" + }, + "add": { + "success": "{entity} ajouté avec succès", + "failed": "Erreur lors de l'ajout de {entity}" + } + }, + + "epic": { + "all": "Tous les Epics", + "label": "{count, plural, one {Epic} other {Epics}}", + "new": "Nouvel Epic", + "adding": "Ajout d'un epic", + "create": { + "success": "Epic créé avec succès" + }, + "add": { + "press_enter": "Appuyez sur 'Entrée' pour ajouter un autre epic", + "label": "Ajouter un Epic" + }, + "title": { + "label": "Titre de l'Epic", + "required": "Le titre de l'Epic est requis." + } + }, + + "issue": { + "label": "{count, plural, one {Élément de travail} other {Éléments de travail}}", + "all": "Tous les éléments de travail", + "edit": "Modifier l'élément de travail", + "title": { + "label": "Titre de l'élément de travail", + "required": "Le titre de l'élément de travail est requis." + }, + "add": { + "press_enter": "Appuyez sur 'Entrée' pour ajouter un autre élément de travail", + "label": "Ajouter un élément de travail", + "cycle": { + "failed": "L'élément de travail n'a pas pu être ajouté au cycle. Veuillez réessayer.", + "success": "{count, plural, one {Élément de travail} other {Éléments de travail}} ajouté(s) au cycle avec succès.", + "loading": "Ajout de {count, plural, one {l'élément de travail} other {éléments de travail}} au cycle" + }, + "assignee": "Ajouter des assignés", + "start_date": "Ajouter une date de début", + "due_date": "Ajouter une date d'échéance", + "parent": "Ajouter un élément de travail parent", + "sub_issue": "Ajouter un sous-élément de travail", + "relation": "Ajouter une relation", + "link": "Ajouter un lien", + "existing": "Ajouter un élément de travail existant" + }, + "remove": { + "label": "Supprimer l'élément de travail", + "cycle": { + "loading": "Suppression de l'élément de travail du cycle", + "success": "Élément de travail supprimé du cycle avec succès.", + "failed": "L'élément de travail n'a pas pu être supprimé du cycle. Veuillez réessayer." + }, + "module": { + "loading": "Suppression de l'élément de travail du module", + "success": "Élément de travail supprimé du module avec succès.", + "failed": "L'élément de travail n'a pas pu être supprimé du module. Veuillez réessayer." + }, + "parent": { + "label": "Supprimer l'élément de travail parent" + } + }, + "new": "Nouvel élément de travail", + "adding": "Ajout d'un élément de travail", + "create": { + "success": "Élément de travail créé avec succès" + }, + "priority": { + "urgent": "Urgent", + "high": "Haute", + "medium": "Moyenne", + "low": "Basse" + }, + "display": { + "properties": { + "label": "Propriétés d'affichage", + "id": "ID", + "issue_type": "Type d'élément de travail", + "sub_issue_count": "Nombre de sous-éléments", + "attachment_count": "Nombre de pièces jointes", + "created_on": "Créé le", + "sub_issue": "Sous-élément de travail" + }, + "extra": { + "show_sub_issues": "Afficher les sous-éléments", + "show_empty_groups": "Afficher les groupes vides" + } + }, + "layouts": { + "ordered_by_label": "Cette disposition est triée par", + "list": "Liste", + "kanban": "Tableau", + "calendar": "Calendrier", + "spreadsheet": "Tableau", + "gantt": "Chronologie", + "title": { + "list": "Disposition en liste", + "kanban": "Disposition en tableau", + "calendar": "Disposition en calendrier", + "spreadsheet": "Disposition en tableau", + "gantt": "Disposition en chronologie" + } + }, + "states": { + "active": "Actif", + "backlog": "Backlog" + }, + "comments": { + "placeholder": "Ajouter un commentaire", + "switch": { + "private": "Passer en commentaire privé", + "public": "Passer en commentaire public" + }, + "create": { + "success": "Commentaire créé avec succès", + "error": "Échec de la création du commentaire. Veuillez réessayer plus tard." + }, + "update": { + "success": "Commentaire mis à jour avec succès", + "error": "Échec de la mise à jour du commentaire. Veuillez réessayer plus tard." + }, + "remove": { + "success": "Commentaire supprimé avec succès", + "error": "Échec de la suppression du commentaire. Veuillez réessayer plus tard." + }, + "upload": { + "error": "Échec du téléchargement du fichier. Veuillez réessayer plus tard." + } + }, + "empty_state": { + "issue_detail": { + "title": "L'élément de travail n'existe pas", + "description": "L'élément de travail que vous recherchez n'existe pas, a été archivé ou a été supprimé.", + "primary_button": { + "text": "Voir les autres éléments de travail" + } + } + }, + "sibling": { + "label": "Éléments de travail frères" + }, + "archive": { + "description": "Seuls les éléments de travail\nterminés ou annulés peuvent être archivés", + "label": "Archiver l'élément de travail", + "confirm_message": "Êtes-vous sûr de vouloir archiver l'élément de travail ? Tous vos éléments archivés peuvent être restaurés ultérieurement.", + "success": { + "label": "Archivage réussi", + "message": "Vos archives se trouvent dans les archives du projet." + }, + "failed": { + "message": "L'élément de travail n'a pas pu être archivé. Veuillez réessayer." + } + }, + "restore": { + "success": { + "title": "Restauration réussie", + "message": "Votre élément de travail se trouve dans les éléments de travail du projet." + }, + "failed": { + "message": "L'élément de travail n'a pas pu être restauré. Veuillez réessayer." + } + }, + "relation": { + "relates_to": "En relation avec", + "duplicate": "Doublon de", + "blocked_by": "Bloqué par", + "blocking": "Bloque" + }, + "copy_link": "Copier le lien de l'élément de travail", + "delete": { + "label": "Supprimer l'élément de travail", + "error": "Erreur lors de la suppression de l'élément de travail" + }, + "subscription": { + "actions": { + "subscribed": "Abonnement à l'élément de travail réussi", + "unsubscribed": "Désabonnement de l'élément de travail réussi" + } + }, + "select": { + "error": "Veuillez sélectionner au moins un élément de travail", + "empty": "Aucun élément de travail sélectionné", + "add_selected": "Ajouter les éléments de travail sélectionnés" + }, + "open_in_full_screen": "Ouvrir l'élément de travail en plein écran" + }, + + "attachment": { + "error": "Le fichier n'a pas pu être joint. Essayez de le télécharger à nouveau.", + "only_one_file_allowed": "Un seul fichier peut être téléchargé à la fois.", + "file_size_limit": "Le fichier doit faire {size}MB ou moins.", + "drag_and_drop": "Glissez-déposez n'importe où pour télécharger", + "delete": "Supprimer la pièce jointe" + }, + + "label": { + "select": "Sélectionner une étiquette", + "create": { + "success": "Étiquette créée avec succès", + "failed": "Échec de la création de l'étiquette", + "already_exists": "L'étiquette existe déjà", + "type": "Tapez pour ajouter une nouvelle étiquette" + } + }, + + "sub_work_item": { + "update": { + "success": "Sous-élément de travail mis à jour avec succès", + "error": "Erreur lors de la mise à jour du sous-élément de travail" + }, + "remove": { + "success": "Sous-élément de travail supprimé avec succès", + "error": "Erreur lors de la suppression du sous-élément de travail" + } + }, + + "view": { + "label": "{count, plural, one {Vue} other {Vues}}", + "create": { + "label": "Créer une vue" + }, + "update": { + "label": "Mettre à jour la vue" + } + }, + + "inbox_issue": { + "status": { + "pending": { + "title": "En attente", + "description": "En attente" + }, + "declined": { + "title": "Refusé", + "description": "Refusé" + }, + "snoozed": { + "title": "Reporté", + "description": "{days, plural, one{# jour} other{# jours}} restant(s)" + }, + "accepted": { + "title": "Accepté", + "description": "Accepté" + }, + "duplicate": { + "title": "Doublon", + "description": "Doublon" + } + }, + "modals": { + "decline": { + "title": "Refuser l'élément de travail", + "content": "Êtes-vous sûr de vouloir refuser l'élément de travail {value} ?" + }, + "delete": { + "title": "Supprimer l'élément de travail", + "content": "Êtes-vous sûr de vouloir supprimer l'élément de travail {value} ?", + "success": "Élément de travail supprimé avec succès" + } + }, + "errors": { + "snooze_permission": "Seuls les administrateurs du projet peuvent reporter/annuler le report des éléments de travail", + "accept_permission": "Seuls les administrateurs du projet peuvent accepter les éléments de travail", + "decline_permission": "Seuls les administrateurs du projet peuvent refuser les éléments de travail" + }, + "actions": { + "accept": "Accepter", + "decline": "Refuser", + "snooze": "Reporter", + "unsnooze": "Annuler le report", + "copy": "Copier le lien de l'élément de travail", + "delete": "Supprimer", + "open": "Ouvrir l'élément de travail", + "mark_as_duplicate": "Marquer comme doublon", + "move": "Déplacer {value} vers les éléments de travail du projet" + }, + "source": { + "in-app": "in-app" + }, + "order_by": { + "created_at": "Créé le", + "updated_at": "Mis à jour le", + "id": "ID" + }, + "label": "Intake", + "page_label": "{workspace} - Intake", + "modal": { + "title": "Créer un élément de travail Intake" + }, + "tabs": { + "open": "Ouvert", + "closed": "Fermé" + }, + "empty_state": { + "sidebar_open_tab": { + "title": "Aucun élément de travail ouvert", + "description": "Trouvez les éléments de travail ouverts ici. Créez un nouvel élément de travail." + }, + "sidebar_closed_tab": { + "title": "Aucun élément de travail fermé", + "description": "Tous les éléments de travail, qu'ils soient acceptés ou refusés, peuvent être trouvés ici." + }, + "sidebar_filter": { + "title": "Aucun élément de travail correspondant", + "description": "Aucun élément de travail ne correspond au filtre appliqué dans Intake. Créez un nouvel élément de travail." + }, + "detail": { + "title": "Sélectionnez un élément de travail pour voir ses détails." + } + } + }, + + "workspace_creation": { + "heading": "Créez votre espace de travail", + "subheading": "Pour commencer à utiliser Plane, vous devez créer ou rejoindre un espace de travail.", + "form": { + "name": { + "label": "Nommez votre espace de travail", + "placeholder": "Quelque chose de familier et reconnaissable est toujours préférable." + }, + "url": { + "label": "Définissez l'URL de votre espace de travail", + "placeholder": "Tapez ou collez une URL", + "edit_slug": "Vous ne pouvez modifier que le slug de l'URL" + }, + "organization_size": { + "label": "Combien de personnes utiliseront cet espace de travail ?", + "placeholder": "Sélectionnez une plage" + } + }, + "errors": { + "creation_disabled": { + "title": "Seul l'administrateur de votre instance peut créer des espaces de travail", + "description": "Si vous connaissez l'adresse e-mail de votre administrateur d'instance, cliquez sur le bouton ci-dessous pour le contacter.", + "request_button": "Contacter l'administrateur d'instance" + }, + "validation": { + "name_alphanumeric": "Les noms d'espaces de travail ne peuvent contenir que (' '), ('-'), ('_') et des caractères alphanumériques.", + "name_length": "Limitez votre nom à 80 caractères.", + "url_alphanumeric": "Les URL ne peuvent contenir que ('-') et des caractères alphanumériques.", + "url_length": "Limitez votre URL à 48 caractères.", + "url_already_taken": "L'URL de l'espace de travail est déjà prise !" + } + }, + "request_email": { + "subject": "Demande d'un nouvel espace de travail", + "body": "Bonjour administrateur(s) d'instance,\n\nVeuillez créer un nouvel espace de travail avec l'URL [/workspace-name] pour [objectif de création de l'espace de travail].\n\nMerci,\n{firstName} {lastName}\n{email}" + }, + "button": { + "default": "Créer l'espace de travail", + "loading": "Création de l'espace de travail" + }, + "toast": { + "success": { + "title": "Succès", + "message": "Espace de travail créé avec succès" + }, + "error": { + "title": "Erreur", + "message": "L'espace de travail n'a pas pu être créé. Veuillez réessayer." + } + } + }, + + "workspace_dashboard": { + "empty_state": { + "general": { + "title": "Aperçu de vos projets, activités et métriques", + "description": "Bienvenue sur Plane, nous sommes ravis de vous avoir parmi nous. Créez votre premier projet et suivez vos éléments de travail, et cette page se transformera en un espace qui vous aide à progresser. Les administrateurs verront également les éléments qui aident leur équipe à progresser.", + "primary_button": { + "text": "Construisez votre premier projet", + "comic": { + "title": "Tout commence par un projet dans Plane", + "description": "Un projet peut être la feuille de route d'un produit, une campagne marketing ou le lancement d'une nouvelle voiture." + } + } + } + } + }, + + "workspace_analytics": { + "label": "Analytique", + "page_label": "{workspace} - Analytique", + "open_tasks": "Total des tâches ouvertes", + "error": "Une erreur s'est produite lors de la récupération des données.", + "work_items_closed_in": "Éléments de travail fermés dans", + "selected_projects": "Projets sélectionnés", + "total_members": "Total des membres", + "total_cycles": "Total des Cycles", + "total_modules": "Total des Modules", + "pending_work_items": { + "title": "Éléments de travail en attente", + "empty_state": "L'analyse des éléments de travail en attente par collègues apparaît ici." + }, + "work_items_closed_in_a_year": { + "title": "Éléments de travail fermés dans l'année", + "empty_state": "Fermez des éléments de travail pour voir leur analyse sous forme de graphique." + }, + "most_work_items_created": { + "title": "Plus d'éléments de travail créés", + "empty_state": "Les collègues et le nombre d'éléments de travail créés par eux apparaissent ici." + }, + "most_work_items_closed": { + "title": "Plus d'éléments de travail fermés", + "empty_state": "Les collègues et le nombre d'éléments de travail fermés par eux apparaissent ici." + }, + "tabs": { + "scope_and_demand": "Portée et Demande", + "custom": "Analytique Personnalisée" + }, + "empty_state": { + "general": { + "title": "Suivez les progrès, les charges de travail et les allocations. Repérez les tendances, supprimez les blocages et accélérez le travail", + "description": "Visualisez la portée par rapport à la demande, les estimations et l'augmentation de la portée. Obtenez les performances par membres de l'équipe et équipes, et assurez-vous que votre projet se déroule dans les délais.", + "primary_button": { + "text": "Commencez votre premier projet", + "comic": { + "title": "L'analytique fonctionne mieux avec les Cycles + Modules", + "description": "D'abord, planifiez vos éléments de travail dans des Cycles et, si possible, regroupez les éléments de travail qui s'étendent sur plus d'un cycle dans des Modules. Consultez les deux dans la navigation de gauche." + } + } + } + } + }, + + "workspace_projects": { + "label": "{count, plural, one {Projet} other {Projets}}", + "create": { + "label": "Ajouter un Projet" + }, + "network": { + "private": { + "title": "Privé", + "description": "Accessible uniquement sur invitation" + }, + "public": { + "title": "Public", + "description": "Accessible à tous dans l'espace de travail sauf les invités" + } + }, + "error": { + "permission": "Vous n'avez pas la permission d'effectuer cette action.", + "cycle_delete": "Échec de la suppression du cycle", + "module_delete": "Échec de la suppression du module", + "issue_delete": "Échec de la suppression de l'élément de travail" + }, + "state": { + "backlog": "Backlog", + "unstarted": "Non commencé", + "started": "Commencé", + "completed": "Terminé", + "cancelled": "Annulé" + }, + "sort": { + "manual": "Manuel", + "name": "Nom", + "created_at": "Date de création", + "members_length": "Nombre de membres" + }, + "scope": { + "my_projects": "Mes projets", + "archived_projects": "Archivés" + }, + "common": { + "months_count": "{months, plural, one{# mois} other{# mois}}" + }, + "empty_state": { + "general": { + "title": "Aucun projet actif", + "description": "Considérez chaque projet comme le parent d'un travail orienté objectif. Les projets sont l'endroit où vivent les Tâches, les Cycles et les Modules et, avec vos collègues, vous aident à atteindre cet objectif. Créez un nouveau projet ou filtrez les projets archivés.", + "primary_button": { + "text": "Commencez votre premier projet", + "comic": { + "title": "Tout commence par un projet dans Plane", + "description": "Un projet peut être la feuille de route d'un produit, une campagne marketing ou le lancement d'une nouvelle voiture." + } + } + }, + "no_projects": { + "title": "Aucun projet", + "description": "Pour créer des éléments de travail ou gérer votre travail, vous devez créer un projet ou faire partie d'un projet.", + "primary_button": { + "text": "Commencez votre premier projet", + "comic": { + "title": "Tout commence par un projet dans Plane", + "description": "Un projet peut être la feuille de route d'un produit, une campagne marketing ou le lancement d'une nouvelle voiture." + } + } + }, + "filter": { + "title": "Aucun projet correspondant", + "description": "Aucun projet détecté avec les critères correspondants. \n Créez plutôt un nouveau projet." + }, + "search": { + "description": "Aucun projet détecté avec les critères correspondants.\nCréez plutôt un nouveau projet" + } + } + }, + + "workspace_views": { + "add_view": "Ajouter une vue", + "empty_state": { + "all-issues": { + "title": "Aucun élément de travail dans le projet", + "description": "Premier projet terminé ! Maintenant, découpez votre travail en morceaux traçables avec des éléments de travail. Allons-y !", + "primary_button": { + "text": "Créer un nouvel élément de travail" + } + }, + "assigned": { + "title": "Aucun élément de travail pour le moment", + "description": "Les éléments de travail qui vous sont assignés peuvent être suivis ici.", + "primary_button": { + "text": "Créer un nouvel élément de travail" + } + }, + "created": { + "title": "Aucun élément de travail pour le moment", + "description": "Tous les éléments de travail que vous créez arrivent ici, suivez-les directement ici.", + "primary_button": { + "text": "Créer un nouvel élément de travail" + } + }, + "subscribed": { + "title": "Aucun élément de travail pour le moment", + "description": "Abonnez-vous aux éléments de travail qui vous intéressent, suivez-les tous ici." + }, + "custom-view": { + "title": "Aucun élément de travail pour le moment", + "description": "Les éléments de travail qui correspondent aux filtres, suivez-les tous ici." + } + } + }, + + "workspace_settings": { + "label": "Paramètres de l'espace de travail", + "page_label": "{workspace} - Paramètres généraux", + "key_created": "Clé créée", + "copy_key": "Copiez et sauvegardez cette clé secrète dans Plane Pages. Vous ne pourrez plus voir cette clé après avoir cliqué sur Fermer. Un fichier CSV contenant la clé a été téléchargé.", + "token_copied": "Jeton copié dans le presse-papiers.", + "settings": { + "general": { + "title": "Général", + "upload_logo": "Télécharger le logo", + "edit_logo": "Modifier le logo", + "name": "Nom de l'espace de travail", + "company_size": "Taille de l'entreprise", + "url": "URL de l'espace de travail", + "update_workspace": "Mettre à jour l'espace de travail", + "delete_workspace": "Supprimer l'espace de travail", + "delete_workspace_description": "Lors de la suppression d'un espace de travail, toutes les données et ressources au sein de cet espace de travail seront définitivement supprimées et ne pourront pas être récupérées.", + "delete_btn": "Supprimer mon espace de travail", + "errors": { + "name": { + "required": "Le nom est requis", + "max_length": "Le nom de l'espace de travail ne doit pas dépasser 80 caractères" + }, + "company_size": { + "required": "La taille de l'entreprise est requise" + } + } + }, + "members": { + "title": "Membres", + "add_member": "Ajouter un membre", + "invitations_sent_successfully": "Invitations envoyées avec succès", + "leave_confirmation": "Êtes-vous sûr de vouloir quitter l'espace de travail ? Vous n'aurez plus accès à cet espace de travail. Cette action ne peut pas être annulée.", + "details": { + "full_name": "Nom complet", + "display_name": "Nom d'affichage", + "email_address": "Adresse e-mail", + "account_type": "Type de compte", + "authentication": "Authentification", + "joining_date": "Date d'adhésion" + }, + "modal": { + "title": "Inviter des personnes à collaborer", + "description": "Invitez des personnes à collaborer sur votre espace de travail.", + "button": "Envoyer les invitations", + "button_loading": "Envoi des invitations", + "placeholder": "nom@entreprise.com", + "errors": { + "required": "Nous avons besoin d'une adresse e-mail pour les inviter.", + "invalid": "L'e-mail est invalide" + } + } + }, + "billing_and_plans": { + "title": "Facturation & Plans", + "current_plan": "Plan actuel", + "free_plan": "Vous utilisez actuellement le plan gratuit", + "view_plans": "Voir les plans" + }, + "exports": { + "title": "Exportations", + "exporting": "Exportation", + "previous_exports": "Exportations précédentes", + "export_separate_files": "Exporter les données dans des fichiers séparés", + "modal": { + "title": "Exporter vers", + "toasts": { + "success": { + "title": "Exportation réussie", + "message": "Vous pourrez télécharger les {entity} exportés depuis l'exportation précédente." + }, + "error": { + "title": "Échec de l'exportation", + "message": "L'exportation a échoué. Veuillez réessayer." + } + } + } + }, + "webhooks": { + "title": "Webhooks", + "add_webhook": "Ajouter un webhook", + "modal": { + "title": "Créer un webhook", + "details": "Détails du webhook", + "payload": "URL de la charge utile", + "question": "Quels événements souhaitez-vous déclencher avec ce webhook ?", + "error": "L'URL est requise" + }, + "secret_key": { + "title": "Clé secrète", + "message": "Générer un jeton pour signer la charge utile du webhook" + }, + "options": { + "all": "Envoyez-moi tout", + "individual": "Sélectionner des événements individuels" + }, + "toasts": { + "created": { + "title": "Webhook créé", + "message": "Le webhook a été créé avec succès" + }, + "not_created": { + "title": "Webhook non créé", + "message": "Le webhook n'a pas pu être créé" + }, + "updated": { + "title": "Webhook mis à jour", + "message": "Le webhook a été mis à jour avec succès" + }, + "not_updated": { + "title": "Webhook non mis à jour", + "message": "Le webhook n'a pas pu être mis à jour" + }, + "removed": { + "title": "Webhook supprimé", + "message": "Le webhook a été supprimé avec succès" + }, + "not_removed": { + "title": "Webhook non supprimé", + "message": "Le webhook n'a pas pu être supprimé" + }, + "secret_key_copied": { + "message": "Clé secrète copiée dans le presse-papiers." + }, + "secret_key_not_copied": { + "message": "Une erreur s'est produite lors de la copie de la clé secrète." + } + } + }, + "api_tokens": { + "title": "Jetons API", + "add_token": "Ajouter un jeton API", + "create_token": "Créer un jeton", + "never_expires": "N'expire jamais", + "generate_token": "Générer un jeton", + "generating": "Génération", + "delete": { + "title": "Supprimer le jeton API", + "description": "Toute application utilisant ce jeton n'aura plus accès aux données de Plane. Cette action ne peut pas être annulée.", + "success": { + "title": "Succès !", + "message": "Le jeton API a été supprimé avec succès" + }, + "error": { + "title": "Erreur !", + "message": "Le jeton API n'a pas pu être supprimé" + } + } + } + }, + "empty_state": { + "api_tokens": { + "title": "Aucun jeton API créé", + "description": "Les API Plane peuvent être utilisées pour intégrer vos données dans Plane avec n'importe quel système externe. Créez un jeton pour commencer." + }, + "webhooks": { + "title": "Aucun webhook ajouté", + "description": "Créez des webhooks pour recevoir des mises à jour en temps réel et automatiser des actions." + }, + "exports": { + "title": "Aucune exportation pour le moment", + "description": "Chaque fois que vous exportez, vous aurez également une copie ici pour référence." + }, + "imports": { + "title": "Aucune importation pour le moment", + "description": "Trouvez toutes vos importations précédentes ici et téléchargez-les." + } + } + }, + + "profile": { + "label": "Profil", + "page_label": "Votre travail", + "work": "Travail", + "details": { + "joined_on": "Inscrit le", + "time_zone": "Fuseau horaire" + }, + "stats": { + "workload": "Charge de travail", + "overview": "Vue d'ensemble", + "created": "Éléments de travail créés", + "assigned": "Éléments de travail assignés", + "subscribed": "Éléments de travail suivis", + "state_distribution": { + "title": "Éléments de travail par état", + "empty": "Créez des éléments de travail pour les visualiser par état dans le graphique pour une meilleure analyse." + }, + "priority_distribution": { + "title": "Éléments de travail par priorité", + "empty": "Créez des éléments de travail pour les visualiser par priorité dans le graphique pour une meilleure analyse." + }, + "recent_activity": { + "title": "Activité récente", + "empty": "Nous n'avons pas trouvé de données. Veuillez consulter vos contributions", + "button": "Télécharger l'activité du jour", + "button_loading": "Téléchargement" + } + }, + "actions": { + "profile": "Profil", + "security": "Sécurité", + "activity": "Activité", + "appearance": "Apparence", + "notifications": "Notifications" + }, + "tabs": { + "summary": "Résumé", + "assigned": "Assigné", + "created": "Créé", + "subscribed": "Suivi", + "activity": "Activité" + }, + "empty_state": { + "activity": { + "title": "Aucune activité pour le moment", + "description": "Commencez par créer un nouvel élément de travail ! Ajoutez-y des détails et des propriétés. Explorez davantage Plane pour voir votre activité." + }, + "assigned": { + "title": "Aucun élément de travail ne vous est assigné", + "description": "Les éléments de travail qui vous sont assignés peuvent être suivis ici." + }, + "created": { + "title": "Aucun élément de travail pour le moment", + "description": "Tous les éléments de travail que vous créez apparaissent ici, suivez-les directement ici." + }, + "subscribed": { + "title": "Aucun élément de travail pour le moment", + "description": "Abonnez-vous aux éléments de travail qui vous intéressent, suivez-les tous ici." + } + } + }, + + "project_settings": { + "general": { + "enter_project_id": "Saisissez l'ID du projet", + "please_select_a_timezone": "Veuillez sélectionner un fuseau horaire", + "archive_project": { + "title": "Archiver le projet", + "description": "L'archivage d'un projet le retirera de votre navigation latérale, bien que vous pourrez toujours y accéder depuis votre page de projets. Vous pouvez restaurer le projet ou le supprimer quand vous le souhaitez.", + "button": "Archiver le projet" + }, + "delete_project": { + "title": "Supprimer le projet", + "description": "Lors de la suppression d'un projet, toutes les données et ressources de ce projet seront définitivement supprimées et ne pourront pas être récupérées.", + "button": "Supprimer mon projet" + }, + "toast": { + "success": "Projet mis à jour avec succès", + "error": "Le projet n'a pas pu être mis à jour. Veuillez réessayer." + } + }, + "members": { + "label": "Membres", + "project_lead": "Chef de projet", + "default_assignee": "Assigné par défaut", + "guest_super_permissions": { + "title": "Accorder l'accès en lecture à tous les éléments de travail pour les utilisateurs invités :", + "sub_heading": "Cela permettra aux invités d'avoir un accès en lecture à tous les éléments de travail du projet." + }, + "invite_members": { + "title": "Inviter des membres", + "sub_heading": "Invitez des membres à travailler sur votre projet.", + "select_co_worker": "Sélectionner un collaborateur" + } + }, + "states": { + "describe_this_state_for_your_members": "Décrivez cet état pour vos membres." + }, + "labels": { + "label_title": "Titre de l'étiquette", + "label_title_is_required": "Le titre de l'étiquette est requis", + "label_max_char": "Le nom de l'étiquette ne doit pas dépasser 255 caractères", + "toast": { + "error": "Erreur lors de la mise à jour de l'étiquette" + } + }, + "estimates": { + "title": "Activer les estimations pour mon projet", + "description": "Elles vous aident à communiquer la complexité et la charge de travail de l'équipe." + }, + "automations": { + "label": "Automatisations", + "auto-archive": { + "title": "Archiver automatiquement les éléments de travail fermés", + "description": "Plane archivera automatiquement les éléments de travail qui ont été terminés ou annulés.", + "duration": "Archiver automatiquement les éléments de travail qui sont fermés depuis" + }, + "auto-close": { + "title": "Fermer automatiquement les éléments de travail", + "description": "Plane fermera automatiquement les éléments de travail qui n'ont pas été terminés ou annulés.", + "duration": "Fermer automatiquement les éléments de travail inactifs depuis", + "auto_close_status": "Statut de fermeture automatique" + } + }, + + "empty_state": { + "labels": { + "title": "Aucune étiquette pour le moment", + "description": "Créez des étiquettes pour aider à organiser et filtrer les éléments de travail dans votre projet." + }, + "estimates": { + "title": "Aucun système d'estimation pour le moment", + "description": "Créez un ensemble d'estimations pour communiquer la quantité de travail par élément de travail.", + "primary_button": "Ajouter un système d'estimation" + } + } + }, + + "project_cycles": { + "add_cycle": "Ajouter un cycle", + "more_details": "Plus de détails", + "cycle": "Cycle", + "update_cycle": "Mettre à jour le cycle", + "create_cycle": "Créer un cycle", + "no_matching_cycles": "Aucun cycle correspondant", + "remove_filters_to_see_all_cycles": "Supprimez les filtres pour voir tous les cycles", + "remove_search_criteria_to_see_all_cycles": "Supprimez les critères de recherche pour voir tous les cycles", + "only_completed_cycles_can_be_archived": "Seuls les cycles terminés peuvent être archivés", + "active_cycle": { + "label": "Cycle actif", + "progress": "Progression", + "chart": "Graphique d'avancement", + "priority_issue": "Éléments de travail prioritaires", + "assignees": "Assignés", + "issue_burndown": "Graphique d'avancement des éléments", + "ideal": "Idéal", + "current": "Actuel", + "labels": "Étiquettes" + }, + "upcoming_cycle": { + "label": "Cycle à venir" + }, + "completed_cycle": { + "label": "Cycle terminé" + }, + "status": { + "days_left": "Jours restants", + "completed": "Terminé", + "yet_to_start": "Pas encore commencé", + "in_progress": "En cours", + "draft": "Brouillon" + }, + "action": { + "restore": { + "title": "Restaurer le cycle", + "success": { + "title": "Cycle restauré", + "description": "Le cycle a été restauré." + }, + "failed": { + "title": "Échec de la restauration du cycle", + "description": "Le cycle n'a pas pu être restauré. Veuillez réessayer." + } + }, + "favorite": { + "loading": "Ajout du cycle aux favoris", + "success": { + "description": "Cycle ajouté aux favoris.", + "title": "Succès !" + }, + "failed": { + "description": "Impossible d'ajouter le cycle aux favoris. Veuillez réessayer.", + "title": "Erreur !" + } + }, + "unfavorite": { + "loading": "Suppression du cycle des favoris", + "success": { + "description": "Cycle retiré des favoris.", + "title": "Succès !" + }, + "failed": { + "description": "Impossible de retirer le cycle des favoris. Veuillez réessayer.", + "title": "Erreur !" + } + }, + "update": { + "loading": "Mise à jour du cycle", + "success": { + "description": "Cycle mis à jour avec succès.", + "title": "Succès !" + }, + "failed": { + "description": "Erreur lors de la mise à jour du cycle. Veuillez réessayer.", + "title": "Erreur !" + }, + "error": { + "already_exists": "Vous avez déjà un cycle aux dates indiquées. Si vous souhaitez créer un cycle en brouillon, vous pouvez le faire en supprimant les deux dates." + } + } + }, + "empty_state": { + "general": { + "title": "Regroupez et planifiez votre travail en Cycles.", + "description": "Découpez le travail en périodes définies, planifiez à rebours depuis la date limite de votre projet pour fixer les dates, et progressez concrètement en équipe.", + "primary_button": { + "text": "Définissez votre premier cycle", + "comic": { + "title": "Les cycles sont des périodes répétitives.", + "description": "Un sprint, une itération, ou tout autre terme que vous utilisez pour le suivi hebdomadaire ou bimensuel du travail est un cycle." + } + } + }, + "no_issues": { + "title": "Aucun élément de travail ajouté au cycle", + "description": "Ajoutez ou créez des éléments de travail que vous souhaitez planifier et livrer dans ce cycle", + "primary_button": { + "text": "Créer un nouvel élément de travail" + }, + "secondary_button": { + "text": "Ajouter un élément existant" + } + }, + "completed_no_issues": { + "title": "Aucun élément de travail dans le cycle", + "description": "Aucun élément de travail dans le cycle. Les éléments sont soit transférés soit masqués. Pour voir les éléments masqués s'il y en a, mettez à jour vos propriétés d'affichage en conséquence." + }, + "active": { + "title": "Aucun cycle actif", + "description": "Un cycle actif inclut toute période qui englobe la date d'aujourd'hui dans sa plage. Trouvez ici la progression et les détails du cycle actif." + }, + "archived": { + "title": "Aucun cycle archivé pour le moment", + "description": "Pour organiser votre projet, archivez les cycles terminés. Retrouvez-les ici une fois archivés." + } + } + }, + + "project_issues": { + "empty_state": { + "no_issues": { + "title": "Créez un élément de travail et assignez-le à quelqu'un, même à vous-même", + "description": "Pensez aux éléments de travail comme des tâches, du travail, ou des JTBD (Jobs To Be Done). Un élément de travail et ses sous-éléments sont généralement des actions temporelles assignées aux membres de votre équipe. Votre équipe crée, assigne et complète des éléments de travail pour faire progresser votre projet vers son objectif.", + "primary_button": { + "text": "Créez votre premier élément de travail", + "comic": { + "title": "Les éléments de travail sont les blocs de construction dans Plane.", + "description": "Refondre l'interface de Plane, Renouveler l'image de marque de l'entreprise, ou Lancer le nouveau système d'injection de carburant sont des exemples d'éléments de travail qui ont probablement des sous-éléments." + } + } + }, + "no_archived_issues": { + "title": "Aucun élément de travail archivé pour le moment", + "description": "Manuellement ou par automatisation, vous pouvez archiver les éléments de travail terminés ou annulés. Retrouvez-les ici une fois archivés.", + "primary_button": { + "text": "Configurer l'automatisation" + } + }, + "issues_empty_filter": { + "title": "Aucun élément de travail trouvé correspondant aux filtres appliqués", + "secondary_button": { + "text": "Effacer tous les filtres" + } + } + } + }, + + "project_module": { + "add_module": "Ajouter un module", + "update_module": "Mettre à jour le module", + "create_module": "Créer un module", + "archive_module": "Archiver le module", + "restore_module": "Restaurer le module", + "delete_module": "Supprimer le module", + "empty_state": { + "general": { + "title": "Associez vos jalons de projet aux Modules et suivez facilement le travail agrégé.", + "description": "Un groupe d'éléments de travail qui appartiennent à un parent logique et hiérarchique forme un module. Considérez-les comme un moyen de suivre le travail par jalons de projet. Ils ont leurs propres périodes et délais ainsi que des analyses pour vous aider à voir à quel point vous êtes proche ou loin d'un jalon.", + "primary_button": { + "text": "Construisez votre premier module", + "comic": { + "title": "Les modules aident à regrouper le travail par hiérarchie.", + "description": "Un module panier, un module châssis et un module entrepôt sont tous de bons exemples de ce regroupement." + } + } + }, + "no_issues": { + "title": "Aucun élément de travail dans le module", + "description": "Créez ou ajoutez des éléments de travail que vous souhaitez accomplir dans le cadre de ce module", + "primary_button": { + "text": "Créer de nouveaux éléments de travail" + }, + "secondary_button": { + "text": "Ajouter un élément existant" + } + }, + "archived": { + "title": "Aucun module archivé pour le moment", + "description": "Pour organiser votre projet, archivez les modules terminés ou annulés. Retrouvez-les ici une fois archivés." + }, + "sidebar": { + "in_active": "Ce module n'est pas encore actif.", + "invalid_date": "Date invalide. Veuillez entrer une date valide." + } + }, + "quick_actions": { + "archive_module": "Archiver le module", + "archive_module_description": "Seuls les modules terminés ou\nannulés peuvent être archivés.", + "delete_module": "Supprimer le module" + }, + "toast": { + "copy": { + "success": "Lien du module copié dans le presse-papiers" + }, + "delete": { + "success": "Module supprimé avec succès", + "error": "Échec de la suppression du module" + } + } + }, + + "project_views": { + "empty_state": { + "general": { + "title": "Enregistrez des vues filtrées pour votre projet. Créez-en autant que nécessaire", + "description": "Les vues sont un ensemble de filtres enregistrés que vous utilisez fréquemment ou auxquels vous souhaitez avoir un accès facile. Tous vos collègues dans un projet peuvent voir les vues de chacun et choisir celle qui convient le mieux à leurs besoins.", + "primary_button": { + "text": "Créez votre première vue", + "comic": { + "title": "Les vues fonctionnent sur les propriétés des éléments de travail.", + "description": "Vous pouvez créer une vue ici avec autant de propriétés comme filtres que vous le jugez approprié." + } + } + }, + "filter": { + "title": "Aucune vue correspondante", + "description": "Aucune vue ne correspond aux critères de recherche. \n Créez plutôt une nouvelle vue." + } + } + }, + + "project_page": { + "empty_state": { + "general": { + "title": "Rédigez une note, un document ou une base de connaissances complète. Obtenez l'aide de Galileo, l'assistant IA de Plane, pour commencer", + "description": "Les Pages sont un espace de réflexion dans Plane. Prenez des notes de réunion, formatez-les facilement, intégrez des éléments de travail, disposez-les à l'aide d'une bibliothèque de composants, et gardez-les tous dans le contexte de votre projet. Pour faciliter la rédaction de tout document, faites appel à Galileo, l'IA de Plane, avec un raccourci ou un clic sur un bouton.", + "primary_button": { + "text": "Créez votre première page" + } + }, + "private": { + "title": "Pas encore de pages privées", + "description": "Gardez vos pensées privées ici. Quand vous serez prêt à partager, l'équipe n'est qu'à un clic.", + "primary_button": { + "text": "Créez votre première page" + } + }, + "public": { + "title": "Pas encore de pages publiques", + "description": "Consultez ici les pages partagées avec tout le monde dans votre projet.", + "primary_button": { + "text": "Créez votre première page" + } + }, + "archived": { + "title": "Pas encore de pages archivées", + "description": "Archivez les pages qui ne sont pas dans votre radar. Accédez-y ici quand nécessaire." + } + } + }, + + "command_k": { + "empty_state": { + "search": { + "title": "Aucun résultat trouvé" + } + } + }, + + "issue_relation": { + "empty_state": { + "search": { + "title": "Aucun élément de travail correspondant trouvé" + }, + "no_issues": { + "title": "Aucun élément de travail trouvé" + } + } + }, + + "issue_comment": { + "empty_state": { + "general": { + "title": "Pas encore de commentaires", + "description": "Les commentaires peuvent être utilisés comme espace de discussion et de suivi pour les éléments de travail" + } + } + }, + + "notification": { + "label": "Boîte de réception", + "page_label": "{workspace} - Boîte de réception", + "options": { + "mark_all_as_read": "Tout marquer comme lu", + "mark_read": "Marquer comme lu", + "mark_unread": "Marquer comme non lu", + "refresh": "Actualiser", + "filters": "Filtres de la boîte de réception", + "show_unread": "Afficher les non lus", + "show_snoozed": "Afficher les reportés", + "show_archived": "Afficher les archivés", + "mark_archive": "Archiver", + "mark_unarchive": "Désarchiver", + "mark_snooze": "Reporter", + "mark_unsnooze": "Annuler le report" + }, + "toasts": { + "read": "Notification marquée comme lue", + "unread": "Notification marquée comme non lue", + "archived": "Notification marquée comme archivée", + "unarchived": "Notification marquée comme non archivée", + "snoozed": "Notification reportée", + "unsnoozed": "Report de la notification annulé" + }, + "empty_state": { + "detail": { + "title": "Sélectionnez pour voir les détails." + }, + "all": { + "title": "Aucun élément de travail assigné", + "description": "Les mises à jour des éléments de travail qui vous sont assignés peuvent être \n vues ici" + }, + "mentions": { + "title": "Aucun élément de travail assigné", + "description": "Les mises à jour des éléments de travail qui vous sont assignés peuvent être \n vues ici" + } + }, + "tabs": { + "all": "Tout", + "mentions": "Mentions" + }, + "filter": { + "assigned": "Assigné à moi", + "created": "Créé par moi", + "subscribed": "Suivi par moi" + }, + "snooze": { + "1_day": "1 jour", + "3_days": "3 jours", + "5_days": "5 jours", + "1_week": "1 semaine", + "2_weeks": "2 semaines", + "custom": "Personnalisé" + } + }, + + "active_cycle": { + "empty_state": { + "progress": { + "title": "Ajoutez des éléments de travail au cycle pour voir sa progression" + }, + "chart": { + "title": "Ajoutez des éléments de travail au cycle pour voir le graphique d'avancement." + }, + "priority_issue": { + "title": "Observez en un coup d'œil les éléments de travail prioritaires traités dans le cycle." + }, + "assignee": { + "title": "Ajoutez des assignés aux éléments de travail pour voir une répartition du travail par assigné." + }, + "label": { + "title": "Ajoutez des étiquettes aux éléments de travail pour voir la répartition du travail par étiquette." + } + } + }, + + "disabled_project": { + "empty_state": { + "inbox": { + "title": "L'Intake n'est pas activé pour le projet.", + "description": "L'Intake vous aide à gérer les demandes entrantes dans votre projet et à les ajouter comme éléments de travail dans votre flux. Activez l'Intake depuis les paramètres du projet pour gérer les demandes.", + "primary_button": { + "text": "Gérer les fonctionnalités" + } + }, + "cycle": { + "title": "Les Cycles ne sont pas activés pour ce projet.", + "description": "Découpez le travail en segments temporels, planifiez à rebours depuis la date limite de votre projet pour définir les dates, et progressez concrètement en équipe. Activez la fonctionnalité Cycles pour votre projet pour commencer à les utiliser.", + "primary_button": { + "text": "Gérer les fonctionnalités" + } + }, + "module": { + "title": "Les Modules ne sont pas activés pour le projet.", + "description": "Les Modules sont les éléments constitutifs de votre projet. Activez les modules depuis les paramètres du projet pour commencer à les utiliser.", + "primary_button": { + "text": "Gérer les fonctionnalités" + } + }, + "page": { + "title": "Les Pages ne sont pas activées pour le projet.", + "description": "Les Pages sont les éléments constitutifs de votre projet. Activez les pages depuis les paramètres du projet pour commencer à les utiliser.", + "primary_button": { + "text": "Gérer les fonctionnalités" + } + }, + "view": { + "title": "Les Vues ne sont pas activées pour le projet.", + "description": "Les Vues sont les éléments constitutifs de votre projet. Activez les vues depuis les paramètres du projet pour commencer à les utiliser.", + "primary_button": { + "text": "Gérer les fonctionnalités" + } + } + } + }, + "workspace_draft_issues": { + "draft_an_issue": "Créer un brouillon d'élément de travail", + "empty_state": { + "title": "Les éléments de travail partiellement rédigés, et bientôt les commentaires, apparaîtront ici.", + "description": "Pour essayer cela, commencez à ajouter un élément de travail et laissez-le à mi-chemin ou créez votre premier brouillon ci-dessous. 😉", + "primary_button": { + "text": "Créez votre premier brouillon" + } + }, + "delete_modal": { + "title": "Supprimer le brouillon", + "description": "Êtes-vous sûr de vouloir supprimer ce brouillon ? Cette action ne peut pas être annulée." + }, + "toasts": { + "created": { + "success": "Brouillon créé", + "error": "L'élément de travail n'a pas pu être créé. Veuillez réessayer." + }, + "deleted": { + "success": "Brouillon supprimé" + } + } + }, + + "stickies": { + "title": "Vos notes adhésives", + "placeholder": "cliquez pour écrire ici", + "all": "Toutes les notes adhésives", + "no-data": "Notez une idée, capturez une révélation ou enregistrez une inspiration. Ajoutez une note adhésive pour commencer.", + "add": "Ajouter une note adhésive", + "search_placeholder": "Rechercher par titre", + "delete": "Supprimer la note adhésive", + "delete_confirmation": "Êtes-vous sûr de vouloir supprimer cette note adhésive ?", + "empty_state": { + "simple": "Notez une idée, capturez une révélation ou enregistrez une inspiration. Ajoutez une note adhésive pour commencer.", + "general": { + "title": "Les notes adhésives sont des notes rapides et des tâches que vous prenez à la volée.", + "description": "Capturez vos pensées et idées facilement en créant des notes adhésives que vous pouvez consulter à tout moment et de n'importe où.", + "primary_button": { + "text": "Ajouter une note adhésive" + } + }, + "search": { + "title": "Cela ne correspond à aucune de vos notes adhésives.", + "description": "Essayez un terme différent ou faites-nous savoir\nsi vous êtes sûr que votre recherche est correcte.", + "primary_button": { + "text": "Ajouter une note adhésive" + } + } + }, + "toasts": { + "errors": { + "wrong_name": "Le nom de la note adhésive ne peut pas dépasser 100 caractères.", + "already_exists": "Il existe déjà une note adhésive sans description" + }, + "created": { + "title": "Note adhésive créée", + "message": "La note adhésive a été créée avec succès" + }, + "not_created": { + "title": "Note adhésive non créée", + "message": "La note adhésive n'a pas pu être créée" + }, + "updated": { + "title": "Note adhésive mise à jour", + "message": "La note adhésive a été mise à jour avec succès" + }, + "not_updated": { + "title": "Note adhésive non mise à jour", + "message": "La note adhésive n'a pas pu être mise à jour" + }, + "removed": { + "title": "Note adhésive supprimée", + "message": "La note adhésive a été supprimée avec succès" + }, + "not_removed": { + "title": "Note adhésive non supprimée", + "message": "La note adhésive n'a pas pu être supprimée" + } + } + }, + + "role_details": { + "guest": { + "title": "Invité", + "description": "Les membres externes des organisations peuvent être invités en tant qu'invités." + }, + "member": { + "title": "Membre", + "description": "Capacité à lire, écrire, modifier et supprimer des entités dans les projets, cycles et modules" + }, + "admin": { + "title": "Administrateur", + "description": "Toutes les permissions sont activées dans l'espace de travail." + } + }, + + "user_roles": { + "product_or_project_manager": "Chef de produit / Chef de projet", + "development_or_engineering": "Développement / Ingénierie", + "founder_or_executive": "Fondateur / Dirigeant", + "freelancer_or_consultant": "Freelance / Consultant", + "marketing_or_growth": "Marketing / Croissance", + "sales_or_business_development": "Ventes / Développement commercial", + "support_or_operations": "Support / Opérations", + "student_or_professor": "Étudiant / Professeur", + "human_resources": "Ressources Humaines", + "other": "Autre" + }, + + "importer": { + "github": { + "title": "GitHub", + "description": "Importez des éléments de travail depuis les dépôts GitHub et synchronisez-les." + }, + "jira": { + "title": "Jira", + "description": "Importez des éléments de travail et des epics depuis les projets et epics Jira." + } + }, + + "exporter": { + "csv": { + "title": "CSV", + "description": "Exportez les éléments de travail vers un fichier CSV.", + "short_description": "Exporter en csv" + }, + "excel": { + "title": "Excel", + "description": "Exportez les éléments de travail vers un fichier Excel.", + "short_description": "Exporter en excel" + }, + "xlsx": { + "title": "Excel", + "description": "Exportez les éléments de travail vers un fichier Excel.", + "short_description": "Exporter en excel" + }, + "json": { + "title": "JSON", + "description": "Exportez les éléments de travail vers un fichier JSON.", + "short_description": "Exporter en json" + } + }, + "default_global_view": { + "all_issues": "Tous les éléments de travail", + "assigned": "Assignés", + "created": "Créés", + "subscribed": "Suivis" + }, + + "themes": { + "theme_options": { + "system_preference": { + "label": "Préférence système" + }, + "light": { + "label": "Clair" + }, + "dark": { + "label": "Sombre" + }, + "light_contrast": { + "label": "Contraste clair élevé" + }, + "dark_contrast": { + "label": "Contraste sombre élevé" + }, + "custom": { + "label": "Thème personnalisé" + } + } + }, + "project_modules": { + "status": { + "backlog": "Backlog", + "planned": "Planifié", + "in_progress": "En cours", + "paused": "En pause", + "completed": "Terminé", + "cancelled": "Annulé" + }, + "layout": { + "list": "Vue liste", + "board": "Vue galerie", + "timeline": "Vue chronologique" + }, + "order_by": { + "name": "Nom", + "progress": "Progression", + "issues": "Nombre d'éléments de travail", + "due_date": "Date d'échéance", + "created_at": "Date de création", + "manual": "Manuel" + } + }, + + "cycle": { + "label": "{count, plural, one {Cycle} other {Cycles}}", + "no_cycle": "Pas de cycle" + }, + + "module": { + "label": "{count, plural, one {Module} other {Modules}}", + "no_module": "Pas de module" + } } diff --git a/packages/i18n/src/locales/ja/translations.json b/packages/i18n/src/locales/ja/translations.json index 77f7b32db2e..3b71443fa08 100644 --- a/packages/i18n/src/locales/ja/translations.json +++ b/packages/i18n/src/locales/ja/translations.json @@ -1,4 +1,174 @@ { + "sidebar": { + "projects": "プロジェクト", + "pages": "ページ", + "new_work_item": "新規作業項目", + "home": "ホーム", + "your_work": "あなたの作業", + "inbox": "受信トレイ", + "workspace": "ワークスペース", + "views": "ビュー", + "analytics": "アナリティクス", + "work_items": "作業項目", + "cycles": "サイクル", + "modules": "モジュール", + "intake": "インテーク", + "drafts": "下書き", + "favorites": "お気に入り", + "pro": "プロ", + "upgrade": "アップグレード" + }, + + "auth": { + "common": { + "email": { + "label": "メールアドレス", + "placeholder": "name@company.com", + "errors": { + "required": "メールアドレスは必須です", + "invalid": "メールアドレスが無効です" + } + }, + "password": { + "label": "パスワード", + "set_password": "パスワードを設定", + "placeholder": "パスワードを入力", + "confirm_password": { + "label": "パスワードの確認", + "placeholder": "パスワードを確認" + }, + "current_password": { + "label": "現在のパスワード" + }, + "new_password": { + "label": "新しいパスワード", + "placeholder": "新しいパスワードを入力" + }, + "change_password": { + "label": { + "default": "パスワードを変更", + "submitting": "パスワードを変更中" + } + }, + "errors": { + "match": "パスワードが一致しません", + "empty": "パスワードを入力してください", + "length": "パスワードは8文字以上である必要があります", + "strength": { + "weak": "パスワードが弱すぎます", + "strong": "パスワードは十分な強度です" + } + }, + "submit": "パスワードを設定", + "toast": { + "change_password": { + "success": { + "title": "成功!", + "message": "パスワードが正常に変更されました。" + }, + "error": { + "title": "エラー!", + "message": "問題が発生しました。もう一度お試しください。" + } + } + } + }, + "unique_code": { + "label": "ユニークコード", + "placeholder": "gets-sets-flys", + "paste_code": "メールで送信されたコードを貼り付けてください", + "requesting_new_code": "新しいコードをリクエスト中", + "sending_code": "コードを送信中" + }, + "already_have_an_account": "すでにアカウントをお持ちですか?", + "login": "ログイン", + "create_account": "アカウントを作成", + "new_to_plane": "Planeは初めてですか?", + "back_to_sign_in": "サインインに戻る", + "resend_in": "{seconds}秒後に再送信", + "sign_in_with_unique_code": "ユニークコードでサインイン", + "forgot_password": "パスワードをお忘れですか?" + }, + "sign_up": { + "header": { + "label": "チームと作業を管理するためのアカウントを作成してください。", + "step": { + "email": { + "header": "サインアップ", + "sub_header": "" + }, + "password": { + "header": "サインアップ", + "sub_header": "メールアドレスとパスワードの組み合わせでサインアップ。" + }, + "unique_code": { + "header": "サインアップ", + "sub_header": "上記のメールアドレスに送信されたユニークコードでサインアップ。" + } + } + }, + "errors": { + "password": { + "strength": "強力なパスワードを設定して続行してください" + } + } + }, + "sign_in": { + "header": { + "label": "チームと作業を管理するためにログインしてください。", + "step": { + "email": { + "header": "ログインまたはサインアップ", + "sub_header": "" + }, + "password": { + "header": "ログインまたはサインアップ", + "sub_header": "メールアドレスとパスワードの組み合わせでログイン。" + }, + "unique_code": { + "header": "ログインまたはサインアップ", + "sub_header": "上記のメールアドレスに送信されたユニークコードでログイン。" + } + } + } + }, + "forgot_password": { + "title": "パスワードをリセット", + "description": "確認済みのユーザーアカウントのメールアドレスを入力してください。パスワードリセットリンクを送信します。", + "email_sent": "リセットリンクをメールアドレスに送信しました", + "send_reset_link": "リセットリンクを送信", + "errors": { + "smtp_not_enabled": "管理者がSMTPを有効にしていないため、パスワードリセットリンクを送信できません" + }, + "toast": { + "success": { + "title": "メール送信完了", + "message": "パスワードをリセットするためのリンクを受信トレイで確認してください。数分以内に表示されない場合は、迷惑メールフォルダを確認してください。" + }, + "error": { + "title": "エラー!", + "message": "問題が発生しました。もう一度お試しください。" + } + } + }, + "reset_password": { + "title": "新しいパスワードを設定", + "description": "強力なパスワードでアカウントを保護" + }, + "set_password": { + "title": "アカウントを保護", + "description": "パスワードを設定して安全にログイン" + }, + "sign_out": { + "toast": { + "error": { + "title": "エラー!", + "message": "サインアウトに失敗しました。もう一度お試しください。" + } + } + } + }, + "submit": "送信", "cancel": "キャンセル", "loading": "読み込み中", @@ -14,10 +184,13 @@ "description": "説明", "search": "検索", "add_member": "メンバーを追加", + "adding_members": "メンバーを追加中", "remove_member": "メンバーを削除", "add_members": "メンバーを追加", + "adding_member": "メンバーを追加中", "remove_members": "メンバーを削除", "add": "追加", + "adding": "追加中", "remove": "削除", "add_new": "新規追加", "remove_selected": "選択項目を削除", @@ -32,16 +205,15 @@ "password": "パスワード", "change_cover": "カバーを変更", "language": "言語", - "saving": "保存中...", + "saving": "保存中", "save_changes": "変更を保存", "deactivate_account": "アカウントを無効化", - "deactivate_account_description": "アカウントを無効化すると、そのアカウント内のすべてのデータとリソースが完全に削除され、復元することはできません。", - "profile_settings": "プロフィール設定", - "your_account": "アカウント", - "profile": "プロフィール", + "deactivate_account_description": "アカウントを無効化すると、そのアカウント内のすべてのデータとリソースが完全に削除され、復元できなくなります。", + "profile_settings": "プロフィール設定", + "your_account": "あなたのアカウント", "security": "セキュリティ", - "activity": "アクティビティ", - "appearance": "アピアンス", + "activity": "アクティビティ", + "appearance": "外観", "notifications": "通知", "workspaces": "ワークスペース", "create_workspace": "ワークスペースを作成", @@ -49,116 +221,75 @@ "summary": "概要", "assigned": "割り当て済み", "created": "作成済み", - "subscribed": "購読済み", + "subscribed": "購読中", "you_do_not_have_the_permission_to_access_this_page": "このページにアクセスする権限がありません。", - "failed_to_sign_out_please_try_again": "サインアウトに失敗しました。もう一度お試しください。", - "password_changed_successfully": "パスワードが正常に変更されました。", - "something_went_wrong_please_try_again": "何かがうまくいきませんでした。もう一度お試しください。", - "change_password": "パスワードを変更", - "passwords_dont_match": "パスワードが一致しません", - "current_password": "現在のパスワード", - "new_password": "新しいパスワード", - "confirm_password": "パスワードを確認", - "this_field_is_required": "このフィールドは必須です", - "changing_password": "パスワードを変更中", - "please_enter_your_password": "パスワードを入力してください。", - "password_length_should_me_more_than_8_characters": "パスワードの長さは8文字以上である必要があります。", - "password_is_weak": "パスワードが弱いです。", - "password_is_strong": "パスワードが強いです。", + "something_went_wrong_please_try_again": "問題が発生しました。もう一度お試しください。", "load_more": "もっと読み込む", - "select_or_customize_your_interface_color_scheme": "インターフェースのカラースキームを選択またはカスタマイズしてください。", + "select_or_customize_your_interface_color_scheme": "インターフェースの配色を選択またはカスタマイズしてください。", "theme": "テーマ", - "system_preference": "システム設定", + "system_preference": "システム設定に従う", "light": "ライト", "dark": "ダーク", - "light_contrast": "ライト高コントラスト", - "dark_contrast": "ダーク高コントラスト", + "light_contrast": "ライトハイコントラスト", + "dark_contrast": "ダークハイコントラスト", "custom": "カスタムテーマ", "select_your_theme": "テーマを選択", "customize_your_theme": "テーマをカスタマイズ", "background_color": "背景色", - "text_color": "テキスト色", - "primary_color": "プライマリ(テーマ)色", - "sidebar_background_color": "サイドバー背景色", - "sidebar_text_color": "サイドバーテキスト色", + "text_color": "文字色", + "primary_color": "プライマリ(テーマ)カラー", + "sidebar_background_color": "サイドバーの背景色", + "sidebar_text_color": "サイドバーの文字色", "set_theme": "テーマを設定", "enter_a_valid_hex_code_of_6_characters": "6文字の有効な16進コードを入力してください", "background_color_is_required": "背景色は必須です", - "text_color_is_required": "テキスト色は必須です", - "primary_color_is_required": "プライマリ色は必須です", - "sidebar_background_color_is_required": "サイドバー背景色は必須です", - "sidebar_text_color_is_required": "サイドバーテキスト色は必須です", + "text_color_is_required": "文字色は必須です", + "primary_color_is_required": "プライマリカラーは必須です", + "sidebar_background_color_is_required": "サイドバーの背景色は必須です", + "sidebar_text_color_is_required": "サイドバーの文字色は必須です", "updating_theme": "テーマを更新中", "theme_updated_successfully": "テーマが正常に更新されました", "failed_to_update_the_theme": "テーマの更新に失敗しました", "email_notifications": "メール通知", - "stay_in_the_loop_on_issues_you_are_subscribed_to_enable_this_to_get_notified": "購読している問題についての通知を受け取るには、これを有効にしてください。", + "stay_in_the_loop_on_issues_you_are_subscribed_to_enable_this_to_get_notified": "購読している作業項目の最新情報を受け取ります。通知を受け取るには有効にしてください。", "email_notification_setting_updated_successfully": "メール通知設定が正常に更新されました", "failed_to_update_email_notification_setting": "メール通知設定の更新に失敗しました", - "notify_me_when": "通知する条件", + "notify_me_when": "通知を受け取るタイミング", "property_changes": "プロパティの変更", - "property_changes_description": "担当者、優先度、見積もりなどのプロパティが変更されたときに通知します。", + "property_changes_description": "作業項目の担当者、優先度、見積もりなどのプロパティが変更されたときに通知します。", "state_change": "状態の変更", - "state_change_description": "問題が別の状態に移動したときに通知します", - "issue_completed": "問題が完了", - "issue_completed_description": "問題が完了したときのみ通知します", + "state_change_description": "作業項目が異なる状態に移動したときに通知します", + "issue_completed": "作業項目の完了", + "issue_completed_description": "作業項目が完了したときのみ通知します", "comments": "コメント", - "comments_description": "誰かが問題にコメントを残したときに通知します", + "comments_description": "誰かが作業項目にコメントを残したときに通知します", "mentions": "メンション", - "mentions_description": "コメントや説明で誰かが自分をメンションしたときのみ通知します", - "create_your_workspace": "ワークスペースを作成", - "only_your_instance_admin_can_create_workspaces": "ワークスペースを作成できるのはインスタンス管理者のみです", - "only_your_instance_admin_can_create_workspaces_description": "インスタンス管理者のメールアドレスを知っている場合は、以下のボタンをクリックして連絡を取ってください。", - "go_back": "戻る", - "request_instance_admin": "インスタンス管理者にリクエスト", - "plane_logo": "プレーンロゴ", - "workspace_creation_disabled": "ワークスペースの作成が無効化されています", - "workspace_request_subject": "新しいワークスペースのリクエスト", - "workspace_request_body": "インスタンス管理者様\n\nURL [/workspace-name] で新しいワークスペースを作成してください。[ワークスペース作成の目的]\n\nありがとうございます。\n{{firstName}} {{lastName}}\n{{email}}", - "creating_workspace": "ワークスペースを作成中", - "workspace_created_successfully": "ワークスペースが正常に作成されました", - "create_workspace_page": "ワークスペース作成ページ", - "workspace_could_not_be_created_please_try_again": "ワークスペースを作成できませんでした。もう一度お試しください。", - "workspace_could_not_be_created_please_try_again_description": "ワークスペースの作成中にエラーが発生しました。もう一度お試しください。", - "this_is_a_required_field": "これは必須フィールドです。", - "name_your_workspace": "ワークスペースに名前を付ける", - "workspaces_names_can_contain_only_space_dash_and_alphanumeric_characters": "ワークスペース名にはスペース、ダッシュ、アンダースコア、英数字のみを含めることができます。", - "limit_your_name_to_80_characters": "名前は80文字以内にしてください。", - "set_your_workspace_url": "ワークスペースのURLを設定", - "limit_your_url_to_48_characters": "URLは48文字以内にしてください。", - "how_many_people_will_use_this_workspace": "このワークスペースを使用する人数は?", - "how_many_people_will_use_this_workspace_description": "購入するシート数を決定するのに役立ちます。", - "select_a_range": "範囲を選択", - "urls_can_contain_only_dash_and_alphanumeric_characters": "URLにはダッシュと英数字のみを含めることができます。", - "something_familiar_and_recognizable_is_always_best": "親しみやすく認識しやすいものが常に最適です。", - "workspace_url_is_already_taken": "ワークスペースのURLは既に使用されています!", - "old_password": "古いパスワード", + "mentions_description": "誰かがコメントや説明で自分をメンションしたときのみ通知します", + "old_password": "現在のパスワード", "general_settings": "一般設定", "sign_out": "サインアウト", "signing_out": "サインアウト中", "active_cycles": "アクティブサイクル", - "active_cycles_description": "プロジェクト全体のサイクルを監視し、高優先度の問題を追跡し、注意が必要なサイクルにズームインします。", + "active_cycles_description": "プロジェクト全体のサイクルを監視し、優先度の高い作業項目を追跡し、注意が必要なサイクルにズームインします。", "on_demand_snapshots_of_all_your_cycles": "すべてのサイクルのオンデマンドスナップショット", "upgrade": "アップグレード", - "10000_feet_view": "すべてのアクティブサイクルの10,000フィートビュー。", - "10000_feet_view_description": "各プロジェクトのサイクルを個別に見るのではなく、すべてのプロジェクトのサイクルを一度に見るためにズームアウトします。", - "get_snapshot_of_each_active_cycle": "各アクティブサイクルのスナップショットを取得します。", - "get_snapshot_of_each_active_cycle_description": "すべてのアクティブサイクルの高レベルのメトリクスを追跡し、進捗状況を確認し、期限に対するスコープの感覚を得ます。", - "compare_burndowns": "バーンダウンを比較します。", - "compare_burndowns_description": "各チームのパフォーマンスを監視し、各サイクルのバーンダウンレポートを覗き見します。", - "quickly_see_make_or_break_issues": "重要な問題をすばやく確認します。", - "quickly_see_make_or_break_issues_description": "各サイクルの期限に対する高優先度の問題をプレビューします。1クリックでサイクルごとにすべてを確認できます。", - "zoom_into_cycles_that_need_attention": "注意が必要なサイクルにズームインします。", - "zoom_into_cycles_that_need_attention_description": "期待に沿わないサイクルの状態を1クリックで調査します。", - "stay_ahead_of_blockers": "ブロッカーを先取りします。", - "stay_ahead_of_blockers_description": "プロジェクト間の課題を見つけ、他のビューからは明らかでないサイクル間の依存関係を確認します。", - "analytics": "分析", + "10000_feet_view": "すべてのアクティブサイクルの俯瞰図", + "10000_feet_view_description": "各プロジェクトのサイクル間を移動する代わりに、すべてのプロジェクトの実行中のサイクルを一度に確認できます。", + "get_snapshot_of_each_active_cycle": "各アクティブサイクルのスナップショットを取得", + "get_snapshot_of_each_active_cycle_description": "すべてのアクティブサイクルの主要な指標を追跡し、進捗状況を確認し、期限に対する範囲を把握します。", + "compare_burndowns": "バーンダウンを比較", + "compare_burndowns_description": "各サイクルのバーンダウンレポートを確認して、各チームのパフォーマンスを監視します。", + "quickly_see_make_or_break_issues": "重要な作業項目をすぐに確認", + "quickly_see_make_or_break_issues_description": "期限に対する各サイクルの優先度の高い作業項目をプレビューします。ワンクリックでサイクルごとにすべての項目を確認できます。", + "zoom_into_cycles_that_need_attention": "注意が必要なサイクルにズームイン", + "zoom_into_cycles_that_need_attention_description": "期待に沿わないサイクルの状態をワンクリックで調査します。", + "stay_ahead_of_blockers": "ブロッカーに先手を打つ", + "stay_ahead_of_blockers_description": "プロジェクト間の課題を特定し、他のビューでは明らかでないサイクル間の依存関係を確認します。", + "analytics": "アナリティクス", "workspace_invites": "ワークスペースの招待", - "workspace_settings": "ワークスペース設定", "enter_god_mode": "ゴッドモードに入る", - "workspace_logo": "ワークスペースロゴ", - "new_issue": "新しい問題", - "home": "ホーム", + "workspace_logo": "ワークスペースのロゴ", + "new_issue": "新規作業項目", "your_work": "あなたの作業", "drafts": "下書き", "projects": "プロジェクト", @@ -168,15 +299,15 @@ "settings": "設定", "failed_to_move_favorite": "お気に入りの移動に失敗しました", "favorites": "お気に入り", - "no_favorites_yet": "まだお気に入りはありません", - "create_folder": "フォルダーを作成", - "new_folder": "新しいフォルダー", + "no_favorites_yet": "まだお気に入りがありません", + "create_folder": "フォルダを作成", + "new_folder": "新規フォルダ", "favorite_updated_successfully": "お気に入りが正常に更新されました", "favorite_created_successfully": "お気に入りが正常に作成されました", - "folder_already_exists": "フォルダーは既に存在します", - "folder_name_cannot_be_empty": "フォルダー名を空にすることはできません", - "something_went_wrong": "何かがうまくいきませんでした", - "failed_to_reorder_favorite": "お気に入りの並べ替えに失敗しました", + "folder_already_exists": "フォルダは既に存在します", + "folder_name_cannot_be_empty": "フォルダ名を空にすることはできません", + "something_went_wrong": "問題が発生しました", + "failed_to_reorder_favorite": "お気に入りの並び替えに失敗しました", "favorite_removed_successfully": "お気に入りが正常に削除されました", "failed_to_create_favorite": "お気に入りの作成に失敗しました", "failed_to_rename_favorite": "お気に入りの名前変更に失敗しました", @@ -184,117 +315,122 @@ "link_copied": "リンクがコピーされました", "add_project": "プロジェクトを追加", "create_project": "プロジェクトを作成", - "failed_to_remove_project_from_favorites": "お気に入りからプロジェクトを削除できませんでした。もう一度お試しください。", + "failed_to_remove_project_from_favorites": "プロジェクトをお気に入りから削除できませんでした。もう一度お試しください。", "project_created_successfully": "プロジェクトが正常に作成されました", - "project_created_successfully_description": "プロジェクトが正常に作成されました。今すぐ問題を追加し始めることができます。", - "project_cover_image_alt": "プロジェクトカバー画像", + "project_created_successfully_description": "プロジェクトが正常に作成されました。作業項目を追加できるようになりました。", + "project_cover_image_alt": "プロジェクトのカバー画像", "name_is_required": "名前は必須です", "title_should_be_less_than_255_characters": "タイトルは255文字未満である必要があります", "project_name": "プロジェクト名", - "project_id_must_be_at_least_1_character": "プロジェクトIDは少なくとも1文字である必要があります", - "project_id_must_be_at_most_5_characters": "プロジェクトIDは最大5文字である必要があります", + "project_id_must_be_at_least_1_character": "プロジェクトIDは最低1文字必要です", + "project_id_must_be_at_most_5_characters": "プロジェクトIDは最大5文字までです", "project_id": "プロジェクトID", - "project_id_tooltip_content": "プロジェクト内の問題を一意に識別するのに役立ちます。最大5文字。", - "description_placeholder": "説明...", - "only_alphanumeric_non_latin_characters_allowed": "英数字と非ラテン文字のみが許可されます。", + "project_id_tooltip_content": "プロジェクト内の作業項目を一意に識別するのに役立ちます。最大5文字。", + "description_placeholder": "説明", + "only_alphanumeric_non_latin_characters_allowed": "英数字と非ラテン文字のみ使用できます。", "project_id_is_required": "プロジェクトIDは必須です", + "project_id_allowed_char": "英数字と非ラテン文字のみ使用できます。", + "project_id_min_char": "プロジェクトIDは最低1文字必要です", + "project_id_max_char": "プロジェクトIDは最大5文字までです", + "project_description_placeholder": "プロジェクトの説明を入力", "select_network": "ネットワークを選択", "lead": "リード", + "date_range": "日付範囲", "private": "プライベート", "public": "パブリック", - "accessible_only_by_invite": "招待によってのみアクセス可能", - "anyone_in_the_workspace_except_guests_can_join": "ゲストを除くワークスペース内の誰でも参加できます", + "accessible_only_by_invite": "招待のみアクセス可能", + "anyone_in_the_workspace_except_guests_can_join": "ゲスト以外のワークスペースのメンバーが参加可能", "creating": "作成中", "creating_project": "プロジェクトを作成中", "adding_project_to_favorites": "プロジェクトをお気に入りに追加中", "project_added_to_favorites": "プロジェクトがお気に入りに追加されました", "couldnt_add_the_project_to_favorites": "プロジェクトをお気に入りに追加できませんでした。もう一度お試しください。", - "removing_project_from_favorites": "お気に入りからプロジェクトを削除中", + "removing_project_from_favorites": "プロジェクトをお気に入りから削除中", "project_removed_from_favorites": "プロジェクトがお気に入りから削除されました", - "couldnt_remove_the_project_from_favorites": "お気に入りからプロジェクトを削除できませんでした。もう一度お試しください。", + "couldnt_remove_the_project_from_favorites": "プロジェクトをお気に入りから削除できませんでした。もう一度お試しください。", "add_to_favorites": "お気に入りに追加", "remove_from_favorites": "お気に入りから削除", "publish_settings": "公開設定", "publish": "公開", "copy_link": "リンクをコピー", - "leave_project": "プロジェクトを離れる", - "join_the_project_to_rearrange": "プロジェクトに参加して並べ替え", - "drag_to_rearrange": "ドラッグして並べ替え", + "leave_project": "プロジェクトを退出", + "join_the_project_to_rearrange": "並び替えるにはプロジェクトに参加してください", + "drag_to_rearrange": "ドラッグして並び替え", "congrats": "おめでとうございます!", - "project": "プロジェクト", "open_project": "プロジェクトを開く", - "issues": "問題", - "cycles": "サイクル", - "modules": "モジュール", - "pages": "ページ", - "intake": "インテーク", - "time_tracking": "時間追跡", + "issues": "作業項目", + "cycles": "Cycles", + "modules": "Modules", + "pages": "Pages", + "intake": "Intake", + "time_tracking": "時間トラッキング", "work_management": "作業管理", - "projects_and_issues": "プロジェクトと問題", - "projects_and_issues_description": "このプロジェクトでオンまたはオフに切り替えます。", - "cycles_description": "プロジェクトごとに作業をタイムボックス化し、期間ごとに頻度を変更します。", - "modules_description": "独自のリードと担当者を持つサブプロジェクトのようなセットアップに作業をグループ化します。", - "views_description": "後で使用するために、または共有するためにソート、フィルター、表示オプションを保存します。", - "pages_description": "何かを書くように何かを書く。", - "intake_description": "購読している問題についての通知を受け取るには、これを有効にしてください。", - "time_tracking_description": "問題とプロジェクトに費やした時間を追跡します。", + "projects_and_issues": "プロジェクトと作業項目", + "projects_and_issues_description": "このプロジェクトでオン/オフを切り替えます。", + "cycles_description": "プロジェクトごとに作業を時間枠で区切り、期間を次の期間に変更します。", + "modules_description": "サブプロジェクトのような設定で、独自のリーダーと担当者を持つグループ作業を行います。", + "views_description": "並び替え、フィルター、表示オプションを後で使用するために保存するか、共有します。", + "pages_description": "何でも書けるように何でも書きます。", + "intake_description": "購読している作業項目の最新情報を受け取ります。通知を受け取るには有効にしてください。", + "time_tracking_description": "作業項目とプロジェクトの作業時間を追跡します。", "work_management_description": "作業とプロジェクトを簡単に管理します。", "documentation": "ドキュメント", - "message_support": "サポートにメッセージを送る", - "contact_sales": "営業に連絡", - "hyper_mode": "ハイパーモード", + "message_support": "サポートにメッセージ", + "contact_sales": "営業に問い合わせ", + "hyper_mode": "Hyper Mode", "keyboard_shortcuts": "キーボードショートカット", - "whats_new": "新着情報", + "whats_new": "新機能", "version": "バージョン", - "we_are_having_trouble_fetching_the_updates": "更新の取得に問題が発生しています。", - "our_changelogs": "私たちの変更履歴", + "we_are_having_trouble_fetching_the_updates": "更新情報の取得に問題が発生しています。", + "our_changelogs": "変更履歴", "for_the_latest_updates": "最新の更新情報については", - "please_visit": "訪問してください", + "please_visit": "をご覧ください", "docs": "ドキュメント", "full_changelog": "完全な変更履歴", "support": "サポート", - "discord": "ディスコード", - "powered_by_plane_pages": "Plane Pagesによって提供されています", + "discord": "Discord", + "powered_by_plane_pages": "Powered by Plane Pages", "please_select_at_least_one_invitation": "少なくとも1つの招待を選択してください。", - "please_select_at_least_one_invitation_description": "ワークスペースに参加するために少なくとも1つの招待を選択してください。", - "we_see_that_someone_has_invited_you_to_join_a_workspace": "誰かがワークスペースに参加するようにあなたを招待したことがわかります", + "please_select_at_least_one_invitation_description": "ワークスペースに参加するには少なくとも1つの招待を選択してください。", + "we_see_that_someone_has_invited_you_to_join_a_workspace": "誰かがあなたをワークスペースに招待しています", "join_a_workspace": "ワークスペースに参加", - "we_see_that_someone_has_invited_you_to_join_a_workspace_description": "誰かがワークスペースに参加するようにあなたを招待したことがわかります", + "we_see_that_someone_has_invited_you_to_join_a_workspace_description": "誰かがあなたをワークスペースに招待しています", "join_a_workspace_description": "ワークスペースに参加", - "accept_and_join": "受け入れて参加", - "go_home": "ホームに戻る", + "accept_and_join": "承諾して参加", + "go_home": "ホームへ", "no_pending_invites": "保留中の招待はありません", "you_can_see_here_if_someone_invites_you_to_a_workspace": "誰かがワークスペースに招待した場合、ここで確認できます", "back_to_home": "ホームに戻る", "workspace_name": "ワークスペース名", "deactivate_your_account": "アカウントを無効化", - "deactivate_your_account_description": "無効化すると、問題を割り当てられなくなり、ワークスペースの請求もされなくなります。アカウントを再有効化するには、このメールアドレスに招待されたワークスペースが必要です。", + "deactivate_your_account_description": "無効化すると、作業項目を割り当てられなくなり、ワークスペースの請求対象外となります。アカウントを再有効化するには、このメールアドレスでワークスペースへの招待が必要です。", "deactivating": "無効化中", "confirm": "確認", + "confirming": "確認中", "draft_created": "下書きが作成されました", - "issue_created_successfully": "問題が正常に作成されました", - "draft_creation_failed": "下書き作成に失敗しました", - "issue_creation_failed": "問題作成に失敗しました", - "draft_issue": "下書き問題", - "issue_updated_successfully": "問題が正常に更新されました", - "issue_could_not_be_updated": "問題を更新できませんでした", + "issue_created_successfully": "作業項目が正常に作成されました", + "draft_creation_failed": "下書きの作成に失敗しました", + "issue_creation_failed": "作業項目の作成に失敗しました", + "draft_issue": "作業項目の下書き", + "issue_updated_successfully": "作業項目が正常に更新されました", + "issue_could_not_be_updated": "作業項目を更新できませんでした", "create_a_draft": "下書きを作成", "save_to_drafts": "下書きに保存", "save": "保存", "update": "更新", "updating": "更新中", - "create_new_issue": "新しい問題を作成", + "create_new_issue": "新規作業項目を作成", "editor_is_not_ready_to_discard_changes": "エディターは変更を破棄する準備ができていません", - "failed_to_move_issue_to_project": "問題をプロジェクトに移動できませんでした", - "create_more": "作成する", + "failed_to_move_issue_to_project": "作業項目をプロジェクトに移動できませんでした", + "create_more": "さらに作成", "add_to_project": "プロジェクトに追加", "discard": "破棄", - "duplicate_issue_found": "重複問題が見つかりました", - "duplicate_issues_found": "重複問題が見つかりました", - "no_matching_results": "一致する結果はありません", + "duplicate_issue_found": "重複する作業項目が見つかりました", + "duplicate_issues_found": "重複する作業項目が見つかりました", + "no_matching_results": "一致する結果がありません", "title_is_required": "タイトルは必須です", "title": "タイトル", - "state": "ステータス", + "state": "状態", "priority": "優先度", "none": "なし", "urgent": "緊急", @@ -302,18 +438,1882 @@ "medium": "中", "low": "低", "members": "メンバー", - "assignee": "アサイン者", - "assignees": "アサイン者", + "assignee": "担当者", + "assignees": "担当者", "you": "あなた", "labels": "ラベル", - "create_new_label": "新しいラベルを作成", + "create_new_label": "新規ラベルを作成", "start_date": "開始日", - "due_date": "期限日", - "cycle": "サイクル", + "end_date": "終了日", + "due_date": "期限", "estimate": "見積もり", - "change_parent_issue": "親問題を変更", - "remove_parent_issue": "親問題を削除", - "add_parent": "親問題を追加", - "loading_members": "メンバーを読み込んでいます...", - "inbox": "受信箱" + "change_parent_issue": "親作業項目を変更", + "remove_parent_issue": "親作業項目を削除", + "add_parent": "親を追加", + "loading_members": "メンバーを読み込み中", + "view_link_copied_to_clipboard": "ビューリンクがクリップボードにコピーされました。", + "required": "必須", + "optional": "任意", + "Cancel": "キャンセル", + "edit": "編集", + "archive": "アーカイブ", + "restor": "復元", + "open_in_new_tab": "新しいタブで開く", + "delete": "削除", + "deleting": "削除中", + "make_a_copy": "コピーを作成", + "move_to_project": "プロジェクトに移動", + "good": "おはよう", + "morning": "ございます", + "afternoon": "こんにちは", + "evening": "こんばんは", + "show_all": "すべて表示", + "show_less": "表示を減らす", + "no_data_yet": "まだデータがありません", + "syncing": "同期中", + "add_work_item": "作業項目を追加", + "advanced_description_placeholder": "コマンドには '/' を押してください", + "create_work_item": "作業項目を作成", + "attachments": "添付ファイル", + "declining": "辞退中", + "declined": "辞退済み", + "decline": "辞退", + "unassigned": "未割り当て", + "work_items": "作業項目", + "add_link": "リンクを追加", + "points": "ポイント", + "no_assignee": "担当者なし", + "no_assignees_yet": "まだ担当者がいません", + "no_labels_yet": "まだラベルがありません", + "ideal": "理想", + "current": "現在", + "no_matching_members": "一致するメンバーがいません", + "leaving": "退出中", + "removing": "削除中", + "leave": "退出", + "refresh": "更新", + "refreshing": "更新中", + "refresh_status": "状態を更新", + "prev": "前へ", + "next": "次へ", + "re_generating": "再生成中", + "re_generate": "再生成", + "re_generate_key": "キーを再生成", + "export": "エクスポート", + "member": "{count, plural, other{# メンバー}}", + + "project_view": { + "sort_by": { + "created_at": "作成日時", + "updated_at": "更新日時", + "name": "名前" + } + }, + + "toast": { + "success": "成功!", + "error": "エラー!" + }, + + "links": { + "toasts": { + "created": { + "title": "リンクが作成されました", + "message": "リンクが正常に作成されました" + }, + "not_created": { + "title": "リンクが作成されませんでした", + "message": "リンクを作成できませんでした" + }, + "updated": { + "title": "リンクが更新されました", + "message": "リンクが正常に更新されました" + }, + "not_updated": { + "title": "リンクが更新されませんでした", + "message": "リンクを更新できませんでした" + }, + "removed": { + "title": "リンクが削除されました", + "message": "リンクが正常に削除されました" + }, + "not_removed": { + "title": "リンクが削除されませんでした", + "message": "リンクを削除できませんでした" + } + } + }, + + "home": { + "empty": { + "create_project": { + "title": "プロジェクトを作成", + "description": "Planeのほとんどはプロジェクトから始まります。", + "cta": "始める" + }, + "invite_team": { + "title": "チームを招待", + "description": "同僚と一緒に構築、デプロイ、管理しましょう。", + "cta": "招待する" + }, + "configure_workspace": { + "title": "ワークスペースを設定する。", + "description": "機能のオン/オフを切り替えたり、さらに詳細な設定を行ったりできます。", + "cta": "このワークスペースを設定" + }, + "personalize_account": { + "title": "Planeをあなた好みにカスタマイズ。", + "description": "プロフィール画像、カラー、その他の設定を選択してください。", + "cta": "今すぐパーソナライズ" + }, + "widgets": { + "title": "ウィジェットがないと静かですね、オンにしましょう", + "description": "すべてのウィジェットがオフになっているようです。体験を向上させるために\n今すぐ有効にしましょう!", + "primary_button": { + "text": "ウィジェットを管理" + } + } + }, + "quick_links": { + "empty": "手元に置いておきたい作業関連のリンクを保存してください。", + "add": "クイックリンクを追加", + "title": "クイックリンク", + "title_plural": "クイックリンク" + }, + "recents": { + "title": "最近", + "empty": { + "project": "プロジェクトを訪問すると、最近のプロジェクトがここに表示されます。", + "page": "ページを訪問すると、最近のページがここに表示されます。", + "issue": "作業項目を訪問すると、最近の作業項目がここに表示されます。", + "default": "まだ最近の項目がありません。" + }, + "filters": { + "all": "すべての項目", + "projects": "プロジェクト", + "pages": "ページ", + "issues": "作業項目" + } + }, + "new_at_plane": { + "title": "Planeの新機能" + }, + "quick_tutorial": { + "title": "クイックチュートリアル" + }, + "widget": { + "reordered_successfully": "ウィジェットの並び替えが完了しました。", + "reordering_failed": "ウィジェットの並び替え中にエラーが発生しました。" + }, + "manage_widgets": "ウィジェットを管理", + "title": "ホーム", + "star_us_on_github": "GitHubでスターをつける" + }, + + "link": { + "modal": { + "url": { + "text": "URL", + "required": "URLが無効です", + "placeholder": "URLを入力または貼り付け" + }, + "title": { + "text": "表示タイトル", + "placeholder": "このリンクをどのように表示したいか" + } + } + }, + + "common": { + "all": "すべて", + "states": "ステータス", + "state": "ステータス", + "state_groups": "ステータスグループ", + "priority": "優先度", + "team_project": "チームプロジェクト", + "project": "プロジェクト", + "cycle": "サイクル", + "cycles": "サイクル", + "module": "モジュール", + "modules": "モジュール", + "labels": "ラベル", + "assignees": "担当者", + "assignee": "担当者", + "created_by": "作成者", + "none": "なし", + "link": "リンク", + "estimate": "見積もり", + "layout": "レイアウト", + "filters": "フィルター", + "display": "表示", + "load_more": "もっと読み込む", + "activity": "アクティビティ", + "analytics": "アナリティクス", + "dates": "日付", + "success": "成功!", + "something_went_wrong": "問題が発生しました", + "error": { + "label": "エラー!", + "message": "エラーが発生しました。もう一度お試しください。" + }, + "group_by": "グループ化", + "epic": "エピック", + "epics": "エピック", + "work_item": "作業項目", + "sub_work_item": "サブ作業項目", + "add": "追加", + "warning": "警告", + "updating": "更新中", + "adding": "追加中", + "update": "更新", + "creating": "作成中", + "create": "作成", + "cancel": "キャンセル", + "description": "説明", + "title": "タイトル", + "attachment": "添付ファイル", + "general": "一般", + "features": "機能", + "automation": "自動化", + "project_name": "プロジェクト名", + "project_id": "プロジェクトID", + "project_timezone": "プロジェクトのタイムゾーン", + "created_on": "作成日", + "update_project": "プロジェクトを更新", + "identifier_already_exists": "識別子は既に存在します", + "add_more": "さらに追加", + "defaults": "デフォルト", + "add_label": "ラベルを追加", + "estimates": "見積もり", + "customize_time_range": "期間をカスタマイズ", + "loading": "読み込み中", + "attachments": "添付ファイル", + "properties": "プロパティ", + "parent": "親", + "remove": "削除", + "archiving": "アーカイブ中", + "archive": "アーカイブ", + "access": { + "public": "公開", + "private": "非公開" + }, + "done": "完了", + "sub_work_items": "サブ作業項目", + "comment": "コメント", + "workspace_level": "ワークスペースレベル", + "order_by": { + "label": "並び順", + "manual": "手動", + "last_created": "最終作成日", + "last_updated": "最終更新日", + "start_date": "開始日", + "due_date": "期限日", + "asc": "昇順", + "desc": "降順", + "updated_on": "更新日" + }, + "sort": { + "asc": "昇順", + "desc": "降順", + "created_on": "作成日", + "updated_on": "更新日" + }, + "comments": "コメント", + "updates": "更新", + "clear_all": "すべてクリア", + "copied": "コピーしました!", + "link_copied": "リンクをコピーしました!", + "link_copied_to_clipboard": "リンクをクリップボードにコピーしました", + "copied_to_clipboard": "作業項目のリンクをクリップボードにコピーしました", + "is_copied_to_clipboard": "作業項目をクリップボードにコピーしました", + "no_links_added_yet": "リンクはまだ追加されていません", + "add_link": "リンクを追加", + "links": "リンク", + "go_to_workspace": "ワークスペースへ移動", + "progress": "進捗", + "optional": "任意", + "join": "参加", + "go_back": "戻る", + "continue": "続ける", + "resend": "再送信", + "relations": "関連", + "errors": { + "default": { + "title": "エラー!", + "message": "問題が発生しました。もう一度お試しください。" + }, + "required": "この項目は必須です", + "entity_required": "{entity}は必須です" + }, + "update_link": "リンクを更新", + "attach": "添付", + "create_new": "新規作成", + "add_existing": "既存を追加", + "type_or_paste_a_url": "URLを入力または貼り付け", + "url_is_invalid": "URLが無効です", + "display_title": "表示タイトル", + "link_title_placeholder": "このリンクをどのように表示したいか", + "url": "URL", + "side_peek": "サイドピーク", + "modal": "モーダル", + "full_screen": "全画面", + "close_peek_view": "ピークビューを閉じる", + "toggle_peek_view_layout": "ピークビューのレイアウトを切り替え", + "options": "オプション", + "duration": "期間", + "today": "今日", + "week": "週", + "month": "月", + "quarter": "四半期", + "press_for_commands": "コマンドは「/」を押してください", + "click_to_add_description": "クリックして説明を追加", + "search": { + "label": "検索", + "placeholder": "検索するキーワードを入力", + "no_matches_found": "一致する結果が見つかりません", + "no_matching_results": "一致する結果がありません" + }, + "actions": { + "edit": "編集", + "make_a_copy": "コピーを作成", + "open_in_new_tab": "新しいタブで開く", + "copy_link": "リンクをコピー", + "archive": "アーカイブ", + "delete": "削除", + "remove_relation": "関連を削除", + "subscribe": "購読", + "unsubscribe": "購読解除", + "clear_sorting": "並び替えをクリア", + "show_weekends": "週末を表示", + "enable": "有効化", + "disable": "無効化" + }, + "name": "名前", + "discard": "破棄", + "confirm": "確認", + "confirming": "確認中", + "read_the_docs": "ドキュメントを読む", + "default": "デフォルト", + "active": "アクティブ", + "enabled": "有効", + "disabled": "無効", + "mandate": "必須", + "mandatory": "必須", + "yes": "はい", + "no": "いいえ", + "please_wait": "お待ちください", + "enabling": "有効化中", + "disabling": "無効化中", + "beta": "ベータ", + "or": "または", + "next": "次へ", + "back": "戻る", + "cancelling": "キャンセル中", + "configuring": "設定中", + "clear": "クリア", + "import": "インポート", + "connect": "接続", + "authorizing": "認証中", + "processing": "処理中", + "no_data_available": "データがありません", + "from": "{name}から", + "authenticated": "認証済み", + "select": "選択", + "upgrade": "アップグレード", + "add_seats": "シートを追加", + "label": "ラベル", + "priorities": "優先度", + "projects": "プロジェクト", + "workspace": "ワークスペース", + "workspaces": "ワークスペース", + "team": "チーム", + "teams": "チーム", + "entity": "エンティティ", + "entities": "エンティティ", + "task": "タスク", + "tasks": "タスク", + "section": "セクション", + "sections": "セクション", + "edit": "編集", + "connecting": "接続中", + "connected": "接続済み", + "disconnect": "切断", + "disconnecting": "切断中", + "installing": "インストール中", + "install": "インストール" + }, + + "form": { + "title": { + "required": "タイトルは必須です", + "max_length": "タイトルは{length}文字未満である必要があります" + } + }, + + "entity": { + "grouping_title": "{entity}のグループ化", + "priority": "{entity}の優先度", + "all": "すべての{entity}", + "drop_here_to_move": "ここにドロップして{entity}を移動", + "delete": { + "label": "{entity}を削除", + "success": "{entity}を削除しました", + "failed": "{entity}の削除に失敗しました" + }, + "update": { + "failed": "{entity}の更新に失敗しました", + "success": "{entity}を更新しました" + }, + "link_copied_to_clipboard": "{entity}のリンクをクリップボードにコピーしました", + "fetch": { + "failed": "{entity}の取得中にエラーが発生しました" + }, + "add": { + "success": "{entity}を追加しました", + "failed": "{entity}の追加中にエラーが発生しました" + } + }, + + "epic": { + "all": "すべてのエピック", + "label": "{count, plural, one {エピック} other {エピック}}", + "new": "新規エピック", + "adding": "エピックを追加中", + "create": { + "success": "エピックを作成しました" + }, + "add": { + "press_enter": "Enterを押して別のエピックを追加", + "label": "エピックを追加" + }, + "title": { + "label": "エピックのタイトル", + "required": "エピックのタイトルは必須です。" + } + }, + + "issue": { + "label": "{count, plural, one {作業項目} other {作業項目}}", + "all": "すべての作業項目", + "edit": "作業項目を編集", + "title": { + "label": "作業項目のタイトル", + "required": "作業項目のタイトルは必須です。" + }, + "add": { + "press_enter": "Enterを押して別の作業項目を追加", + "label": "作業項目を追加", + "cycle": { + "failed": "作業項目をサイクルに追加できませんでした。もう一度お試しください。", + "success": "{count, plural, one {作業項目} other {作業項目}}をサイクルに追加しました。", + "loading": "{count, plural, one {作業項目} other {作業項目}}をサイクルに追加中" + }, + "assignee": "担当者を追加", + "start_date": "開始日を追加", + "due_date": "期限日を追加", + "parent": "親作業項目を追加", + "sub_issue": "サブ作業項目を追加", + "relation": "関連を追加", + "link": "リンクを追加", + "existing": "既存の作業項目を追加" + }, + "remove": { + "label": "作業項目を削除", + "cycle": { + "loading": "サイクルから作業項目を削除中", + "success": "作業項目をサイクルから削除しました。", + "failed": "作業項目をサイクルから削除できませんでした。もう一度お試しください。" + }, + "module": { + "loading": "モジュールから作業項目を削除中", + "success": "作業項目をモジュールから削除しました。", + "failed": "作業項目をモジュールから削除できませんでした。もう一度お試しください。" + }, + "parent": { + "label": "親作業項目を削除" + } + }, + "new": "新規作業項目", + "adding": "作業項目を追加中", + "create": { + "success": "作業項目を作成しました" + }, + "priority": { + "urgent": "緊急", + "high": "高", + "medium": "中", + "low": "低" + }, + "display": { + "properties": { + "label": "表示プロパティ", + "id": "ID", + "issue_type": "作業項目タイプ", + "sub_issue_count": "サブ作業項目数", + "attachment_count": "添付ファイル数", + "created_on": "作成日", + "sub_issue": "サブ作業項目" + }, + "extra": { + "show_sub_issues": "サブ作業項目を表示", + "show_empty_groups": "空のグループを表示" + } + }, + "layouts": { + "ordered_by_label": "このレイアウトは次の順序で並べ替えられています:", + "list": "リスト", + "kanban": "ボード", + "calendar": "カレンダー", + "spreadsheet": "テーブル", + "gantt": "タイムライン", + "title": { + "list": "リストレイアウト", + "kanban": "ボードレイアウト", + "calendar": "カレンダーレイアウト", + "spreadsheet": "テーブルレイアウト", + "gantt": "タイムラインレイアウト" + } + }, + "states": { + "active": "アクティブ", + "backlog": "バックログ" + }, + "comments": { + "placeholder": "コメントを追加", + "switch": { + "private": "プライベートコメントに切り替え", + "public": "公開コメントに切り替え" + }, + "create": { + "success": "コメントを作成しました", + "error": "コメントの作成に失敗しました。後でもう一度お試しください。" + }, + "update": { + "success": "コメントを更新しました", + "error": "コメントの更新に失敗しました。後でもう一度お試しください。" + }, + "remove": { + "success": "コメントを削除しました", + "error": "コメントの削除に失敗しました。後でもう一度お試しください。" + }, + "upload": { + "error": "アセットのアップロードに失敗しました。後でもう一度お試しください。" + } + }, + "empty_state": { + "issue_detail": { + "title": "作業項目が存在しません", + "description": "お探しの作業項目は存在しないか、アーカイブされているか、削除されています。", + "primary_button": { + "text": "他の作業項目を表示" + } + } + }, + "sibling": { + "label": "兄弟作業項目" + }, + "archive": { + "description": "完了またはキャンセルされた\n作業項目のみアーカイブできます", + "label": "作業項目をアーカイブ", + "confirm_message": "作業項目をアーカイブしてもよろしいですか?アーカイブされた作業項目は後で復元できます。", + "success": { + "label": "アーカイブ成功", + "message": "アーカイブはプロジェクトのアーカイブで確認できます。" + }, + "failed": { + "message": "作業項目をアーカイブできませんでした。もう一度お試しください。" + } + }, + "restore": { + "success": { + "title": "復元成功", + "message": "作業項目はプロジェクトの作業項目で確認できます。" + }, + "failed": { + "message": "作業項目を復元できませんでした。もう一度お試しください。" + } + }, + "relation": { + "relates_to": "関連する", + "duplicate": "重複する", + "blocked_by": "ブロックされている", + "blocking": "ブロックしている" + }, + "copy_link": "作業項目のリンクをコピー", + "delete": { + "label": "作業項目を削除", + "error": "作業項目の削除中にエラーが発生しました" + }, + "subscription": { + "actions": { + "subscribed": "作業項目を購読しました", + "unsubscribed": "作業項目の購読を解除しました" + } + }, + "select": { + "error": "少なくとも1つの作業項目を選択してください", + "empty": "作業項目が選択されていません", + "add_selected": "選択した作業項目を追加" + }, + "open_in_full_screen": "作業項目をフルスクリーンで開く" + }, + + "attachment": { + "error": "ファイルを添付できませんでした。もう一度アップロードしてください。", + "only_one_file_allowed": "一度にアップロードできるファイルは1つだけです。", + "file_size_limit": "ファイルサイズは{size}MB以下である必要があります。", + "drag_and_drop": "どこにでもドラッグ&ドロップでアップロード", + "delete": "添付ファイルを削除" + }, + + "label": { + "select": "ラベルを選択", + "create": { + "success": "ラベルを作成しました", + "failed": "ラベルの作成に失敗しました", + "already_exists": "ラベルは既に存在します", + "type": "新しいラベルを追加するには入力してください" + } + }, + + "sub_work_item": { + "update": { + "success": "サブ作業項目を更新しました", + "error": "サブ作業項目の更新中にエラーが発生しました" + }, + "remove": { + "success": "サブ作業項目を削除しました", + "error": "サブ作業項目の削除中にエラーが発生しました" + } + }, + + "view": { + "label": "{count, plural, one {ビュー} other {ビュー}}", + "create": { + "label": "ビューを作成" + }, + "update": { + "label": "ビューを更新" + } + }, + + "inbox_issue": { + "status": { + "pending": { + "title": "保留中", + "description": "保留中" + }, + "declined": { + "title": "却下", + "description": "却下" + }, + "snoozed": { + "title": "スヌーズ", + "description": "残り{days, plural, one{# 日} other{# 日}}" + }, + "accepted": { + "title": "承認済み", + "description": "承認済み" + }, + "duplicate": { + "title": "重複", + "description": "重複" + } + }, + "modals": { + "decline": { + "title": "作業項目を却下", + "content": "作業項目{value}を却下してもよろしいですか?" + }, + "delete": { + "title": "作業項目を削除", + "content": "作業項目{value}を削除してもよろしいですか?", + "success": "作業項目を削除しました" + } + }, + "errors": { + "snooze_permission": "プロジェクト管理者のみが作業項目をスヌーズ/スヌーズ解除できます", + "accept_permission": "プロジェクト管理者のみが作業項目を承認できます", + "decline_permission": "プロジェクト管理者のみが作業項目を却下できます" + }, + "actions": { + "accept": "承認", + "decline": "却下", + "snooze": "スヌーズ", + "unsnooze": "スヌーズ解除", + "copy": "作業項目のリンクをコピー", + "delete": "削除", + "open": "作業項目を開く", + "mark_as_duplicate": "重複としてマーク", + "move": "{value}をプロジェクトの作業項目に移動" + }, + "source": { + "in-app": "アプリ内" + }, + "order_by": { + "created_at": "作成日", + "updated_at": "更新日", + "id": "ID" + }, + "label": "インテーク", + "page_label": "{workspace} - インテーク", + "modal": { + "title": "インテーク作業項目を作成" + }, + "tabs": { + "open": "オープン", + "closed": "クローズ" + }, + "empty_state": { + "sidebar_open_tab": { + "title": "オープンな作業項目がありません", + "description": "オープンな作業項目はここで見つかります。新しい作業項目を作成してください。" + }, + "sidebar_closed_tab": { + "title": "クローズされた作業項目がありません", + "description": "承認または却下されたすべての作業項目はここで見つかります。" + }, + "sidebar_filter": { + "title": "一致する作業項目がありません", + "description": "インテークに適用されたフィルターに一致する作業項目がありません。新しい作業項目を作成してください。" + }, + "detail": { + "title": "詳細を表示する作業項目を選択してください。" + } + } + }, + + "workspace_creation": { + "heading": "ワークスペースを作成", + "subheading": "Planeを使用するには、ワークスペースを作成するか参加する必要があります。", + "form": { + "name": { + "label": "ワークスペース名を設定", + "placeholder": "馴染みがあり認識しやすい名前が最適です。" + }, + "url": { + "label": "ワークスペースのURLを設定", + "placeholder": "URLを入力または貼り付け", + "edit_slug": "URLのスラッグのみ編集可能です" + }, + "organization_size": { + "label": "このワークスペースを何人で使用しますか?", + "placeholder": "範囲を選択" + } + }, + "errors": { + "creation_disabled": { + "title": "インスタンス管理者のみがワークスペースを作成できます", + "description": "インスタンス管理者のメールアドレスをご存知の場合は、下のボタンをクリックして連絡を取ってください。", + "request_button": "インスタンス管理者にリクエスト" + }, + "validation": { + "name_alphanumeric": "ワークスペース名には (' '), ('-'), ('_') と英数字のみ使用できます。", + "name_length": "名前は80文字以内にしてください。", + "url_alphanumeric": "URLには ('-') と英数字のみ使用できます。", + "url_length": "URLは48文字以内にしてください。", + "url_already_taken": "ワークスペースのURLは既に使用されています!" + } + }, + "request_email": { + "subject": "新規ワークスペースのリクエスト", + "body": "インスタンス管理者様\n\n[ワークスペース作成の目的]のために、URL [/workspace-name] の新規ワークスペースを作成していただけますでしょうか。\n\nよろしくお願いいたします。\n{firstName} {lastName}\n{email}" + }, + "button": { + "default": "ワークスペースを作成", + "loading": "ワークスペースを作成中" + }, + "toast": { + "success": { + "title": "成功", + "message": "ワークスペースが正常に作成されました" + }, + "error": { + "title": "エラー", + "message": "ワークスペースを作成できませんでした。もう一度お試しください。" + } + } + }, + + "workspace_dashboard": { + "empty_state": { + "general": { + "title": "プロジェクト、アクティビティ、メトリクスの概要", + "description": "Planeへようこそ。ご利用いただき嬉しく思います。最初のプロジェクトを作成して作業項目を追跡すると、このページは進捗を把握するのに役立つスペースに変わります。管理者はチームの進捗に役立つ項目も表示されます。", + "primary_button": { + "text": "最初のプロジェクトを作成", + "comic": { + "title": "Planeではすべてがプロジェクトから始まります", + "description": "プロジェクトは製品のロードマップ、マーケティングキャンペーン、新車の発売などになります。" + } + } + } + } + }, + + "workspace_analytics": { + "label": "アナリティクス", + "page_label": "{workspace} - アナリティクス", + "open_tasks": "オープンタスクの合計", + "error": "データの取得中にエラーが発生しました。", + "work_items_closed_in": "クローズされた作業項目", + "selected_projects": "選択されたプロジェクト", + "total_members": "メンバー総数", + "total_cycles": "サイクル総数", + "total_modules": "モジュール総数", + "pending_work_items": { + "title": "保留中の作業項目", + "empty_state": "同僚による保留中の作業項目の分析がここに表示されます。" + }, + "work_items_closed_in_a_year": { + "title": "1年間でクローズされた作業項目", + "empty_state": "作業項目をクローズすると、グラフ形式で分析が表示されます。" + }, + "most_work_items_created": { + "title": "作成された作業項目が最も多い", + "empty_state": "同僚と作成した作業項目の数がここに表示されます。" + }, + "most_work_items_closed": { + "title": "クローズされた作業項目が最も多い", + "empty_state": "同僚とクローズした作業項目の数がここに表示されます。" + }, + "tabs": { + "scope_and_demand": "スコープと需要", + "custom": "カスタムアナリティクス" + }, + "empty_state": { + "general": { + "title": "進捗、ワークロード、割り当てを追跡。傾向を把握し、ブロッカーを解消して、作業をより速く進めましょう", + "description": "スコープと需要、見積もり、スコープクリープを確認できます。チームメンバーとチームのパフォーマンスを把握し、プロジェクトが予定通りに進むようにします。", + "primary_button": { + "text": "最初のプロジェクトを開始", + "comic": { + "title": "アナリティクスはサイクル + モジュールで最も効果を発揮", + "description": "まず、作業項目をサイクルでタイムボックス化し、可能であれば、複数のサイクルにまたがる作業項目をモジュールにグループ化します。左のナビゲーションで両方を確認してください。" + } + } + } + } + }, + + "workspace_projects": { + "label": "{count, plural, one {プロジェクト} other {プロジェクト}}", + "create": { + "label": "プロジェクトを追加" + }, + "network": { + "private": { + "title": "非公開", + "description": "招待された人のみアクセス可能" + }, + "public": { + "title": "公開", + "description": "ゲスト以外のワークスペースの全員が参加可能" + } + }, + "error": { + "permission": "この操作を実行する権限がありません。", + "cycle_delete": "サイクルの削除に失敗しました", + "module_delete": "モジュールの削除に失敗しました", + "issue_delete": "作業項目の削除に失敗しました" + }, + "state": { + "backlog": "バックログ", + "unstarted": "未開始", + "started": "開始済み", + "completed": "完了", + "cancelled": "キャンセル" + }, + "sort": { + "manual": "手動", + "name": "名前", + "created_at": "作成日", + "members_length": "メンバー数" + }, + "scope": { + "my_projects": "自分のプロジェクト", + "archived_projects": "アーカイブ済み" + }, + "common": { + "months_count": "{months, plural, one{# ヶ月} other{# ヶ月}}" + }, + "empty_state": { + "general": { + "title": "アクティブなプロジェクトがありません", + "description": "各プロジェクトは目標指向の作業の親として考えてください。プロジェクトには作業、サイクル、モジュールが含まれ、同僚と共にその目標の達成を支援します。新しいプロジェクトを作成するか、アーカイブされたプロジェクトをフィルタリングしてください。", + "primary_button": { + "text": "最初のプロジェクトを開始", + "comic": { + "title": "Planeではすべてがプロジェクトから始まります", + "description": "プロジェクトは製品のロードマップ、マーケティングキャンペーン、新車の発売などになります。" + } + } + }, + "no_projects": { + "title": "プロジェクトがありません", + "description": "作業項目を作成したり作業を管理したりするには、プロジェクトを作成するか、プロジェクトのメンバーになる必要があります。", + "primary_button": { + "text": "最初のプロジェクトを開始", + "comic": { + "title": "Planeではすべてがプロジェクトから始まります", + "description": "プロジェクトは製品のロードマップ、マーケティングキャンペーン、新車の発売などになります。" + } + } + }, + "filter": { + "title": "一致するプロジェクトがありません", + "description": "条件に一致するプロジェクトが見つかりません。\n代わりに新しいプロジェクトを作成してください。" + }, + "search": { + "description": "条件に一致するプロジェクトが見つかりません。\n代わりに新しいプロジェクトを作成してください。" + } + } + }, + + "workspace_views": { + "add_view": "ビューを追加", + "empty_state": { + "all-issues": { + "title": "プロジェクトに作業項目がありません", + "description": "最初のプロジェクトが完了しました!次は、作業を追跡可能な作業項目に分割しましょう。始めましょう!", + "primary_button": { + "text": "新しい作業項目を作成" + } + }, + "assigned": { + "title": "作業項目がまだありません", + "description": "あなたに割り当てられた作業項目をここで追跡できます。", + "primary_button": { + "text": "新しい作業項目を作成" + } + }, + "created": { + "title": "作業項目がまだありません", + "description": "あなたが作成したすべての作業項目がここに表示され、直接追跡できます。", + "primary_button": { + "text": "新しい作業項目を作成" + } + }, + "subscribed": { + "title": "作業項目がまだありません", + "description": "興味のある作業項目を購読して、ここですべてを追跡できます。" + }, + "custom-view": { + "title": "作業項目がまだありません", + "description": "フィルターに該当する作業項目をここで追跡できます。" + } + } + }, + + "workspace_settings": { + "label": "ワークスペース設定", + "page_label": "{workspace} - 一般設定", + "key_created": "キーが作成されました", + "copy_key": "このシークレットキーをコピーしてPlaneページに保存してください。閉じた後はこのキーを見ることができません。キーを含むCSVファイルがダウンロードされました。", + "token_copied": "トークンがクリップボードにコピーされました。", + "settings": { + "general": { + "title": "一般", + "upload_logo": "ロゴをアップロード", + "edit_logo": "ロゴを編集", + "name": "ワークスペース名", + "company_size": "会社の規模", + "url": "ワークスペースURL", + "update_workspace": "ワークスペースを更新", + "delete_workspace": "ワークスペースを削除", + "delete_workspace_description": "ワークスペースを削除すると、そのワークスペース内のすべてのデータとリソースが永久に削除され、復元できなくなります。", + "delete_btn": "ワークスペースを削除", + "errors": { + "name": { + "required": "名前は必須です", + "max_length": "ワークスペース名は80文字を超えることはできません" + }, + "company_size": { + "required": "会社の規模は必須です" + } + } + }, + "members": { + "title": "メンバー", + "add_member": "メンバーを追加", + "invitations_sent_successfully": "招待が正常に送信されました", + "leave_confirmation": "ワークスペースから退出してもよろしいですか?このワークスペースにアクセスできなくなります。この操作は取り消せません。", + "details": { + "full_name": "フルネーム", + "display_name": "表示名", + "email_address": "メールアドレス", + "account_type": "アカウントタイプ", + "authentication": "認証", + "joining_date": "参加日" + }, + "modal": { + "title": "共同作業者を招待", + "description": "ワークスペースに共同作業者を招待します。", + "button": "招待を送信", + "button_loading": "招待を送信中", + "placeholder": "name@company.com", + "errors": { + "required": "招待するにはメールアドレスが必要です。", + "invalid": "メールアドレスが無効です" + } + } + }, + "billing_and_plans": { + "title": "請求とプラン", + "current_plan": "現在のプラン", + "free_plan": "現在フリープランを使用中です", + "view_plans": "プランを表示" + }, + "exports": { + "title": "エクスポート", + "exporting": "エクスポート中", + "previous_exports": "過去のエクスポート", + "export_separate_files": "データを個別のファイルにエクスポート", + "modal": { + "title": "エクスポート先", + "toasts": { + "success": { + "title": "エクスポート成功", + "message": "エクスポートした{entity}は過去のエクスポートからダウンロードできます。" + }, + "error": { + "title": "エクスポート失敗", + "message": "エクスポートに失敗しました。もう一度お試しください。" + } + } + } + }, + "webhooks": { + "title": "Webhook", + "add_webhook": "Webhookを追加", + "modal": { + "title": "Webhookを作成", + "details": "Webhook詳細", + "payload": "ペイロードURL", + "question": "このWebhookをトリガーするイベントを選択してください", + "error": "URLは必須です" + }, + "secret_key": { + "title": "シークレットキー", + "message": "Webhookペイロードにサインインするためのトークンを生成" + }, + "options": { + "all": "すべてを送信", + "individual": "個別のイベントを選択" + }, + "toasts": { + "created": { + "title": "Webhook作成完了", + "message": "Webhookが正常に作成されました" + }, + "not_created": { + "title": "Webhook作成失敗", + "message": "Webhookを作成できませんでした" + }, + "updated": { + "title": "Webhook更新完了", + "message": "Webhookが正常に更新されました" + }, + "not_updated": { + "title": "Webhook更新失敗", + "message": "Webhookを更新できませんでした" + }, + "removed": { + "title": "Webhook削除完了", + "message": "Webhookが正常に削除されました" + }, + "not_removed": { + "title": "Webhook削除失敗", + "message": "Webhookを削除できませんでした" + }, + "secret_key_copied": { + "message": "シークレットキーがクリップボードにコピーされました。" + }, + "secret_key_not_copied": { + "message": "シークレットキーのコピー中にエラーが発生しました。" + } + } + }, + "api_tokens": { + "title": "APIトークン", + "add_token": "APIトークンを追加", + "create_token": "トークンを作成", + "never_expires": "無期限", + "generate_token": "トークンを生成", + "generating": "生成中", + "delete": { + "title": "APIトークンを削除", + "description": "このトークンを使用しているアプリケーションはPlaneのデータにアクセスできなくなります。この操作は取り消せません。", + "success": { + "title": "成功!", + "message": "APIトークンが正常に削除されました" + }, + "error": { + "title": "エラー!", + "message": "APIトークンを削除できませんでした" + } + } + } + }, + "empty_state": { + "api_tokens": { + "title": "APIトークンがまだ作成されていません", + "description": "PlaneのAPIを使用して、Planeのデータを外部システムと統合できます。トークンを作成して始めましょう。" + }, + "webhooks": { + "title": "Webhookが追加されていません", + "description": "Webhookを作成してリアルタイムの更新を受け取り、アクションを自動化します。" + }, + "exports": { + "title": "エクスポートがまだありません", + "description": "エクスポートすると、参照用のコピーがここに保存されます。" + }, + "imports": { + "title": "インポートがまだありません", + "description": "過去のインポートをここで確認し、ダウンロードできます。" + } + } + }, + + "profile": { + "label": "プロフィール", + "page_label": "あなたの作業", + "work": "作業", + "details": { + "joined_on": "参加日", + "time_zone": "タイムゾーン" + }, + "stats": { + "workload": "作業負荷", + "overview": "概要", + "created": "作成した作業項目", + "assigned": "割り当てられた作業項目", + "subscribed": "購読中の作業項目", + "state_distribution": { + "title": "状態別作業項目", + "empty": "より良い分析のために、作業項目を作成してグラフで状態別に表示します。" + }, + "priority_distribution": { + "title": "優先度別作業項目", + "empty": "より良い分析のために、作業項目を作成してグラフで優先度別に表示します。" + }, + "recent_activity": { + "title": "最近のアクティビティ", + "empty": "データが見つかりませんでした。入力内容を確認してください", + "button": "今日のアクティビティをダウンロード", + "button_loading": "ダウンロード中" + } + }, + "actions": { + "profile": "プロフィール", + "security": "セキュリティ", + "activity": "アクティビティ", + "appearance": "外観", + "notifications": "通知" + }, + "tabs": { + "summary": "サマリー", + "assigned": "割り当て済み", + "created": "作成済み", + "subscribed": "購読中", + "activity": "アクティビティ" + }, + "empty_state": { + "activity": { + "title": "アクティビティがまだありません", + "description": "新しい作業項目を作成して始めましょう!詳細とプロパティを追加してください。Planeをさらに探索してアクティビティを確認しましょう。" + }, + "assigned": { + "title": "割り当てられた作業項目がありません", + "description": "あなたに割り当てられた作業項目をここで追跡できます。" + }, + "created": { + "title": "作業項目がまだありません", + "description": "あなたが作成したすべての作業項目がここに表示され、直接追跡できます。" + }, + "subscribed": { + "title": "作業項目がまだありません", + "description": "興味のある作業項目を購読して、ここですべてを追跡できます。" + } + } + }, + + "project_settings": { + "general": { + "enter_project_id": "プロジェクトIDを入力", + "please_select_a_timezone": "タイムゾーンを選択してください", + "archive_project": { + "title": "プロジェクトをアーカイブ", + "description": "プロジェクトをアーカイブすると、サイドナビゲーションから非表示になりますが、プロジェクトページからアクセスすることはできます。プロジェクトを復元または削除することもできます。", + "button": "プロジェクトをアーカイブ" + }, + "delete_project": { + "title": "プロジェクトを削除", + "description": "プロジェクトを削除すると、そのプロジェクト内のすべてのデータとリソースが永久に削除され、復元できなくなります。", + "button": "プロジェクトを削除" + }, + "toast": { + "success": "プロジェクトが正常に更新されました", + "error": "プロジェクトを更新できませんでした。もう一度お試しください。" + } + }, + "members": { + "label": "メンバー", + "project_lead": "プロジェクトリーダー", + "default_assignee": "デフォルトの担当者", + "guest_super_permissions": { + "title": "ゲストユーザーにすべての作業項目の閲覧権限を付与:", + "sub_heading": "これにより、ゲストはプロジェクトのすべての作業項目を閲覧できるようになります。" + }, + "invite_members": { + "title": "メンバーを招待", + "sub_heading": "プロジェクトに参加するメンバーを招待します。", + "select_co_worker": "共同作業者を選択" + } + }, + "states": { + "describe_this_state_for_your_members": "このステータスについてメンバーに説明してください。" + }, + "labels": { + "label_title": "ラベルタイトル", + "label_title_is_required": "ラベルタイトルは必須です", + "label_max_char": "ラベル名は255文字を超えることはできません", + "toast": { + "error": "ラベルの更新中にエラーが発生しました" + } + }, + "estimates": { + "title": "プロジェクトの見積もりを有効にする", + "description": "チームの複雑さと作業負荷を伝えるのに役立ちます。" + }, + "automations": { + "label": "自動化", + "auto-archive": { + "title": "完了した作業項目を自動アーカイブ", + "description": "Planeは完了またはキャンセルされた作業項目を自動的にアーカイブします。", + "duration": "次の期間完了している作業項目を自動アーカイブ" + }, + "auto-close": { + "title": "作業項目を自動クローズ", + "description": "Planeは完了またはキャンセルされていない作業項目を自動的にクローズします。", + "duration": "次の期間非アクティブな作業項目を自動クローズ", + "auto_close_status": "自動クローズステータス" + } + }, + + "empty_state": { + "labels": { + "title": "ラベルがまだありません", + "description": "プロジェクトの作業項目を整理してフィルタリングするためのラベルを作成します。" + }, + "estimates": { + "title": "見積もりシステムがまだありません", + "description": "作業項目ごとの作業量を伝えるための見積もりセットを作成します。", + "primary_button": "見積もりシステムを追加" + } + } + }, + + "project_cycles": { + "add_cycle": "サイクルを追加", + "more_details": "詳細情報", + "cycle": "サイクル", + "update_cycle": "サイクルを更新", + "create_cycle": "サイクルを作成", + "no_matching_cycles": "一致するサイクルがありません", + "remove_filters_to_see_all_cycles": "すべてのサイクルを表示するにはフィルターを解除してください", + "remove_search_criteria_to_see_all_cycles": "すべてのサイクルを表示するには検索条件を解除してください", + "only_completed_cycles_can_be_archived": "完了したサイクルのみアーカイブできます", + "active_cycle": { + "label": "アクティブなサイクル", + "progress": "進捗", + "chart": "バーンダウンチャート", + "priority_issue": "優先作業項目", + "assignees": "担当者", + "issue_burndown": "作業項目バーンダウン", + "ideal": "理想", + "current": "現在", + "labels": "ラベル" + }, + "upcoming_cycle": { + "label": "今後のサイクル" + }, + "completed_cycle": { + "label": "完了したサイクル" + }, + "status": { + "days_left": "残り日数", + "completed": "完了", + "yet_to_start": "開始前", + "in_progress": "進行中", + "draft": "下書き" + }, + "action": { + "restore": { + "title": "サイクルを復元", + "success": { + "title": "サイクルが復元されました", + "description": "サイクルが復元されました。" + }, + "failed": { + "title": "サイクルの復元に失敗", + "description": "サイクルを復元できませんでした。もう一度お試しください。" + } + }, + "favorite": { + "loading": "お気に入りにサイクルを追加中", + "success": { + "description": "サイクルがお気に入りに追加されました。", + "title": "成功!" + }, + "failed": { + "description": "サイクルをお気に入りに追加できませんでした。もう一度お試しください。", + "title": "エラー!" + } + }, + "unfavorite": { + "loading": "お気に入りからサイクルを削除中", + "success": { + "description": "サイクルがお気に入りから削除されました。", + "title": "成功!" + }, + "failed": { + "description": "サイクルをお気に入りから削除できませんでした。もう一度お試しください。", + "title": "エラー!" + } + }, + "update": { + "loading": "サイクルを更新中", + "success": { + "description": "サイクルが正常に更新されました。", + "title": "成功!" + }, + "failed": { + "description": "サイクルの更新中にエラーが発生しました。もう一度お試しください。", + "title": "エラー!" + }, + "error": { + "already_exists": "指定した日付のサイクルは既に存在します。下書きサイクルを作成する場合は、両方の日付を削除してください。" + } + } + }, + "empty_state": { + "general": { + "title": "サイクルで作業をグループ化してタイムボックス化します。", + "description": "作業をタイムボックス化された単位に分割し、プロジェクトの期限から逆算して日付を設定し、チームとして具体的な進捗を作ります。", + "primary_button": { + "text": "最初のサイクルを設定", + "comic": { + "title": "サイクルは繰り返されるタイムボックスです。", + "description": "スプリント、イテレーション、または週次や隔週の作業追跡に使用するその他の用語がサイクルです。" + } + } + }, + "no_issues": { + "title": "サイクルに作業項目が追加されていません", + "description": "このサイクル内でタイムボックス化して提供したい作業項目を追加または作成します", + "primary_button": { + "text": "新しい作業項目を作成" + }, + "secondary_button": { + "text": "既存の作業項目を追加" + } + }, + "completed_no_issues": { + "title": "サイクルに作業項目がありません", + "description": "サイクルに作業項目がありません。作業項目は転送されたか非表示になっています。非表示の作業項目がある場合は、表示プロパティを更新して確認してください。" + }, + "active": { + "title": "アクティブなサイクルがありません", + "description": "アクティブなサイクルには、その期間内に今日の日付が含まれるものが該当します。アクティブなサイクルの進捗と詳細をここで確認できます。" + }, + "archived": { + "title": "アーカイブされたサイクルがまだありません", + "description": "プロジェクトを整理するために、完了したサイクルをアーカイブします。アーカイブ後はここで確認できます。" + } + } + }, + + "project_issues": { + "empty_state": { + "no_issues": { + "title": "作業項目を作成して誰かに割り当てましょう。自分自身でも構いません", + "description": "作業項目は、仕事、タスク、作業、またはJTBD(私たちが好む用語)と考えてください。作業項目とそのサブ作業項目は通常、チームメンバーに割り当てられる時間ベースのアクションアイテムです。チームは作業項目を作成、割り当て、完了することでプロジェクトの目標に向かって進みます。", + "primary_button": { + "text": "最初の作業項目を作成", + "comic": { + "title": "作業項目はPlaneの構成要素です。", + "description": "PlaneのUIの再設計、会社のリブランド、新しい燃料噴射システムの立ち上げなどは、サブ作業項目を持つ可能性が高い作業項目の例です。" + } + } + }, + "no_archived_issues": { + "title": "アーカイブされた作業項目がまだありません", + "description": "手動または自動化を通じて、完了またはキャンセルされた作業項目をアーカイブできます。アーカイブ後はここで確認できます。", + "primary_button": { + "text": "自動化を設定" + } + }, + "issues_empty_filter": { + "title": "適用されたフィルターに一致する作業項目が見つかりません", + "secondary_button": { + "text": "すべてのフィルターをクリア" + } + } + } + }, + + "project_module": { + "add_module": "モジュールを追加", + "update_module": "モジュールを更新", + "create_module": "モジュールを作成", + "archive_module": "モジュールをアーカイブ", + "restore_module": "モジュールを復元", + "delete_module": "モジュールを削除", + "empty_state": { + "general": { + "title": "プロジェクトのマイルストーンをモジュールにマッピングし、集計された作業を簡単に追跡できます。", + "description": "論理的で階層的な親に属する作業項目のグループがモジュールを形成します。プロジェクトのマイルストーンで作業を追跡する方法として考えてください。期間や期限があり、マイルストーンまでの進捗状況を確認できる分析機能も備えています。", + "primary_button": { + "text": "最初のモジュールを作成", + "comic": { + "title": "モジュールは階層的に作業をグループ化するのに役立ちます。", + "description": "カートモジュール、シャーシモジュール、倉庫モジュールは、このグループ化の良い例です。" + } + } + }, + "no_issues": { + "title": "モジュールに作業項目がありません", + "description": "このモジュールの一部として達成したい作業項目を作成または追加してください", + "primary_button": { + "text": "新しい作業項目を作成" + }, + "secondary_button": { + "text": "既存の作業項目を追加" + } + }, + "archived": { + "title": "アーカイブされたモジュールがまだありません", + "description": "プロジェクトを整理するために、完了またはキャンセルされたモジュールをアーカイブします。アーカイブ後はここで確認できます。" + }, + "sidebar": { + "in_active": "このモジュールはまだアクティブではありません。", + "invalid_date": "無効な日付です。有効な日付を入力してください。" + } + }, + "quick_actions": { + "archive_module": "モジュールをアーカイブ", + "archive_module_description": "完了またはキャンセルされた\nモジュールのみアーカイブできます。", + "delete_module": "モジュールを削除" + }, + "toast": { + "copy": { + "success": "モジュールのリンクがクリップボードにコピーされました" + }, + "delete": { + "success": "モジュールが正常に削除されました", + "error": "モジュールを削除できませんでした" + } + } + }, + + "project_views": { + "empty_state": { + "general": { + "title": "プロジェクトのフィルター付きビューを保存します。必要な数だけ作成できます", + "description": "ビューは、頻繁に使用するフィルターや簡単にアクセスしたいフィルターの集合です。プロジェクト内のすべての同僚が全員のビューを確認でき、自分のニーズに最も合うものを選択できます。", + "primary_button": { + "text": "最初のビューを作成", + "comic": { + "title": "ビューは作業項目のプロパティの上で機能します。", + "description": "ここから、必要に応じて多くのプロパティやフィルターを使用してビューを作成できます。" + } + } + }, + "filter": { + "title": "一致するビューがありません", + "description": "検索条件に一致するビューがありません。\n代わりに新しいビューを作成してください。" + } + } + }, + + "project_page": { + "empty_state": { + "general": { + "title": "メモ、ドキュメント、または完全なナレッジベースを作成しましょう。PlaneのAIアシスタントGalileoが開始をサポートします", + "description": "ページはPlaneの思考整理スペースです。会議のメモを取り、簡単に整形し、作業項目を埋め込み、コンポーネントライブラリを使用してレイアウトし、すべてをプロジェクトのコンテキストに保存できます。ドキュメントを素早く作成するには、ショートカットまたはボタンのクリックでPlaneのAI、Galileoを呼び出してください。", + "primary_button": { + "text": "最初のページを作成" + } + }, + "private": { + "title": "プライベートページがまだありません", + "description": "プライベートな考えをここに保存しましょう。共有する準備ができたら、チームはクリック一つで共有できます。", + "primary_button": { + "text": "最初のページを作成" + } + }, + "public": { + "title": "公開ページがまだありません", + "description": "プロジェクト内の全員と共有されているページをここで確認できます。", + "primary_button": { + "text": "最初のページを作成" + } + }, + "archived": { + "title": "アーカイブされたページがまだありません", + "description": "注目していないページをアーカイブします。必要な時にここでアクセスできます。" + } + } + }, + + "command_k": { + "empty_state": { + "search": { + "title": "結果が見つかりません" + } + } + }, + + "issue_relation": { + "empty_state": { + "search": { + "title": "一致する作業項目が見つかりません" + }, + "no_issues": { + "title": "作業項目が見つかりません" + } + } + }, + + "issue_comment": { + "empty_state": { + "general": { + "title": "コメントがまだありません", + "description": "コメントは作業項目のディスカッションとフォローアップのスペースとして使用できます" + } + } + }, + + "notification": { + "label": "受信トレイ", + "page_label": "{workspace} - 受信トレイ", + "options": { + "mark_all_as_read": "すべて既読にする", + "mark_read": "既読にする", + "mark_unread": "未読にする", + "refresh": "更新", + "filters": "受信トレイフィルター", + "show_unread": "未読を表示", + "show_snoozed": "スヌーズを表示", + "show_archived": "アーカイブを表示", + "mark_archive": "アーカイブ", + "mark_unarchive": "アーカイブ解除", + "mark_snooze": "スヌーズ", + "mark_unsnooze": "スヌーズ解除" + }, + "toasts": { + "read": "通知を既読にしました", + "unread": "通知を未読にしました", + "archived": "通知をアーカイブしました", + "unarchived": "通知をアーカイブ解除しました", + "snoozed": "通知をスヌーズしました", + "unsnoozed": "通知のスヌーズを解除しました" + }, + "empty_state": { + "detail": { + "title": "詳細を表示するには選択してください。" + }, + "all": { + "title": "割り当てられた作業項目がありません", + "description": "あなたに割り当てられた作業項目の更新が\nここに表示されます" + }, + "mentions": { + "title": "割り当てられた作業項目がありません", + "description": "あなたに割り当てられた作業項目の更新が\nここに表示されます" + } + }, + "tabs": { + "all": "すべて", + "mentions": "メンション" + }, + "filter": { + "assigned": "自分に割り当て", + "created": "自分が作成", + "subscribed": "自分が購読" + }, + "snooze": { + "1_day": "1日", + "3_days": "3日", + "5_days": "5日", + "1_week": "1週間", + "2_weeks": "2週間", + "custom": "カスタム" + } + }, + + "active_cycle": { + "empty_state": { + "progress": { + "title": "サイクルの進捗を表示するには作業項目を追加してください" + }, + "chart": { + "title": "バーンダウンチャートを表示するには作業項目を追加してください。" + }, + "priority_issue": { + "title": "サイクルで取り組まれている優先度の高い作業項目を一目で確認できます。" + }, + "assignee": { + "title": "担当者別の作業の内訳を確認するには、作業項目に担当者を追加してください。" + }, + "label": { + "title": "ラベル別の作業の内訳を確認するには、作業項目にラベルを追加してください。" + } + } + }, + + "disabled_project": { + "empty_state": { + "inbox": { + "title": "インテークがプロジェクトで有効になっていません。", + "description": "インテークは、プロジェクトへの受信リクエストを管理し、ワークフローに作業項目として追加するのに役立ちます。リクエストを管理するには、プロジェクト設定でインテークを有効にしてください。", + "primary_button": { + "text": "機能を管理" + } + }, + "cycle": { + "title": "サイクルがこのプロジェクトで有効になっていません。", + "description": "時間枠で作業を分割し、プロジェクトの期限から逆算して日付を設定し、チームとして具体的な進捗を作ります。サイクルを使用するには、プロジェクトでサイクル機能を有効にしてください。", + "primary_button": { + "text": "機能を管理" + } + }, + "module": { + "title": "モジュールがプロジェクトで有効になっていません。", + "description": "モジュールはプロジェクトの構成要素です。モジュールを使用するには、プロジェクト設定でモジュールを有効にしてください。", + "primary_button": { + "text": "機能を管理" + } + }, + "page": { + "title": "ページがプロジェクトで有効になっていません。", + "description": "ページはプロジェクトの構成要素です。ページを使用するには、プロジェクト設定でページを有効にしてください。", + "primary_button": { + "text": "機能を管理" + } + }, + "view": { + "title": "ビューがプロジェクトで有効になっていません。", + "description": "ビューはプロジェクトの構成要素です。ビューを使用するには、プロジェクト設定でビューを有効にしてください。", + "primary_button": { + "text": "機能を管理" + } + } + } + }, + "workspace_draft_issues": { + "draft_an_issue": "作業項目の下書き", + "empty_state": { + "title": "書きかけの作業項目、そしてまもなくコメントがここに表示されます。", + "description": "試してみるには、作業項目の追加を開始して途中で中断するか、以下で最初の下書きを作成してください。😉", + "primary_button": { + "text": "最初の下書きを作成" + } + }, + "delete_modal": { + "title": "下書きを削除", + "description": "この下書きを削除してもよろしいですか?この操作は取り消せません。" + }, + "toasts": { + "created": { + "success": "下書きを作成しました", + "error": "作業項目を作成できませんでした。もう一度お試しください。" + }, + "deleted": { + "success": "下書きを削除しました" + } + } + }, + + "stickies": { + "title": "あなたの付箋", + "placeholder": "ここをクリックして入力", + "all": "すべての付箋", + "no-data": "アイデアをメモしたり、ひらめきをキャプチャしたり、閃きを記録したりしましょう。付箋を追加して始めましょう。", + "add": "付箋を追加", + "search_placeholder": "タイトルで検索", + "delete": "付箋を削除", + "delete_confirmation": "この付箋を削除してもよろしいですか?", + "empty_state": { + "simple": "アイデアをメモしたり、ひらめきをキャプチャしたり、閃きを記録したりしましょう。付箋を追加して始めましょう。", + "general": { + "title": "付箋は、その場で素早く取るメモやToDoです。", + "description": "いつでもどこからでもアクセスできる付箋を作成して、思考やアイデアを簡単にキャプチャできます。", + "primary_button": { + "text": "付箋を追加" + } + }, + "search": { + "title": "付箋に一致するものがありません。", + "description": "別の用語を試すか、検索が正しいと\n確信がある場合はお知らせください。", + "primary_button": { + "text": "付箋を追加" + } + } + }, + "toasts": { + "errors": { + "wrong_name": "付箋の名前は100文字を超えることはできません。", + "already_exists": "説明のない付箋がすでに存在します" + }, + "created": { + "title": "付箋を作成しました", + "message": "付箋が正常に作成されました" + }, + "not_created": { + "title": "付箋を作成できませんでした", + "message": "付箋を作成できませんでした" + }, + "updated": { + "title": "付箋を更新しました", + "message": "付箋が正常に更新されました" + }, + "not_updated": { + "title": "付箋を更新できませんでした", + "message": "付箋を更新できませんでした" + }, + "removed": { + "title": "付箋を削除しました", + "message": "付箋が正常に削除されました" + }, + "not_removed": { + "title": "付箋を削除できませんでした", + "message": "付箋を削除できませんでした" + } + } + }, + + "role_details": { + "guest": { + "title": "ゲスト", + "description": "組織の外部メンバーをゲストとして招待できます。" + }, + "member": { + "title": "メンバー", + "description": "プロジェクト、サイクル、モジュール内のエンティティの読み取り、書き込み、編集、削除が可能" + }, + "admin": { + "title": "管理者", + "description": "ワークスペース内のすべての権限が有効。" + } + }, + + "user_roles": { + "product_or_project_manager": "プロダクト/プロジェクトマネージャー", + "development_or_engineering": "開発/エンジニアリング", + "founder_or_executive": "創業者/エグゼクティブ", + "freelancer_or_consultant": "フリーランス/コンサルタント", + "marketing_or_growth": "マーケティング/グロース", + "sales_or_business_development": "営業/ビジネス開発", + "support_or_operations": "サポート/オペレーション", + "student_or_professor": "学生/教授", + "human_resources": "人事", + "other": "その他" + }, + + "importer": { + "github": { + "title": "GitHub", + "description": "GitHubリポジトリから作業項目をインポートして同期します。" + }, + "jira": { + "title": "Jira", + "description": "Jiraプロジェクトとエピックから作業項目とエピックをインポートします。" + } + }, + + "exporter": { + "csv": { + "title": "CSV", + "description": "作業項目をCSVファイルにエクスポートします。", + "short_description": "CSVとしてエクスポート" + }, + "excel": { + "title": "Excel", + "description": "作業項目をExcelファイルにエクスポートします。", + "short_description": "Excelとしてエクスポート" + }, + "xlsx": { + "title": "Excel", + "description": "作業項目をExcelファイルにエクスポートします。", + "short_description": "Excelとしてエクスポート" + }, + "json": { + "title": "JSON", + "description": "作業項目をJSONファイルにエクスポートします。", + "short_description": "JSONとしてエクスポート" + } + }, + "default_global_view": { + "all_issues": "すべての作業項目", + "assigned": "割り当て済み", + "created": "作成済み", + "subscribed": "購読中" + }, + + "themes": { + "theme_options": { + "system_preference": { + "label": "システム設定" + }, + "light": { + "label": "ライト" + }, + "dark": { + "label": "ダーク" + }, + "light_contrast": { + "label": "ライトハイコントラスト" + }, + "dark_contrast": { + "label": "ダークハイコントラスト" + }, + "custom": { + "label": "カスタムテーマ" + } + } + }, + "project_modules": { + "status": { + "backlog": "バックログ", + "planned": "計画済み", + "in_progress": "進行中", + "paused": "一時停止", + "completed": "完了", + "cancelled": "キャンセル" + }, + "layout": { + "list": "リスト表示", + "board": "ギャラリー表示", + "timeline": "タイムライン表示" + }, + "order_by": { + "name": "名前", + "progress": "進捗", + "issues": "作業項目数", + "due_date": "期限", + "created_at": "作成日", + "manual": "手動" + } + }, + + "cycle": { + "label": "{count, plural, one {サイクル} other {サイクル}}", + "no_cycle": "サイクルなし" + }, + + "module": { + "label": "{count, plural, one {モジュール} other {モジュール}}", + "no_module": "モジュールなし" + } } diff --git a/packages/i18n/src/locales/zh-CN/translations.json b/packages/i18n/src/locales/zh-CN/translations.json index f88ca6b6c29..3bfe142ea88 100644 --- a/packages/i18n/src/locales/zh-CN/translations.json +++ b/packages/i18n/src/locales/zh-CN/translations.json @@ -1,4 +1,174 @@ { + "sidebar": { + "projects": "项目", + "pages": "页面", + "new_work_item": "新工作项", + "home": "主页", + "your_work": "我的工作", + "inbox": "收件箱", + "workspace": "工作区", + "views": "视图", + "analytics": "分析", + "work_items": "工作项", + "cycles": "周期", + "modules": "模块", + "intake": "收集", + "drafts": "草稿", + "favorites": "收藏", + "pro": "专业版", + "upgrade": "升级" + }, + + "auth": { + "common": { + "email": { + "label": "邮箱", + "placeholder": "name@company.com", + "errors": { + "required": "邮箱是必填项", + "invalid": "邮箱格式无效" + } + }, + "password": { + "label": "密码", + "set_password": "设置密码", + "placeholder": "输入密码", + "confirm_password": { + "label": "确认密码", + "placeholder": "确认密码" + }, + "current_password": { + "label": "当前密码" + }, + "new_password": { + "label": "新密码", + "placeholder": "输入新密码" + }, + "change_password": { + "label": { + "default": "修改密码", + "submitting": "正在修改密码" + } + }, + "errors": { + "match": "密码不匹配", + "empty": "请输入密码", + "length": "密码长度应超过8个字符", + "strength": { + "weak": "密码强度较弱", + "strong": "密码强度较强" + } + }, + "submit": "设置密码", + "toast": { + "change_password": { + "success": { + "title": "成功!", + "message": "密码修改成功。" + }, + "error": { + "title": "错误!", + "message": "出现错误。请重试。" + } + } + } + }, + "unique_code": { + "label": "唯一码", + "placeholder": "gets-sets-flys", + "paste_code": "粘贴发送到您邮箱的验证码", + "requesting_new_code": "正在请求新验证码", + "sending_code": "正在发送验证码" + }, + "already_have_an_account": "已有账号?", + "login": "登录", + "create_account": "创建账号", + "new_to_plane": "首次使用 Plane?", + "back_to_sign_in": "返回登录", + "resend_in": "{seconds} 秒后重新发送", + "sign_in_with_unique_code": "使用唯一码登录", + "forgot_password": "忘记密码?" + }, + "sign_up": { + "header": { + "label": "创建账号以开始与团队一起管理工作。", + "step": { + "email": { + "header": "注册", + "sub_header": "" + }, + "password": { + "header": "注册", + "sub_header": "使用邮箱-密码组合注册。" + }, + "unique_code": { + "header": "注册", + "sub_header": "使用发送到上述邮箱的唯一码注册。" + } + } + }, + "errors": { + "password": { + "strength": "请设置一个强密码以继续" + } + } + }, + "sign_in": { + "header": { + "label": "登录以开始与团队一起管理工作。", + "step": { + "email": { + "header": "登录或注册", + "sub_header": "" + }, + "password": { + "header": "登录或注册", + "sub_header": "使用您的邮箱-密码组合登录。" + }, + "unique_code": { + "header": "登录或注册", + "sub_header": "使用发送到上述邮箱的唯一码登录。" + } + } + } + }, + "forgot_password": { + "title": "重置密码", + "description": "输入您的用户账号已验证的邮箱地址,我们将向您发送密码重置链接。", + "email_sent": "我们已将重置链接发送到您的邮箱", + "send_reset_link": "发送重置链接", + "errors": { + "smtp_not_enabled": "我们发现您的管理员未启用 SMTP,我们将无法发送密码重置链接" + }, + "toast": { + "success": { + "title": "邮件已发送", + "message": "请查看您的收件箱以获取重置密码的链接。如果几分钟内未收到,请检查垃圾邮件文件夹。" + }, + "error": { + "title": "错误!", + "message": "出现错误。请重试。" + } + } + }, + "reset_password": { + "title": "设置新密码", + "description": "使用强密码保护您的账号" + }, + "set_password": { + "title": "保护您的账号", + "description": "设置密码有助于您安全登录" + }, + "sign_out": { + "toast": { + "error": { + "title": "错误!", + "message": "登出失败。请重试。" + } + } + } + }, + "submit": "提交", "cancel": "取消", "loading": "加载中", @@ -14,31 +184,33 @@ "description": "描述", "search": "搜索", "add_member": "添加成员", + "adding_members": "正在添加成员", "remove_member": "移除成员", "add_members": "添加成员", + "adding_member": "正在添加成员", "remove_members": "移除成员", "add": "添加", + "adding": "添加中", "remove": "移除", - "add_new": "新增", - "remove_selected": "移除选中项", - "first_name": "名字", - "last_name": "姓氏", - "email": "电子邮件", + "add_new": "添加新的", + "remove_selected": "移除所选", + "first_name": "名", + "last_name": "姓", + "email": "邮箱", "display_name": "显示名称", "role": "角色", "timezone": "时区", "avatar": "头像", "cover_image": "封面图片", "password": "密码", - "change_cover": "更换封面", + "change_cover": "更改封面", "language": "语言", - "saving": "保存中...", + "saving": "保存中", "save_changes": "保存更改", - "deactivate_account": "停用账户", - "deactivate_account_description": "停用账户后,该账户内的所有数据和资源将被永久删除,且无法恢复。", + "deactivate_account": "停用账号", + "deactivate_account_description": "停用账号后,该账号内的所有数据和资源将被永久删除且无法恢复。", "profile_settings": "个人资料设置", - "your_account": "账户", - "profile": "个人资料", + "your_account": "您的账号", "security": "安全", "activity": "活动", "appearance": "外观", @@ -50,116 +222,75 @@ "assigned": "已分配", "created": "已创建", "subscribed": "已订阅", - "you_do_not_have_the_permission_to_access_this_page": "您没有权限访问此页面。", - "failed_to_sign_out_please_try_again": "登出失败,请重试。", - "password_changed_successfully": "密码已成功更改。", - "something_went_wrong_please_try_again": "出错了,请重试。", - "change_password": "更改密码", - "passwords_dont_match": "密码不匹配", - "current_password": "当前密码", - "new_password": "新密码", - "confirm_password": "确认密码", - "this_field_is_required": "此字段为必填项", - "changing_password": "正在更改密码", - "please_enter_your_password": "请输入您的密码。", - "password_length_should_me_more_than_8_characters": "密码长度应大于8个字符。", - "password_is_weak": "密码强度较弱。", - "password_is_strong": "密码强度较强。", + "you_do_not_have_the_permission_to_access_this_page": "您没有访问此页面的权限。", + "something_went_wrong_please_try_again": "出现错误。请重试。", "load_more": "加载更多", - "select_or_customize_your_interface_color_scheme": "选择或自定义界面配色方案。", + "select_or_customize_your_interface_color_scheme": "选择或自定义您的界面配色方案。", "theme": "主题", "system_preference": "系统偏好", "light": "浅色", "dark": "深色", - "light_contrast": "浅色高对比", - "dark_contrast": "深色高对比", + "light_contrast": "浅色高对比度", + "dark_contrast": "深色高对比度", "custom": "自定义主题", - "select_your_theme": "选择主题", - "customize_your_theme": "自定义主题", + "select_your_theme": "选择您的主题", + "customize_your_theme": "自定义您的主题", "background_color": "背景颜色", "text_color": "文字颜色", - "primary_color": "主色调", + "primary_color": "主要(主题)颜色", "sidebar_background_color": "侧边栏背景颜色", "sidebar_text_color": "侧边栏文字颜色", "set_theme": "设置主题", - "enter_a_valid_hex_code_of_6_characters": "请输入6位有效的十六进制代码", + "enter_a_valid_hex_code_of_6_characters": "输入有效的6位十六进制代码", "background_color_is_required": "背景颜色为必填项", "text_color_is_required": "文字颜色为必填项", - "primary_color_is_required": "主色调为必填项", + "primary_color_is_required": "主要颜色为必填项", "sidebar_background_color_is_required": "侧边栏背景颜色为必填项", "sidebar_text_color_is_required": "侧边栏文字颜色为必填项", "updating_theme": "正在更新主题", - "theme_updated_successfully": "主题已成功更新", + "theme_updated_successfully": "主题更新成功", "failed_to_update_the_theme": "主题更新失败", - "email_notifications": "电子邮件通知", - "stay_in_the_loop_on_issues_you_are_subscribed_to_enable_this_to_get_notified": "启用此功能以接收您订阅问题的通知。", - "email_notification_setting_updated_successfully": "电子邮件通知设置已成功更新", - "failed_to_update_email_notification_setting": "电子邮件通知设置更新失败", - "notify_me_when": "在以下情况下通知我", - "property_changes": "属性更改", - "property_changes_description": "当负责人、优先级、估算等属性更改时通知。", - "state_change": "状态更改", - "state_change_description": "当问题状态更改时通知", - "issue_completed": "问题完成", - "issue_completed_description": "仅在问题完成时通知", + "email_notifications": "邮件通知", + "stay_in_the_loop_on_issues_you_are_subscribed_to_enable_this_to_get_notified": "及时了解您订阅的工作项。启用此功能以获取通知。", + "email_notification_setting_updated_successfully": "邮件通知设置更新成功", + "failed_to_update_email_notification_setting": "邮件通知设置更新失败", + "notify_me_when": "在以下情况通知我", + "property_changes": "属性变更", + "property_changes_description": "当工作项的属性(如负责人、优先级、估算等)发生变更时通知我。", + "state_change": "状态变更", + "state_change_description": "当工作项移动到不同状态时通知我", + "issue_completed": "工作项完成", + "issue_completed_description": "仅当工作项完成时通知我", "comments": "评论", - "comments_description": "当有人在问题中添加评论时通知", + "comments_description": "当有人在工作项上发表评论时通知我", "mentions": "提及", - "mentions_description": "仅在评论或描述中提及您时通知", - "create_your_workspace": "创建工作区", - "only_your_instance_admin_can_create_workspaces": "只有实例管理员可以创建工作区", - "only_your_instance_admin_can_create_workspaces_description": "如果您知道实例管理员的电子邮件地址,请点击以下按钮联系他们。", - "go_back": "返回", - "request_instance_admin": "请求实例管理员", - "plane_logo": "Plane 徽标", - "workspace_creation_disabled": "工作区创建已禁用", - "workspace_request_subject": "新工作区请求", - "workspace_request_body": "实例管理员:\n\n请在 URL [/workspace-name] 处创建一个新的工作区。[工作区创建目的]\n\n谢谢。\n{{firstName}} {{lastName}}\n{{email}}", - "creating_workspace": "正在创建工作区", - "workspace_created_successfully": "工作区已成功创建", - "create_workspace_page": "创建工作区页面", - "workspace_could_not_be_created_please_try_again": "无法创建工作区,请重试。", - "workspace_could_not_be_created_please_try_again_description": "创建工作区时出错,请重试。", - "this_is_a_required_field": "此字段为必填项。", - "name_your_workspace": "为工作区命名", - "workspaces_names_can_contain_only_space_dash_and_alphanumeric_characters": "工作区名称只能包含空格、破折号和字母数字字符。", - "limit_your_name_to_80_characters": "名称长度限制为80个字符。", - "set_your_workspace_url": "设置工作区URL", - "limit_your_url_to_48_characters": "URL长度限制为48个字符。", - "how_many_people_will_use_this_workspace": "有多少人将使用此工作区?", - "how_many_people_will_use_this_workspace_description": "这将帮助确定购买的席位数量。", - "select_a_range": "选择一个范围", - "urls_can_contain_only_dash_and_alphanumeric_characters": "URL只能包含破折号和字母数字字符。", - "something_familiar_and_recognizable_is_always_best": "熟悉且易于识别的名称总是最佳选择。", - "workspace_url_is_already_taken": "工作区URL已被占用!", + "mentions_description": "仅当有人在评论或描述中提及我时通知我", "old_password": "旧密码", "general_settings": "常规设置", - "sign_out": "登出", - "signing_out": "正在登出", - "active_cycles": "活跃周期", - "active_cycles_description": "监控所有项目的周期,跟踪高优先级问题,并关注需要关注的周期。", - "on_demand_snapshots_of_all_your_cycles": "所有周期的按需快照", + "sign_out": "退出登录", + "signing_out": "正在退出登录", + "active_cycles": "活动周期", + "active_cycles_description": "监控各个项目的周期,跟踪高优先级工作项,并关注需要注意的周期。", + "on_demand_snapshots_of_all_your_cycles": "所有周期的实时快照", "upgrade": "升级", - "10000_feet_view": "所有活跃周期的概览", - "10000_feet_view_description": "放大查看所有项目的周期,而不是单独查看每个项目的周期。", - "get_snapshot_of_each_active_cycle": "获取每个活跃周期的快照", - "get_snapshot_of_each_active_cycle_description": "跟踪所有活跃周期的高级指标,查看进度并了解范围与截止日期的关系。", - "compare_burndowns": "比较燃尽图", - "compare_burndowns_description": "监控每个团队的表现,查看每个周期的燃尽报告。", - "quickly_see_make_or_break_issues": "快速查看关键问题", - "quickly_see_make_or_break_issues_description": "预览每个周期中与截止日期相关的高优先级问题。一键查看所有周期的详细信息。", - "zoom_into_cycles_that_need_attention": "关注需要关注的周期", - "zoom_into_cycles_that_need_attention_description": "一键调查未达预期的周期状态。", - "stay_ahead_of_blockers": "提前解决阻碍", - "stay_ahead_of_blockers_description": "发现项目间的挑战,查看其他视图中不明显的周期依赖关系。", + "10000_feet_view": "所有活动周期的全局视图。", + "10000_feet_view_description": "放大视角,一次性查看所有项目中正在进行的周期,而不是在每个项目中逐个查看周期。", + "get_snapshot_of_each_active_cycle": "获取每个活动周期的快照。", + "get_snapshot_of_each_active_cycle_description": "跟踪所有活动周期的高级指标,查看其进度状态,并了解与截止日期相关的范围。", + "compare_burndowns": "比较燃尽图。", + "compare_burndowns_description": "通过查看每个周期的燃尽报告,监控每个团队的表现。", + "quickly_see_make_or_break_issues": "快速查看关键工作项。", + "quickly_see_make_or_break_issues_description": "预览每个周期中与截止日期相关的高优先级工作项。一键查看每个周期的所有工作项。", + "zoom_into_cycles_that_need_attention": "关注需要注意的周期。", + "zoom_into_cycles_that_need_attention_description": "一键调查任何不符合预期的周期状态。", + "stay_ahead_of_blockers": "提前预防阻塞。", + "stay_ahead_of_blockers_description": "发现从一个项目到另一个项目的挑战,并查看从其他视图中不易发现的周期间依赖关系。", "analytics": "分析", "workspace_invites": "工作区邀请", - "workspace_settings": "工作区设置", - "enter_god_mode": "进入上帝模式", - "workspace_logo": "工作区徽标", - "new_issue": "新建问题", - "home": "首页", - "your_work": "您的工作", + "enter_god_mode": "进入管理员模式", + "workspace_logo": "工作区标志", + "new_issue": "新工作项", + "your_work": "我的工作", "drafts": "草稿", "projects": "项目", "views": "视图", @@ -168,128 +299,134 @@ "settings": "设置", "failed_to_move_favorite": "移动收藏失败", "favorites": "收藏", - "no_favorites_yet": "尚无收藏", + "no_favorites_yet": "暂无收藏", "create_folder": "创建文件夹", "new_folder": "新建文件夹", - "favorite_updated_successfully": "收藏已成功更新", - "favorite_created_successfully": "收藏已成功创建", + "favorite_updated_successfully": "收藏更新成功", + "favorite_created_successfully": "收藏创建成功", "folder_already_exists": "文件夹已存在", "folder_name_cannot_be_empty": "文件夹名称不能为空", - "something_went_wrong": "出错了", + "something_went_wrong": "出现错误", "failed_to_reorder_favorite": "重新排序收藏失败", - "favorite_removed_successfully": "收藏已成功移除", + "favorite_removed_successfully": "收藏移除成功", "failed_to_create_favorite": "创建收藏失败", "failed_to_rename_favorite": "重命名收藏失败", "project_link_copied_to_clipboard": "项目链接已复制到剪贴板", "link_copied": "链接已复制", "add_project": "添加项目", "create_project": "创建项目", - "failed_to_remove_project_from_favorites": "无法从收藏中移除项目,请重试。", - "project_created_successfully": "项目已成功创建", - "project_created_successfully_description": "项目已成功创建。您现在可以开始添加问题。", + "failed_to_remove_project_from_favorites": "无法从收藏中移除项目。请重试。", + "project_created_successfully": "项目创建成功", + "project_created_successfully_description": "项目创建成功。您现在可以开始添加工作项了。", "project_cover_image_alt": "项目封面图片", "name_is_required": "名称为必填项", "title_should_be_less_than_255_characters": "标题应少于255个字符", "project_name": "项目名称", - "project_id_must_be_at_least_1_character": "项目ID至少为1个字符", - "project_id_must_be_at_most_5_characters": "项目ID最多为5个字符", + "project_id_must_be_at_least_1_character": "项目ID至少需要1个字符", + "project_id_must_be_at_most_5_characters": "项目ID最多只能有5个字符", "project_id": "项目ID", - "project_id_tooltip_content": "用于唯一标识项目中的问题。最多5个字符。", - "description_placeholder": "描述...", + "project_id_tooltip_content": "帮助您唯一标识项目中的工作项。最多5个字符。", + "description_placeholder": "描述", "only_alphanumeric_non_latin_characters_allowed": "仅允许字母数字和非拉丁字符。", "project_id_is_required": "项目ID为必填项", + "project_id_allowed_char": "仅允许字母数字和非拉丁字符。", + "project_id_min_char": "项目ID至少需要1个字符", + "project_id_max_char": "项目ID最多只能有5个字符", + "project_description_placeholder": "输入项目描述", "select_network": "选择网络", "lead": "负责人", + "date_range": "日期范围", "private": "私有", "public": "公开", - "accessible_only_by_invite": "仅限邀请访问", - "anyone_in_the_workspace_except_guests_can_join": "除访客外,工作区中的任何人都可以加入", + "accessible_only_by_invite": "仅受邀者可访问", + "anyone_in_the_workspace_except_guests_can_join": "除访客外的工作区所有成员都可以加入", "creating": "创建中", "creating_project": "正在创建项目", "adding_project_to_favorites": "正在将项目添加到收藏", "project_added_to_favorites": "项目已添加到收藏", - "couldnt_add_the_project_to_favorites": "无法将项目添加到收藏,请重试。", + "couldnt_add_the_project_to_favorites": "无法将项目添加到收藏。请重试。", "removing_project_from_favorites": "正在从收藏中移除项目", "project_removed_from_favorites": "项目已从收藏中移除", - "couldnt_remove_the_project_from_favorites": "无法从收藏中移除项目,请重试。", + "couldnt_remove_the_project_from_favorites": "无法从收藏中移除项目。请重试。", "add_to_favorites": "添加到收藏", "remove_from_favorites": "从收藏中移除", "publish_settings": "发布设置", "publish": "发布", "copy_link": "复制链接", "leave_project": "离开项目", - "join_the_project_to_rearrange": "加入项目以重新排序", - "drag_to_rearrange": "拖动以重新排序", + "join_the_project_to_rearrange": "加入项目以重新排列", + "drag_to_rearrange": "拖动以重新排列", "congrats": "恭喜!", - "project": "项目", "open_project": "打开项目", - "issues": "问题", + "issues": "工作项", "cycles": "周期", "modules": "模块", "pages": "页面", - "intake": "接收", + "intake": "收集", "time_tracking": "时间跟踪", "work_management": "工作管理", - "projects_and_issues": "项目与问题", - "projects_and_issues_description": "在此项目上启用或禁用。", - "cycles_description": "按时间段对项目工作进行时间盒管理,并调整频率。", - "modules_description": "将工作分组为类似子项目的设置,拥有自己的负责人和分配人。", - "views_description": "保存排序、过滤和显示选项以供以后使用或共享。", - "pages_description": "写下一些内容。", - "intake_description": "启用此功能以接收您订阅问题的通知。", - "time_tracking_description": "跟踪问题和项目所花费的时间。", - "work_management_description": "轻松管理工作和项目。", + "projects_and_issues": "项目和工作项", + "projects_and_issues_description": "在此项目中开启或关闭这些功能。", + "cycles_description": "根据项目需要设置时间框,可以根据不同时期更改频率。", + "modules_description": "将工作分组为类似子项目的设置,具有各自的负责人和分配者。", + "views_description": "保存排序、筛选和显示选项以供后续使用或分享。", + "pages_description": "像写任何东西一样写任何东西。", + "intake_description": "及时了解您订阅的工作项。启用此功能以获取通知。", + "time_tracking_description": "跟踪工作项和项目的时间消耗。", + "work_management_description": "轻松管理您的工作和项目。", "documentation": "文档", "message_support": "联系支持", "contact_sales": "联系销售", - "hyper_mode": "超速模式", + "hyper_mode": "超级模式", "keyboard_shortcuts": "键盘快捷键", "whats_new": "新功能", "version": "版本", - "we_are_having_trouble_fetching_the_updates": "获取更新时出现问题。", + "we_are_having_trouble_fetching_the_updates": "我们在获取更新时遇到问题。", "our_changelogs": "我们的更新日志", - "for_the_latest_updates": "获取最新更新,请访问", + "for_the_latest_updates": "获取最新更新。", "please_visit": "请访问", "docs": "文档", "full_changelog": "完整更新日志", "support": "支持", "discord": "Discord", - "powered_by_plane_pages": "由 Plane Pages 提供支持", + "powered_by_plane_pages": "由Plane Pages提供支持", "please_select_at_least_one_invitation": "请至少选择一个邀请。", - "please_select_at_least_one_invitation_description": "请至少选择一个邀请以加入工作区。", - "we_see_that_someone_has_invited_you_to_join_a_workspace": "我们发现有人邀请您加入一个工作区", + "please_select_at_least_one_invitation_description": "请至少选择一个加入工作区的邀请。", + "we_see_that_someone_has_invited_you_to_join_a_workspace": "我们看到有人邀请您加入工作区", "join_a_workspace": "加入工作区", - "we_see_that_someone_has_invited_you_to_join_a_workspace_description": "我们发现有人邀请您加入一个工作区", + "we_see_that_someone_has_invited_you_to_join_a_workspace_description": "我们看到有人邀请您加入工作区", "join_a_workspace_description": "加入工作区", "accept_and_join": "接受并加入", "go_home": "返回首页", "no_pending_invites": "没有待处理的邀请", - "you_can_see_here_if_someone_invites_you_to_a_workspace": "如果有人邀请您加入工作区,您可以在此处查看", + "you_can_see_here_if_someone_invites_you_to_a_workspace": "如果有人邀请您加入工作区,您可以在这里看到", "back_to_home": "返回首页", "workspace_name": "工作区名称", "deactivate_your_account": "停用您的账户", - "deactivate_your_account_description": "停用后,您将无法再分配问题,工作区的计费也将停止。要重新激活账户,您需要使用此电子邮件地址收到工作区邀请。", + "deactivate_your_account_description": "一旦停用,您将无法被分配工作项,也不会被计入工作区的账单。要重新激活您的账户,您需要收到发送到此电子邮件地址的工作区邀请。", "deactivating": "正在停用", "confirm": "确认", + "confirming": "确认中", "draft_created": "草稿已创建", - "issue_created_successfully": "问题已成功创建", + "issue_created_successfully": "工作项创建成功", "draft_creation_failed": "草稿创建失败", - "issue_creation_failed": "问题创建失败", - "draft_issue": "草稿问题", - "issue_updated_successfully": "问题已成功更新", - "issue_could_not_be_updated": "无法更新问题", + "issue_creation_failed": "工作项创建失败", + "draft_issue": "草稿工作项", + "issue_updated_successfully": "工作项更新成功", + "issue_could_not_be_updated": "工作项无法更新", "create_a_draft": "创建草稿", - "save_to_drafts": "保存为草稿", + "save_to_drafts": "保存到草稿", "save": "保存", - "updating": "正在更新", - "create_new_issue": "创建新问题", - "editor_is_not_ready_to_discard_changes": "编辑器尚未准备好丢弃更改", - "failed_to_move_issue_to_project": "无法将问题移动到项目", + "update": "更新", + "updating": "更新中", + "create_new_issue": "创建新工作项", + "editor_is_not_ready_to_discard_changes": "编辑器尚未准备好放弃更改", + "failed_to_move_issue_to_project": "无法将工作项移动到项目", "create_more": "创建更多", "add_to_project": "添加到项目", - "discard": "丢弃", - "duplicate_issue_found": "发现重复问题", - "duplicate_issues_found": "发现重复问题", + "discard": "放弃", + "duplicate_issue_found": "发现重复的工作项", + "duplicate_issues_found": "发现重复的工作项", "no_matching_results": "没有匹配的结果", "title_is_required": "标题为必填项", "title": "标题", @@ -301,18 +438,1882 @@ "medium": "中", "low": "低", "members": "成员", - "assignee": "分配人", - "assignees": "分配人", + "assignee": "负责人", + "assignees": "负责人", "you": "您", "labels": "标签", "create_new_label": "创建新标签", "start_date": "开始日期", + "end_date": "结束日期", "due_date": "截止日期", - "cycle": "周期", "estimate": "估算", - "change_parent_issue": "更改父问题", - "remove_parent_issue": "移除父问题", - "add_parent": "添加父问题", - "loading_members": "正在加载成员...", - "inbox": "收件箱" + "change_parent_issue": "更改父工作项", + "remove_parent_issue": "移除父工作项", + "add_parent": "添加父项", + "loading_members": "正在加载成员", + "view_link_copied_to_clipboard": "视图链接已复制到剪贴板", + "required": "必填", + "optional": "可选", + "Cancel": "取消", + "edit": "编辑", + "archive": "归档", + "restor": "恢复", + "open_in_new_tab": "在新标签页中打开", + "delete": "删除", + "deleting": "删除中", + "make_a_copy": "创建副本", + "move_to_project": "移动到项目", + "good": "早上", + "morning": "早上", + "afternoon": "下午", + "evening": "晚上", + "show_all": "显示全部", + "show_less": "显示更少", + "no_data_yet": "暂无数据", + "syncing": "同步中", + "add_work_item": "添加工作项", + "advanced_description_placeholder": "按'/'使用命令", + "create_work_item": "创建工作项", + "attachments": "附件", + "declining": "拒绝中", + "declined": "已拒绝", + "decline": "拒绝", + "unassigned": "未分配", + "work_items": "工作项", + "add_link": "添加链接", + "points": "点数", + "no_assignee": "无负责人", + "no_assignees_yet": "暂无负责人", + "no_labels_yet": "暂无标签", + "ideal": "理想", + "current": "当前", + "no_matching_members": "没有匹配的成员", + "leaving": "离开中", + "removing": "移除中", + "leave": "离开", + "refresh": "刷新", + "refreshing": "刷新中", + "refresh_status": "刷新状态", + "prev": "上一个", + "next": "下一个", + "re_generating": "重新生成中", + "re_generate": "重新生成", + "re_generate_key": "重新生成密钥", + "export": "导出", + "member": "{count, plural, other{# 成员}}", + + "project_view": { + "sort_by": { + "created_at": "创建时间", + "updated_at": "更新时间", + "name": "名称" + } + }, + + "toast": { + "success": "成功!", + "error": "错误!" + }, + + "links": { + "toasts": { + "created": { + "title": "链接已创建", + "message": "链接已成功创建" + }, + "not_created": { + "title": "链接未创建", + "message": "无法创建链接" + }, + "updated": { + "title": "链接已更新", + "message": "链接已成功更新" + }, + "not_updated": { + "title": "链接未更新", + "message": "无法更新链接" + }, + "removed": { + "title": "链接已移除", + "message": "链接已成功移除" + }, + "not_removed": { + "title": "链接未移除", + "message": "无法移除链接" + } + } + }, + + "home": { + "empty": { + "create_project": { + "title": "创建项目", + "description": "在Plane中,大多数事情都从项目开始。", + "cta": "开始使用" + }, + "invite_team": { + "title": "邀请您的团队", + "description": "与同事一起构建、发布和管理。", + "cta": "邀请他们加入" + }, + "configure_workspace": { + "title": "设置您的工作区", + "description": "开启或关闭功能,或进行更多设置。", + "cta": "配置此工作区" + }, + "personalize_account": { + "title": "让Plane更适合您", + "description": "选择您的头像、颜色等。", + "cta": "立即个性化" + }, + "widgets": { + "title": "没有小部件看起来很安静,开启它们吧", + "description": "看起来您的所有小部件都已关闭。现在启用它们\n来提升您的体验!", + "primary_button": { + "text": "管理小部件" + } + } + }, + "quick_links": { + "empty": "保存您想要方便访问的工作相关链接。", + "add": "添加快速链接", + "title": "快速链接", + "title_plural": "快速链接" + }, + "recents": { + "title": "最近", + "empty": { + "project": "访问项目后,您的最近项目将显示在这里。", + "page": "访问页面后,您的最近页面将显示在这里。", + "issue": "访问工作项后,您的最近工作项将显示在这里。", + "default": "您还没有任何最近项目。" + }, + "filters": { + "all": "所有项目", + "projects": "项目", + "pages": "页面", + "issues": "工作项" + } + }, + "new_at_plane": { + "title": "Plane新功能" + }, + "quick_tutorial": { + "title": "快速教程" + }, + "widget": { + "reordered_successfully": "小部件重新排序成功。", + "reordering_failed": "重新排序小部件时出错。" + }, + "manage_widgets": "管理小部件", + "title": "首页", + "star_us_on_github": "在GitHub上为我们加星" + }, + + "link": { + "modal": { + "url": { + "text": "URL", + "required": "URL无效", + "placeholder": "输入或粘贴URL" + }, + "title": { + "text": "显示标题", + "placeholder": "您希望如何显示此链接" + } + } + }, + + "common": { + "all": "全部", + "states": "状态", + "state": "状态", + "state_groups": "状态组", + "priority": "优先级", + "team_project": "团队项目", + "project": "项目", + "cycle": "周期", + "cycles": "周期", + "module": "模块", + "modules": "模块", + "labels": "标签", + "assignees": "负责人", + "assignee": "负责人", + "created_by": "创建者", + "none": "无", + "link": "链接", + "estimate": "估算", + "layout": "布局", + "filters": "筛选", + "display": "显示", + "load_more": "加载更多", + "activity": "活动", + "analytics": "分析", + "dates": "日期", + "success": "成功!", + "something_went_wrong": "出现错误", + "error": { + "label": "错误!", + "message": "发生错误。请重试。" + }, + "group_by": "分组方式", + "epic": "史诗", + "epics": "史诗", + "work_item": "工作项", + "sub_work_item": "子工作项", + "add": "添加", + "warning": "警告", + "updating": "更新中", + "adding": "添加中", + "update": "更新", + "creating": "创建中", + "create": "创建", + "cancel": "取消", + "description": "描述", + "title": "标题", + "attachment": "附件", + "general": "常规", + "features": "功能", + "automation": "自动化", + "project_name": "项目名称", + "project_id": "项目ID", + "project_timezone": "项目时区", + "created_on": "创建于", + "update_project": "更新项目", + "identifier_already_exists": "标识符已存在", + "add_more": "添加更多", + "defaults": "默认值", + "add_label": "添加标签", + "estimates": "估算", + "customize_time_range": "自定义时间范围", + "loading": "加载中", + "attachments": "附件", + "properties": "属性", + "parent": "父项", + "remove": "移除", + "archiving": "归档中", + "archive": "归档", + "access": { + "public": "公开", + "private": "私有" + }, + "done": "完成", + "sub_work_items": "子工作项", + "comment": "评论", + "workspace_level": "工作区级别", + "order_by": { + "label": "排序方式", + "manual": "手动", + "last_created": "最近创建", + "last_updated": "最近更新", + "start_date": "开始日期", + "due_date": "截止日期", + "asc": "升序", + "desc": "降序", + "updated_on": "更新时间" + }, + "sort": { + "asc": "升序", + "desc": "降序", + "created_on": "创建时间", + "updated_on": "更新时间" + }, + "comments": "评论", + "updates": "更新", + "clear_all": "清除全部", + "copied": "已复制!", + "link_copied": "链接已复制!", + "link_copied_to_clipboard": "链接已复制到剪贴板", + "copied_to_clipboard": "工作项链接已复制到剪贴板", + "is_copied_to_clipboard": "工作项已复制到剪贴板", + "no_links_added_yet": "暂无添加的链接", + "add_link": "添加链接", + "links": "链接", + "go_to_workspace": "前往工作区", + "progress": "进度", + "optional": "可选", + "join": "加入", + "go_back": "返回", + "continue": "继续", + "resend": "重新发送", + "relations": "关系", + "errors": { + "default": { + "title": "错误!", + "message": "发生错误。请重试。" + }, + "required": "此字段为必填项", + "entity_required": "{entity}为必填项" + }, + "update_link": "更新链接", + "attach": "附加", + "create_new": "创建新的", + "add_existing": "添加现有", + "type_or_paste_a_url": "输入或粘贴URL", + "url_is_invalid": "URL无效", + "display_title": "显示标题", + "link_title_placeholder": "您希望如何显示此链接", + "url": "URL", + "side_peek": "侧边预览", + "modal": "模态框", + "full_screen": "全屏", + "close_peek_view": "关闭预览视图", + "toggle_peek_view_layout": "切换预览视图布局", + "options": "选项", + "duration": "持续时间", + "today": "今天", + "week": "周", + "month": "月", + "quarter": "季度", + "press_for_commands": "按'/'使用命令", + "click_to_add_description": "点击添加描述", + "search": { + "label": "搜索", + "placeholder": "输入搜索内容", + "no_matches_found": "未找到匹配项", + "no_matching_results": "没有匹配的结果" + }, + "actions": { + "edit": "编辑", + "make_a_copy": "创建副本", + "open_in_new_tab": "在新标签页中打开", + "copy_link": "复制链接", + "archive": "归档", + "delete": "删除", + "remove_relation": "移除关系", + "subscribe": "订阅", + "unsubscribe": "取消订阅", + "clear_sorting": "清除排序", + "show_weekends": "显示周末", + "enable": "启用", + "disable": "禁用" + }, + "name": "名称", + "discard": "放弃", + "confirm": "确认", + "confirming": "确认中", + "read_the_docs": "阅读文档", + "default": "默认", + "active": "活动", + "enabled": "已启用", + "disabled": "已禁用", + "mandate": "授权", + "mandatory": "必需的", + "yes": "是", + "no": "否", + "please_wait": "请稍候", + "enabling": "正在启用", + "disabling": "正在禁用", + "beta": "测试版", + "or": "或", + "next": "下一步", + "back": "返回", + "cancelling": "正在取消", + "configuring": "正在配置", + "clear": "清除", + "import": "导入", + "connect": "连接", + "authorizing": "正在授权", + "processing": "正在处理", + "no_data_available": "暂无数据", + "from": "来自 {name}", + "authenticated": "已认证", + "select": "选择", + "upgrade": "升级", + "add_seats": "添加席位", + "label": "标签", + "priorities": "优先级", + "projects": "项目", + "workspace": "工作区", + "workspaces": "工作区", + "team": "团队", + "teams": "团队", + "entity": "实体", + "entities": "实体", + "task": "任务", + "tasks": "任务", + "section": "部分", + "sections": "部分", + "edit": "编辑", + "connecting": "正在连接", + "connected": "已连接", + "disconnect": "断开连接", + "disconnecting": "正在断开连接", + "installing": "正在安装", + "install": "安装" + }, + + "form": { + "title": { + "required": "标题为必填项", + "max_length": "标题应少于 {length} 个字符" + } + }, + + "entity": { + "grouping_title": "{entity}分组", + "priority": "{entity}优先级", + "all": "所有{entity}", + "drop_here_to_move": "拖放到此处以移动{entity}", + "delete": { + "label": "删除{entity}", + "success": "{entity}删除成功", + "failed": "{entity}删除失败" + }, + "update": { + "failed": "{entity}更新失败", + "success": "{entity}更新成功" + }, + "link_copied_to_clipboard": "{entity}链接已复制到剪贴板", + "fetch": { + "failed": "获取{entity}时出错" + }, + "add": { + "success": "{entity}添加成功", + "failed": "添加{entity}时出错" + } + }, + + "epic": { + "all": "所有史诗", + "label": "{count, plural, one {史诗} other {史诗}}", + "new": "新建史诗", + "adding": "正在添加史诗", + "create": { + "success": "史诗创建成功" + }, + "add": { + "press_enter": "按'Enter'添加另一个史诗", + "label": "添加史诗" + }, + "title": { + "label": "史诗标题", + "required": "史诗标题为必填项" + } + }, + + "issue": { + "label": "{count, plural, one {工作项} other {工作项}}", + "all": "所有工作项", + "edit": "编辑工作项", + "title": { + "label": "工作项标题", + "required": "工作项标题为必填项" + }, + "add": { + "press_enter": "按'Enter'添加另一个工作项", + "label": "添加工作项", + "cycle": { + "failed": "无法将工作项添加到周期。请重试。", + "success": "{count, plural, one {工作项} other {工作项}}已成功添加到周期。", + "loading": "正在将{count, plural, one {工作项} other {工作项}}添加到周期" + }, + "assignee": "添加负责人", + "start_date": "添加开始日期", + "due_date": "添加截止日期", + "parent": "添加父工作项", + "sub_issue": "添加子工作项", + "relation": "添加关系", + "link": "添加链接", + "existing": "添加现有工作项" + }, + "remove": { + "label": "移除工作项", + "cycle": { + "loading": "正在从周期中移除工作项", + "success": "已成功从周期中移除工作项。", + "failed": "无法从周期中移除工作项。请重试。" + }, + "module": { + "loading": "正在从模块中移除工作项", + "success": "已成功从模块中移除工作项。", + "failed": "无法从模块中移除工作项。请重试。" + }, + "parent": { + "label": "移除父工作项" + } + }, + "new": "新建工作项", + "adding": "正在添加工作项", + "create": { + "success": "工作项创建成功" + }, + "priority": { + "urgent": "紧急", + "high": "高", + "medium": "中", + "low": "低" + }, + "display": { + "properties": { + "label": "显示属性", + "id": "ID", + "issue_type": "工作项类型", + "sub_issue_count": "子工作项数量", + "attachment_count": "附件数量", + "created_on": "创建于", + "sub_issue": "子工作项" + }, + "extra": { + "show_sub_issues": "显示子工作项", + "show_empty_groups": "显示空组" + } + }, + "layouts": { + "ordered_by_label": "此布局按以下方式排序", + "list": "列表", + "kanban": "看板", + "calendar": "日历", + "spreadsheet": "表格", + "gantt": "时间线", + "title": { + "list": "列表布局", + "kanban": "看板布局", + "calendar": "日历布局", + "spreadsheet": "表格布局", + "gantt": "时间线布局" + } + }, + "states": { + "active": "活动", + "backlog": "待办" + }, + "comments": { + "placeholder": "添加评论", + "switch": { + "private": "切换为私密评论", + "public": "切换为公开评论" + }, + "create": { + "success": "评论创建成功", + "error": "评论创建失败。请稍后重试。" + }, + "update": { + "success": "评论更新成功", + "error": "评论更新失败。请稍后重试。" + }, + "remove": { + "success": "评论删除成功", + "error": "评论删除失败。请稍后重试。" + }, + "upload": { + "error": "资源上传失败。请稍后重试。" + } + }, + "empty_state": { + "issue_detail": { + "title": "工作项不存在", + "description": "您查找的工作项不存在、已归档或已删除。", + "primary_button": { + "text": "查看其他工作项" + } + } + }, + "sibling": { + "label": "同级工作项" + }, + "archive": { + "description": "只有已完成或已取消的\n工作项可以归档", + "label": "归档工作项", + "confirm_message": "您确定要归档此工作项吗?所有已归档的工作项稍后可以恢复。", + "success": { + "label": "归档成功", + "message": "您的归档可以在项目归档中找到。" + }, + "failed": { + "message": "无法归档工作项。请重试。" + } + }, + "restore": { + "success": { + "title": "恢复成功", + "message": "您的工作项可以在项目工作项中找到。" + }, + "failed": { + "message": "无法恢复工作项。请重试。" + } + }, + "relation": { + "relates_to": "关联到", + "duplicate": "重复于", + "blocked_by": "被阻止于", + "blocking": "阻止" + }, + "copy_link": "复制工作项链接", + "delete": { + "label": "删除工作项", + "error": "删除工作项时出错" + }, + "subscription": { + "actions": { + "subscribed": "工作项订阅成功", + "unsubscribed": "工作项取消订阅成功" + } + }, + "select": { + "error": "请至少选择一个工作项", + "empty": "未选择工作项", + "add_selected": "添加所选工作项" + }, + "open_in_full_screen": "在全屏中打开工作项" + }, + + "attachment": { + "error": "无法附加文件。请重新上传。", + "only_one_file_allowed": "一次只能上传一个文件。", + "file_size_limit": "文件大小必须小于或等于 {size}MB。", + "drag_and_drop": "拖放到任意位置以上传", + "delete": "删除附件" + }, + + "label": { + "select": "选择标签", + "create": { + "success": "标签创建成功", + "failed": "标签创建失败", + "already_exists": "标签已存在", + "type": "输入以添加新标签" + } + }, + + "sub_work_item": { + "update": { + "success": "子工作项更新成功", + "error": "更新子工作项时出错" + }, + "remove": { + "success": "子工作项移除成功", + "error": "移除子工作项时出错" + } + }, + + "view": { + "label": "{count, plural, one {视图} other {视图}}", + "create": { + "label": "创建视图" + }, + "update": { + "label": "更新视图" + } + }, + + "inbox_issue": { + "status": { + "pending": { + "title": "待处理", + "description": "待处理" + }, + "declined": { + "title": "已拒绝", + "description": "已拒绝" + }, + "snoozed": { + "title": "已暂停", + "description": "还剩{days, plural, one{# 天} other{# 天}}" + }, + "accepted": { + "title": "已接受", + "description": "已接受" + }, + "duplicate": { + "title": "重复", + "description": "重复" + } + }, + "modals": { + "decline": { + "title": "拒绝工作项", + "content": "您确定要拒绝工作项 {value} 吗?" + }, + "delete": { + "title": "删除工作项", + "content": "您确定要删除工作项 {value} 吗?", + "success": "工作项删除成功" + } + }, + "errors": { + "snooze_permission": "只有项目管理员可以暂停/取消暂停工作项", + "accept_permission": "只有项目管理员可以接受工作项", + "decline_permission": "只有项目管理员可以拒绝工作项" + }, + "actions": { + "accept": "接受", + "decline": "拒绝", + "snooze": "暂停", + "unsnooze": "取消暂停", + "copy": "复制工作项链接", + "delete": "删除", + "open": "打开工作项", + "mark_as_duplicate": "标记为重复", + "move": "将 {value} 移至项目工作项" + }, + "source": { + "in-app": "应用内" + }, + "order_by": { + "created_at": "创建时间", + "updated_at": "更新时间", + "id": "ID" + }, + "label": "收集", + "page_label": "{workspace} - 收集", + "modal": { + "title": "创建收集工作项" + }, + "tabs": { + "open": "未处理", + "closed": "已处理" + }, + "empty_state": { + "sidebar_open_tab": { + "title": "没有未处理的工作项", + "description": "在此处查找未处理的工作项。创建新工作项。" + }, + "sidebar_closed_tab": { + "title": "没有已处理的工作项", + "description": "所有已接受或已拒绝的工作项都可以在这里找到。" + }, + "sidebar_filter": { + "title": "没有匹配的工作项", + "description": "收集中没有符合筛选条件的工作项。创建新工作项。" + }, + "detail": { + "title": "选择一个工作项以查看其详细信息。" + } + } + }, + + "workspace_creation": { + "heading": "创建您的工作区", + "subheading": "要开始使用 Plane,您需要创建或加入一个工作区。", + "form": { + "name": { + "label": "为您的工作区命名", + "placeholder": "熟悉且易于识别的名称总是最好的。" + }, + "url": { + "label": "设置您的工作区 URL", + "placeholder": "输入或粘贴 URL", + "edit_slug": "您只能编辑 URL 的标识符部分" + }, + "organization_size": { + "label": "有多少人将使用这个工作区?", + "placeholder": "选择一个范围" + } + }, + "errors": { + "creation_disabled": { + "title": "只有您的实例管理员可以创建工作区", + "description": "如果您知道实例管理员的电子邮件地址,请点击下方按钮与他们联系。", + "request_button": "请求实例管理员" + }, + "validation": { + "name_alphanumeric": "工作区名称只能包含 (' '), ('-'), ('_') 和字母数字字符。", + "name_length": "名称限制在 80 个字符以内。", + "url_alphanumeric": "URL 只能包含 ('-') 和字母数字字符。", + "url_length": "URL 限制在 48 个字符以内。", + "url_already_taken": "工作区 URL 已被占用!" + } + }, + "request_email": { + "subject": "请求新工作区", + "body": "您好,实例管理员:\n\n请为 [创建工作区的目的] 创建一个 URL 为 [/workspace-name] 的新工作区。\n\n谢谢,\n{firstName} {lastName}\n{email}" + }, + "button": { + "default": "创建工作区", + "loading": "正在创建工作区" + }, + "toast": { + "success": { + "title": "成功", + "message": "工作区创建成功" + }, + "error": { + "title": "错误", + "message": "工作区创建失败。请重试。" + } + } + }, + + "workspace_dashboard": { + "empty_state": { + "general": { + "title": "项目、活动和指标概览", + "description": "欢迎使用 Plane,我们很高兴您能来到这里。创建您的第一个项目并跟踪您的工作项,这个页面将转变为帮助您进展的空间。管理员还将看到帮助团队进展的项目。", + "primary_button": { + "text": "构建您的第一个项目", + "comic": { + "title": "在 Plane 中一切都从项目开始", + "description": "项目可以是产品路线图、营销活动或新车发布。" + } + } + } + } + }, + + "workspace_analytics": { + "label": "分析", + "page_label": "{workspace} - 分析", + "open_tasks": "总开放任务", + "error": "获取数据时出现错误。", + "work_items_closed_in": "已关闭的工作项", + "selected_projects": "已选择的项目", + "total_members": "总成员数", + "total_cycles": "总周期数", + "total_modules": "总模块数", + "pending_work_items": { + "title": "待处理工作项", + "empty_state": "同事的待处理工作项分析将显示在这里。" + }, + "work_items_closed_in_a_year": { + "title": "一年内关闭的工作项", + "empty_state": "关闭工作项以查看以图表形式显示的分析。" + }, + "most_work_items_created": { + "title": "创建最多工作项", + "empty_state": "同事及其创建的工作项数量将显示在这里。" + }, + "most_work_items_closed": { + "title": "关闭最多工作项", + "empty_state": "同事及其关闭的工作项数量将显示在这里。" + }, + "tabs": { + "scope_and_demand": "范围和需求", + "custom": "自定义分析" + }, + "empty_state": { + "general": { + "title": "跟踪进度、工作量和分配。发现趋势、消除障碍并加快工作进度", + "description": "查看范围与需求、估算和范围蔓延。获取团队成员和团队的表现,确保您的项目按时运行。", + "primary_button": { + "text": "开始您的第一个项目", + "comic": { + "title": "分析在周期 + 模块中效果最佳", + "description": "首先,将您的工作项限定在周期中,如果可能的话,将跨越多个周期的工作项分组到模块中。在左侧导航栏中查看这两项。" + } + } + } + } + }, + + "workspace_projects": { + "label": "{count, plural, one {项目} other {项目}}", + "create": { + "label": "添加项目" + }, + "network": { + "private": { + "title": "私有", + "description": "仅限邀请访问" + }, + "public": { + "title": "公开", + "description": "工作区中除访客外的任何人都可以加入" + } + }, + "error": { + "permission": "您没有执行此操作的权限。", + "cycle_delete": "删除周期失败", + "module_delete": "删除模块失败", + "issue_delete": "删除工作项失败" + }, + "state": { + "backlog": "待办", + "unstarted": "未开始", + "started": "进行中", + "completed": "已完成", + "cancelled": "已取消" + }, + "sort": { + "manual": "手动", + "name": "名称", + "created_at": "创建日期", + "members_length": "成员数量" + }, + "scope": { + "my_projects": "我的项目", + "archived_projects": "已归档" + }, + "common": { + "months_count": "{months, plural, one{# 个月} other{# 个月}}" + }, + "empty_state": { + "general": { + "title": "没有活动项目", + "description": "将每个项目视为目标导向工作的父级。项目是工作项、周期和模块所在的地方,与您的同事一起帮助您实现目标。创建新项目或筛选已归档的项目。", + "primary_button": { + "text": "开始您的第一个项目", + "comic": { + "title": "在 Plane 中一切都从项目开始", + "description": "项目可以是产品路线图、营销活动或新车发布。" + } + } + }, + "no_projects": { + "title": "没有项目", + "description": "要创建工作项或管理您的工作,您需要创建一个项目或成为项目的一部分。", + "primary_button": { + "text": "开始您的第一个项目", + "comic": { + "title": "在 Plane 中一切都从项目开始", + "description": "项目可以是产品路线图、营销活动或新车发布。" + } + } + }, + "filter": { + "title": "没有匹配的项目", + "description": "未检测到符合匹配条件的项目。\n创建一个新项目。" + }, + "search": { + "description": "未检测到符合匹配条件的项目。\n创建一个新项目" + } + } + }, + + "workspace_views": { + "add_view": "添加视图", + "empty_state": { + "all-issues": { + "title": "项目中没有工作项", + "description": "第一个项目完成!现在,将您的工作分解成可跟踪的工作项。让我们开始吧!", + "primary_button": { + "text": "创建新工作项" + } + }, + "assigned": { + "title": "还没有工作项", + "description": "可以在这里跟踪分配给您的工作项。", + "primary_button": { + "text": "创建新工作项" + } + }, + "created": { + "title": "还没有工作项", + "description": "您创建的所有工作项都会出现在这里,直接在这里跟踪它们。", + "primary_button": { + "text": "创建新工作项" + } + }, + "subscribed": { + "title": "还没有工作项", + "description": "订阅您感兴趣的工作项,在这里跟踪所有这些工作项。" + }, + "custom-view": { + "title": "还没有工作项", + "description": "符合筛选条件的工作项,在这里跟踪所有这些工作项。" + } + } + }, + + "workspace_settings": { + "label": "工作区设置", + "page_label": "{workspace} - 常规设置", + "key_created": "密钥已创建", + "copy_key": "复制并将此密钥保存在 Plane Pages 中。关闭后您将无法看到此密钥。包含密钥的 CSV 文件已下载。", + "token_copied": "令牌已复制到剪贴板。", + "settings": { + "general": { + "title": "常规", + "upload_logo": "上传标志", + "edit_logo": "编辑标志", + "name": "工作区名称", + "company_size": "公司规模", + "url": "工作区网址", + "update_workspace": "更新工作区", + "delete_workspace": "删除工作区", + "delete_workspace_description": "删除工作区时,该工作区内的所有数据和资源将被永久删除且无法恢复。", + "delete_btn": "删除我的工作区", + "errors": { + "name": { + "required": "名称为必填项", + "max_length": "工作区名称不应超过80个字符" + }, + "company_size": { + "required": "公司规模为必填项" + } + } + }, + "members": { + "title": "成员", + "add_member": "添加成员", + "invitations_sent_successfully": "邀请发送成功", + "leave_confirmation": "您确定要离开工作区吗?您将无法再访问此工作区。此操作无法撤消。", + "details": { + "full_name": "全名", + "display_name": "显示名称", + "email_address": "电子邮件地址", + "account_type": "账户类型", + "authentication": "身份验证", + "joining_date": "加入日期" + }, + "modal": { + "title": "邀请人员协作", + "description": "邀请人员在您的工作区中协作。", + "button": "发送邀请", + "button_loading": "正在发送邀请", + "placeholder": "name@company.com", + "errors": { + "required": "我们需要一个电子邮件地址来邀请他们。", + "invalid": "电子邮件无效" + } + } + }, + "billing_and_plans": { + "title": "账单与计划", + "current_plan": "当前计划", + "free_plan": "您目前使用的是免费计划", + "view_plans": "查看计划" + }, + "exports": { + "title": "导出", + "exporting": "导出中", + "previous_exports": "以前的导出", + "export_separate_files": "将数据导出为单独的文件", + "modal": { + "title": "导出到", + "toasts": { + "success": { + "title": "导出成功", + "message": "您可以从之前的导出中下载导出的{entity}" + }, + "error": { + "title": "导出失败", + "message": "导出未成功。请重试。" + } + } + } + }, + "webhooks": { + "title": "Webhooks", + "add_webhook": "添加 webhook", + "modal": { + "title": "创建 webhook", + "details": "Webhook 详情", + "payload": "负载 URL", + "question": "您希望触发此 webhook 的事件有哪些?", + "error": "URL 为必填项" + }, + "secret_key": { + "title": "密钥", + "message": "生成令牌以登录 webhook 负载" + }, + "options": { + "all": "发送所有内容", + "individual": "选择单个事件" + }, + "toasts": { + "created": { + "title": "Webhook 已创建", + "message": "Webhook 已成功创建" + }, + "not_created": { + "title": "Webhook 未创建", + "message": "无法创建 webhook" + }, + "updated": { + "title": "Webhook 已更新", + "message": "Webhook 已成功更新" + }, + "not_updated": { + "title": "Webhook 未更新", + "message": "无法更新 webhook" + }, + "removed": { + "title": "Webhook 已移除", + "message": "Webhook 已成功移除" + }, + "not_removed": { + "title": "Webhook 未移除", + "message": "无法移除 webhook" + }, + "secret_key_copied": { + "message": "密钥已复制到剪贴板。" + }, + "secret_key_not_copied": { + "message": "复制密钥时出错。" + } + } + }, + "api_tokens": { + "title": "API 令牌", + "add_token": "添加 API 令牌", + "create_token": "创建令牌", + "never_expires": "永不过期", + "generate_token": "生成令牌", + "generating": "生成中", + "delete": { + "title": "删除 API 令牌", + "description": "使用此令牌的任何应用程序将无法再访问 Plane 数据。此操作无法撤消。", + "success": { + "title": "成功!", + "message": "API 令牌已成功删除" + }, + "error": { + "title": "错误!", + "message": "无法删除 API 令牌" + } + } + } + }, + "empty_state": { + "api_tokens": { + "title": "尚未创建 API 令牌", + "description": "Plane API 可用于将您在 Plane 中的数据与任何外部系统集成。创建令牌以开始使用。" + }, + "webhooks": { + "title": "尚未添加 webhook", + "description": "创建 webhook 以接收实时更新并自动执行操作。" + }, + "exports": { + "title": "尚无导出", + "description": "每次导出时,您都会在这里有一个副本以供参考。" + }, + "imports": { + "title": "尚无导入", + "description": "在这里查找所有以前的导入并下载它们。" + } + } + }, + + "profile": { + "label": "个人资料", + "page_label": "您的工作", + "work": "工作", + "details": { + "joined_on": "加入时间", + "time_zone": "时区" + }, + "stats": { + "workload": "工作量", + "overview": "概览", + "created": "已创建的工作项", + "assigned": "已分配的工作项", + "subscribed": "已订阅的工作项", + "state_distribution": { + "title": "按状态分类的工作项", + "empty": "创建工作项以在图表中查看按状态分类的工作项,以便更好地分析。" + }, + "priority_distribution": { + "title": "按优先级分类的工作项", + "empty": "创建工作项以在图表中查看按优先级分类的工作项,以便更好地分析。" + }, + "recent_activity": { + "title": "最近活动", + "empty": "我们找不到数据。请查看您的输入", + "button": "下载今天的活动", + "button_loading": "下载中" + } + }, + "actions": { + "profile": "个人资料", + "security": "安全", + "activity": "活动", + "appearance": "外观", + "notifications": "通知" + }, + "tabs": { + "summary": "摘要", + "assigned": "已分配", + "created": "已创建", + "subscribed": "已订阅", + "activity": "活动" + }, + "empty_state": { + "activity": { + "title": "尚无活动", + "description": "通过创建新工作项开始!为其添加详细信息和属性。在 Plane 中探索更多内容以查看您的活动。" + }, + "assigned": { + "title": "没有分配给您的工作项", + "description": "可以从这里跟踪分配给您的工作项。" + }, + "created": { + "title": "尚无工作项", + "description": "您创建的所有工作项都会出现在这里,直接在这里跟踪它们。" + }, + "subscribed": { + "title": "尚无工作项", + "description": "订阅您感兴趣的工作项,在这里跟踪所有这些工作项。" + } + } + }, + + "project_settings": { + "general": { + "enter_project_id": "输入项目 ID", + "please_select_a_timezone": "请选择时区", + "archive_project": { + "title": "归档项目", + "description": "归档项目将从您的侧边导航中取消列出您的项目,但您仍然可以从项目页面访问它。您可以随时恢复或删除项目。", + "button": "归档项目" + }, + "delete_project": { + "title": "删除项目", + "description": "删除项目时,该项目内的所有数据和资源将被永久删除且无法恢复。", + "button": "删除我的项目" + }, + "toast": { + "success": "项目更新成功", + "error": "项目无法更新。请重试。" + } + }, + "members": { + "label": "成员", + "project_lead": "项目负责人", + "default_assignee": "默认受理人", + "guest_super_permissions": { + "title": "为访客用户授予查看所有工作项的权限:", + "sub_heading": "这将允许访客查看所有项目工作项。" + }, + "invite_members": { + "title": "邀请成员", + "sub_heading": "邀请成员参与您的项目。", + "select_co_worker": "选择同事" + } + }, + "states": { + "describe_this_state_for_your_members": "为您的成员描述此状态。" + }, + "labels": { + "label_title": "标签标题", + "label_title_is_required": "标签标题为必填项", + "label_max_char": "标签名称不应超过255个字符", + "toast": { + "error": "更新标签时出错" + } + }, + "estimates": { + "title": "为我的项目启用估算", + "description": "它们有助于您传达团队的复杂性和工作量。" + }, + "automations": { + "label": "自动化", + "auto-archive": { + "title": "自动归档已关闭的工作项", + "description": "Plane 将自动归档已完成或已取消的工作项。", + "duration": "自动归档已关闭" + }, + "auto-close": { + "title": "自动关闭工作项", + "description": "Plane 将自动关闭尚未完成或取消的工作项。", + "duration": "自动关闭不活跃", + "auto_close_status": "自动关闭状态" + } + }, + + "empty_state": { + "labels": { + "title": "尚无标签", + "description": "创建标签以帮助组织和筛选项目中的工作项。" + }, + "estimates": { + "title": "尚无估算系统", + "description": "创建一组估算以传达每个工作项的工作量。", + "primary_button": "添加估算系统" + } + } + }, + + "project_cycles": { + "add_cycle": "添加周期", + "more_details": "更多详情", + "cycle": "周期", + "update_cycle": "更新周期", + "create_cycle": "创建周期", + "no_matching_cycles": "没有匹配的周期", + "remove_filters_to_see_all_cycles": "移除筛选器以查看所有周期", + "remove_search_criteria_to_see_all_cycles": "移除搜索条件以查看所有周期", + "only_completed_cycles_can_be_archived": "只能归档已完成的周期", + "active_cycle": { + "label": "活动周期", + "progress": "进度", + "chart": "燃尽图", + "priority_issue": "优先工作项", + "assignees": "受理人", + "issue_burndown": "工作项燃尽", + "ideal": "理想", + "current": "当前", + "labels": "标签" + }, + "upcoming_cycle": { + "label": "即将到来的周期" + }, + "completed_cycle": { + "label": "已完成的周期" + }, + "status": { + "days_left": "剩余天数", + "completed": "已完成", + "yet_to_start": "尚未开始", + "in_progress": "进行中", + "draft": "草稿" + }, + "action": { + "restore": { + "title": "恢复周期", + "success": { + "title": "周期已恢复", + "description": "周期已被恢复。" + }, + "failed": { + "title": "周期恢复失败", + "description": "无法恢复周期。请重试。" + } + }, + "favorite": { + "loading": "正在将周期添加到收藏", + "success": { + "description": "周期已添加到收藏。", + "title": "成功!" + }, + "failed": { + "description": "无法将周期添加到收藏。请重试。", + "title": "错误!" + } + }, + "unfavorite": { + "loading": "正在从收藏中移除周期", + "success": { + "description": "周期已从收藏中移除。", + "title": "成功!" + }, + "failed": { + "description": "无法从收藏中移除周期。请重试。", + "title": "错误!" + } + }, + "update": { + "loading": "正在更新周期", + "success": { + "description": "周期更新成功。", + "title": "成功!" + }, + "failed": { + "description": "更新周期时出错。请重试。", + "title": "错误!" + }, + "error": { + "already_exists": "在给定日期范围内已存在周期,如果您想创建草稿周期,可以通过移除两个日期来实现。" + } + } + }, + "empty_state": { + "general": { + "title": "在周期中分组和时间框定您的工作。", + "description": "将工作按时间框分解,从项目截止日期倒推设置日期,并作为团队取得切实的进展。", + "primary_button": { + "text": "设置您的第一个周期", + "comic": { + "title": "周期是重复的时间框。", + "description": "冲刺、迭代或您用于每周或每两周跟踪工作的任何其他术语都是一个周期。" + } + } + }, + "no_issues": { + "title": "尚未向周期添加工作项", + "description": "添加或创建您希望在此周期内时间框定和交付的工作项", + "primary_button": { + "text": "创建新工作项" + }, + "secondary_button": { + "text": "添加现有工作项" + } + }, + "completed_no_issues": { + "title": "周期中没有工作项", + "description": "周期中没有工作项。工作项已被转移或隐藏。要查看隐藏的工作项(如果有),请相应更新您的显示属性。" + }, + "active": { + "title": "没有活动周期", + "description": "活动周期包括其范围内包含今天日期的任何时期。在这里查找活动周期的进度和详细信息。" + }, + "archived": { + "title": "尚无已归档的周期", + "description": "为了整理您的项目,归档已完成的周期。归档后可以在这里找到它们。" + } + } + }, + + "project_issues": { + "empty_state": { + "no_issues": { + "title": "创建工作项并将其分配给某人,甚至是您自己", + "description": "将工作项视为工作、任务或待完成的工作。工作项及其子工作项通常是基于时间的、分配给团队成员的可执行项。您的团队通过创建、分配和完成工作项来推动项目实现其目标。", + "primary_button": { + "text": "创建您的第一个工作项", + "comic": { + "title": "工作项是 Plane 中的基本构建块。", + "description": "重新设计 Plane 界面、重塑公司品牌或启动新的燃料喷射系统都是可能包含子工作项的工作项示例。" + } + } + }, + "no_archived_issues": { + "title": "尚无已归档的工作项", + "description": "通过手动或自动化方式,您可以归档已完成或已取消的工作项。归档后可以在这里找到它们。", + "primary_button": { + "text": "设置自动化" + } + }, + "issues_empty_filter": { + "title": "未找到符合筛选条件的工作项", + "secondary_button": { + "text": "清除所有筛选条件" + } + } + } + }, + + "project_module": { + "add_module": "添加模块", + "update_module": "更新模块", + "create_module": "创建模块", + "archive_module": "归档模块", + "restore_module": "恢复模块", + "delete_module": "删除模块", + "empty_state": { + "general": { + "title": "将项目里程碑映射到模块,轻松跟踪汇总工作。", + "description": "属于逻辑层次结构父级的一组工作项形成一个模块。将其视为按项目里程碑跟踪工作的方式。它们有自己的周期和截止日期以及分析功能,帮助您了解距离里程碑的远近。", + "primary_button": { + "text": "构建您的第一个模块", + "comic": { + "title": "模块帮助按层次结构对工作进行分组。", + "description": "购物车模块、底盘模块和仓库模块都是这种分组的好例子。" + } + } + }, + "no_issues": { + "title": "模块中没有工作项", + "description": "创建或添加您想作为此模块一部分完成的工作项", + "primary_button": { + "text": "创建新工作项" + }, + "secondary_button": { + "text": "添加现有工作项" + } + }, + "archived": { + "title": "尚无已归档的模块", + "description": "为了整理您的项目,归档已完成或已取消的模块。归档后可以在这里找到它们。" + }, + "sidebar": { + "in_active": "此模块尚未激活。", + "invalid_date": "日期无效。请输入有效日期。" + } + }, + "quick_actions": { + "archive_module": "归档模块", + "archive_module_description": "只有已完成或已取消的\n模块可以归档。", + "delete_module": "删除模块" + }, + "toast": { + "copy": { + "success": "模块链接已复制到剪贴板" + }, + "delete": { + "success": "模块删除成功", + "error": "删除模块失败" + } + } + }, + + "project_views": { + "empty_state": { + "general": { + "title": "为您的项目保存筛选视图。根据需要创建任意数量", + "description": "视图是您经常使用或想要轻松访问的一组已保存的筛选条件。项目中的所有同事都可以看到每个人的视图,并选择最适合他们需求的视图。", + "primary_button": { + "text": "创建您的第一个视图", + "comic": { + "title": "视图基于工作项属性运作。", + "description": "您可以在此处创建一个视图,根据需要使用任意数量的属性作为筛选条件。" + } + } + }, + "filter": { + "title": "没有匹配的视图", + "description": "没有符合搜索条件的视图。\n创建一个新视图。" + } + } + }, + + "project_page": { + "empty_state": { + "general": { + "title": "写笔记、文档或完整的知识库。让 Plane 的 AI 助手 Galileo 帮助您开始", + "description": "页面是 Plane 中的思维记录空间。记录会议笔记,轻松格式化,嵌入工作项,使用组件库进行布局,并将它们全部保存在项目上下文中。要快速完成任何文档,可以通过快捷键或点击按钮调用 Plane 的 AI Galileo。", + "primary_button": { + "text": "创建您的第一个页面" + } + }, + "private": { + "title": "尚无私人页面", + "description": "在这里保存您的私人想法。准备好分享时,团队就在一键之遥。", + "primary_button": { + "text": "创建您的第一个页面" + } + }, + "public": { + "title": "尚无公共页面", + "description": "在这里查看与项目中所有人共享的页面。", + "primary_button": { + "text": "创建您的第一个页面" + } + }, + "archived": { + "title": "尚无已归档的页面", + "description": "归档不在您关注范围内的页面。需要时可以在这里访问它们。" + } + } + }, + + "command_k": { + "empty_state": { + "search": { + "title": "未找到结果" + } + } + }, + + "issue_relation": { + "empty_state": { + "search": { + "title": "未找到匹配的工作项" + }, + "no_issues": { + "title": "未找到工作项" + } + } + }, + + "issue_comment": { + "empty_state": { + "general": { + "title": "尚无评论", + "description": "评论可用作工作项的讨论和跟进空间" + } + } + }, + + "notification": { + "label": "收件箱", + "page_label": "{workspace} - 收件箱", + "options": { + "mark_all_as_read": "全部标记为已读", + "mark_read": "标记为已读", + "mark_unread": "标记为未读", + "refresh": "刷新", + "filters": "收件箱筛选", + "show_unread": "显示未读", + "show_snoozed": "显示已暂停", + "show_archived": "显示已归档", + "mark_archive": "归档", + "mark_unarchive": "取消归档", + "mark_snooze": "暂停", + "mark_unsnooze": "取消暂停" + }, + "toasts": { + "read": "通知已标记为已读", + "unread": "通知已标记为未读", + "archived": "通知已标记为已归档", + "unarchived": "通知已标记为未归档", + "snoozed": "通知已暂停", + "unsnoozed": "通知已取消暂停" + }, + "empty_state": { + "detail": { + "title": "选择以查看详情。" + }, + "all": { + "title": "没有分配的工作项", + "description": "在这里可以看到分配给您的工作项的更新" + }, + "mentions": { + "title": "没有分配的工作项", + "description": "在这里可以看到分配给您的工作项的更新" + } + }, + "tabs": { + "all": "全部", + "mentions": "提及" + }, + "filter": { + "assigned": "分配给我", + "created": "由我创建", + "subscribed": "由我订阅" + }, + "snooze": { + "1_day": "1 天", + "3_days": "3 天", + "5_days": "5 天", + "1_week": "1 周", + "2_weeks": "2 周", + "custom": "自定义" + } + }, + + "active_cycle": { + "empty_state": { + "progress": { + "title": "向周期添加工作项以查看其进度" + }, + "chart": { + "title": "向周期添加工作项以查看燃尽图。" + }, + "priority_issue": { + "title": "一目了然地观察周期中处理的高优先级工作项。" + }, + "assignee": { + "title": "为工作项添加负责人以查看按负责人划分的工作明细。" + }, + "label": { + "title": "为工作项添加标签以查看按标签划分的工作明细。" + } + } + }, + + "disabled_project": { + "empty_state": { + "inbox": { + "title": "项目未启用收集功能。", + "description": "收集功能帮助您管理项目的传入请求,并将其添加为工作流中的工作项。从项目设置启用收集功能以管理请求。", + "primary_button": { + "text": "管理功能" + } + }, + "cycle": { + "title": "此项目未启用周期功能。", + "description": "按时间框将工作分解,从项目截止日期倒推设置日期,并作为团队取得切实的进展。为您的项目启用周期功能以开始使用它们。", + "primary_button": { + "text": "管理功能" + } + }, + "module": { + "title": "项目未启用模块功能。", + "description": "模块是项目的基本构建块。从项目设置启用模块以开始使用它们。", + "primary_button": { + "text": "管理功能" + } + }, + "page": { + "title": "项目未启用页面功能。", + "description": "页面是项目的基本构建块。从项目设置启用页面以开始使用它们。", + "primary_button": { + "text": "管理功能" + } + }, + "view": { + "title": "项目未启用视图功能。", + "description": "视图是项目的基本构建块。从项目设置启用视图以开始使用它们。", + "primary_button": { + "text": "管理功能" + } + } + } + }, + "workspace_draft_issues": { + "draft_an_issue": "起草工作项", + "empty_state": { + "title": "半写的工作项,以及即将推出的评论将在这里显示。", + "description": "要试用此功能,请开始添加工作项并中途离开,或在下方创建您的第一个草稿。😉", + "primary_button": { + "text": "创建您的第一个草稿" + } + }, + "delete_modal": { + "title": "删除草稿", + "description": "您确定要删除此草稿吗?此操作无法撤消。" + }, + "toasts": { + "created": { + "success": "草稿已创建", + "error": "无法创建工作项。请重试。" + }, + "deleted": { + "success": "草稿已删除" + } + } + }, + + "stickies": { + "title": "您的便签", + "placeholder": "点击此处输入", + "all": "所有便签", + "no-data": "记下一个想法,捕捉一个灵感,或记录一个突发奇想。添加便签开始使用。", + "add": "添加便签", + "search_placeholder": "按标题搜索", + "delete": "删除便签", + "delete_confirmation": "您确定要删除此便签吗?", + "empty_state": { + "simple": "记下一个想法,捕捉一个灵感,或记录一个突发奇想。添加便签开始使用。", + "general": { + "title": "便签是您随手记下的快速笔记和待办事项。", + "description": "通过创建随时随地都可以访问的便签,轻松捕捉您的想法和创意。", + "primary_button": { + "text": "添加便签" + } + }, + "search": { + "title": "这与您的任何便签都不匹配。", + "description": "尝试使用不同的术语,或如果您确定\n搜索是正确的,请告诉我们。", + "primary_button": { + "text": "添加便签" + } + } + }, + "toasts": { + "errors": { + "wrong_name": "便签名称不能超过100个字符。", + "already_exists": "已存在一个没有描述的便签" + }, + "created": { + "title": "便签已创建", + "message": "便签已成功创建" + }, + "not_created": { + "title": "便签未创建", + "message": "无法创建便签" + }, + "updated": { + "title": "便签已更新", + "message": "便签已成功更新" + }, + "not_updated": { + "title": "便签未更新", + "message": "无法更新便签" + }, + "removed": { + "title": "便签已移除", + "message": "便签已成功移除" + }, + "not_removed": { + "title": "便签未移除", + "message": "无法移除便签" + } + } + }, + + "role_details": { + "guest": { + "title": "访客", + "description": "组织的外部成员可以被邀请为访客。" + }, + "member": { + "title": "成员", + "description": "可以在项目、周期和模块内读取、写入、编辑和删除实体" + }, + "admin": { + "title": "管理员", + "description": "在工作区内所有权限均设置为允许。" + } + }, + + "user_roles": { + "product_or_project_manager": "产品/项目经理", + "development_or_engineering": "开发/工程", + "founder_or_executive": "创始人/高管", + "freelancer_or_consultant": "自由职业者/顾问", + "marketing_or_growth": "市场/增长", + "sales_or_business_development": "销售/业务发展", + "support_or_operations": "支持/运营", + "student_or_professor": "学生/教授", + "human_resources": "人力资源", + "other": "其他" + }, + + "importer": { + "github": { + "title": "GitHub", + "description": "从 GitHub 仓库导入工作项并同步。" + }, + "jira": { + "title": "Jira", + "description": "从 Jira 项目和史诗导入工作项和史诗。" + } + }, + + "exporter": { + "csv": { + "title": "CSV", + "description": "将工作项导出为 CSV 文件。", + "short_description": "导出为 CSV" + }, + "excel": { + "title": "Excel", + "description": "将工作项导出为 Excel 文件。", + "short_description": "导出为 Excel" + }, + "xlsx": { + "title": "Excel", + "description": "将工作项导出为 Excel 文件。", + "short_description": "导出为 Excel" + }, + "json": { + "title": "JSON", + "description": "将工作项导出为 JSON 文件。", + "short_description": "导出为 JSON" + } + }, + "default_global_view": { + "all_issues": "所有工作项", + "assigned": "已分配", + "created": "已创建", + "subscribed": "已订阅" + }, + + "themes": { + "theme_options": { + "system_preference": { + "label": "系统偏好" + }, + "light": { + "label": "浅色" + }, + "dark": { + "label": "深色" + }, + "light_contrast": { + "label": "浅色高对比度" + }, + "dark_contrast": { + "label": "深色高对比度" + }, + "custom": { + "label": "自定义主题" + } + } + }, + "project_modules": { + "status": { + "backlog": "待办", + "planned": "已计划", + "in_progress": "进行中", + "paused": "已暂停", + "completed": "已完成", + "cancelled": "已取消" + }, + "layout": { + "list": "列表布局", + "board": "画廊布局", + "timeline": "时间线布局" + }, + "order_by": { + "name": "名称", + "progress": "进度", + "issues": "工作项数量", + "due_date": "截止日期", + "created_at": "创建日期", + "manual": "手动" + } + }, + + "cycle": { + "label": "{count, plural, one {周期} other {周期}}", + "no_cycle": "无周期" + }, + + "module": { + "label": "{count, plural, one {模块} other {模块}}", + "no_module": "无模块" + } } diff --git a/packages/i18n/src/store/index.ts b/packages/i18n/src/store/index.ts index d5bd3e598d6..78e1311adbf 100644 --- a/packages/i18n/src/store/index.ts +++ b/packages/i18n/src/store/index.ts @@ -1,8 +1,11 @@ import IntlMessageFormat from "intl-messageformat"; import get from "lodash/get"; -import { makeAutoObservable } from "mobx"; +import merge from "lodash/merge"; +import { makeAutoObservable, runInAction } from "mobx"; // constants import { FALLBACK_LANGUAGE, SUPPORTED_LANGUAGES, STORAGE_KEY } from "../constants"; +// core translations imports +import coreEn from "../locales/en/core.json"; // types import { TLanguage, ILanguageOption, ITranslations } from "../types"; @@ -12,42 +15,35 @@ import { TLanguage, ILanguageOption, ITranslations } from "../types"; * Uses IntlMessageFormat to format the translations */ export class TranslationStore { + // Core translations that are always loaded + private coreTranslations: ITranslations = { + en: coreEn, + }; // List of translations for each language private translations: ITranslations = {}; // Cache for IntlMessageFormat instances private messageCache: Map = new Map(); // Current language currentLocale: TLanguage = FALLBACK_LANGUAGE; + // Loading state + isLoading: boolean = true; + isInitialized: boolean = false; + // Set of loaded languages + private loadedLanguages: Set = new Set(); /** * Constructor for the TranslationStore class */ constructor() { makeAutoObservable(this); + // Initialize with core translations immediately + this.translations = this.coreTranslations; + // Initialize language this.initializeLanguage(); + // Load all the translations this.loadTranslations(); } - /** - * Loads translations from JSON files and initializes the message cache - */ - private async loadTranslations() { - try { - // dynamic import of translations - const translations = { - en: (await import("../locales/en/translations.json")).default, - fr: (await import("../locales/fr/translations.json")).default, - es: (await import("../locales/es/translations.json")).default, - ja: (await import("../locales/ja/translations.json")).default, - "zh-CN": (await import("../locales/zh-CN/translations.json")).default, - }; - this.translations = translations; - this.messageCache.clear(); // Clear cache when translations change - } catch (error) { - console.error("Failed to load translations:", error); - } - } - /** Initializes the language based on the local storage or browser language */ private initializeLanguage() { if (typeof window === "undefined") return; @@ -62,6 +58,100 @@ export class TranslationStore { this.setLanguage(browserLang); } + /** Loads the translations for the current language */ + private async loadTranslations(): Promise { + try { + // Set initialized to true (Core translations are already loaded) + runInAction(() => { + this.isInitialized = true; + }); + // Load current and fallback languages in parallel + await this.loadPrimaryLanguages(); + // Load all remaining languages in parallel + this.loadRemainingLanguages(); + } catch (error) { + console.error("Failed in translation initialization:", error); + runInAction(() => { + this.isLoading = false; + }); + } + } + + private async loadPrimaryLanguages(): Promise { + try { + // Load current and fallback languages in parallel + const languagesToLoad = new Set([this.currentLocale]); + // Add fallback language only if different from current + if (this.currentLocale !== FALLBACK_LANGUAGE) { + languagesToLoad.add(FALLBACK_LANGUAGE); + } + // Load all primary languages in parallel + const loadPromises = Array.from(languagesToLoad).map((lang) => this.loadLanguageTranslations(lang)); + await Promise.all(loadPromises); + // Update loading state + runInAction(() => { + this.isLoading = false; + }); + } catch (error) { + console.error("Failed to load primary languages:", error); + runInAction(() => { + this.isLoading = false; + }); + } + } + + private loadRemainingLanguages(): void { + const remainingLanguages = SUPPORTED_LANGUAGES.map((lang) => lang.value).filter( + (lang) => + !this.loadedLanguages.has(lang as TLanguage) && lang !== this.currentLocale && lang !== FALLBACK_LANGUAGE + ); + // Load all remaining languages in parallel + Promise.all(remainingLanguages.map((lang) => this.loadLanguageTranslations(lang as TLanguage))).catch((error) => { + console.error("Failed to load some remaining languages:", error); + }); + } + + private async loadLanguageTranslations(language: TLanguage): Promise { + // Skip if already loaded + if (this.loadedLanguages.has(language)) return; + + try { + const translations = await this.importLanguageFile(language); + runInAction(() => { + // Use lodash merge for deep merging + this.translations[language] = merge({}, this.coreTranslations[language] || {}, translations.default); + // Add to loaded languages + this.loadedLanguages.add(language); + // Clear cache + this.messageCache.clear(); + }); + } catch (error) { + console.error(`Failed to load translations for ${language}:`, error); + } + } + + /** + * Imports the translations for the given language + * @param language - The language to import the translations for + * @returns {Promise} + */ + private importLanguageFile(language: TLanguage): Promise { + switch (language) { + case "en": + return import("../locales/en/translations.json"); + case "fr": + return import("../locales/fr/translations.json"); + case "es": + return import("../locales/es/translations.json"); + case "ja": + return import("../locales/ja/translations.json"); + case "zh-CN": + return import("../locales/zh-CN/translations.json"); + default: + throw new Error(`Unsupported language: ${language}`); + } + } + /** Checks if the language is valid based on the supported languages */ private isValidLanguage(lang: string | null): lang is TLanguage { return lang !== null && this.availableLanguages.some((l) => l.value === lang); @@ -173,20 +263,26 @@ export class TranslationStore { * Sets the current language and updates the translations * @param lng - The new language */ - setLanguage(lng: TLanguage): void { + async setLanguage(lng: TLanguage): Promise { try { if (!this.isValidLanguage(lng)) { throw new Error(`Invalid language: ${lng}`); } - if (typeof window !== "undefined") { - localStorage.setItem(STORAGE_KEY, lng); + // Safeguard in case background loading failed + if (!this.loadedLanguages.has(lng)) { + await this.loadLanguageTranslations(lng); } - this.currentLocale = lng; - this.messageCache.clear(); // Clear cache when language changes + if (typeof window !== "undefined") { + localStorage.setItem(STORAGE_KEY, lng); document.documentElement.lang = lng; } + + runInAction(() => { + this.currentLocale = lng; + this.messageCache.clear(); // Clear cache when language changes + }); } catch (error) { console.error("Failed to set language:", error); } diff --git a/packages/types/src/inbox.d.ts b/packages/types/src/inbox.d.ts index 5ae6c160e80..30ad0120c81 100644 --- a/packages/types/src/inbox.d.ts +++ b/packages/types/src/inbox.d.ts @@ -2,23 +2,6 @@ import { TPaginationInfo } from "./common"; import { TIssuePriorities } from "./issues"; import { TIssue } from "./issues/base"; -enum EInboxIssueCurrentTab { - OPEN = "open", - CLOSED = "closed", -} - -enum EInboxIssueStatus { - PENDING = -2, - DECLINED = -1, - SNOOZED = 0, - ACCEPTED = 1, - DUPLICATE = 2, -} - -export type TInboxIssueCurrentTab = EInboxIssueCurrentTab; - -export type TInboxIssueStatus = EInboxIssueStatus; - // filters export type TInboxIssueFilterMemberKeys = "assignees" | "created_by"; @@ -38,10 +21,7 @@ export type TInboxIssueFilter = { // sorting filters export type TInboxIssueSortingKeys = "order_by" | "sort_by"; -export type TInboxIssueSortingOrderByKeys = - | "issue__created_at" - | "issue__updated_at" - | "issue__sequence_id"; +export type TInboxIssueSortingOrderByKeys = "issue__created_at" | "issue__updated_at" | "issue__sequence_id"; export type TInboxIssueSortingSortByKeys = "asc" | "desc"; @@ -78,17 +58,6 @@ export type TInboxDuplicateIssueDetails = { name: string; }; -export type TInboxIssue = { - id: string; - status: TInboxIssueStatus; - snoozed_till: Date | null; - duplicate_to: string | undefined; - source: string; - issue: TIssue; - created_by: string; - duplicate_issue_detail: TInboxDuplicateIssueDetails | undefined; -}; - export type TInboxIssuePaginationInfo = TPaginationInfo & { total_results: number; }; diff --git a/packages/types/src/issues.d.ts b/packages/types/src/issues.d.ts index f77408fd6c0..a630d0ba20a 100644 --- a/packages/types/src/issues.d.ts +++ b/packages/types/src/issues.d.ts @@ -15,7 +15,7 @@ import type { TIssueGroupByOptions, TIssueOrderByOptions, TIssueGroupingFilters, - TIssueExtraOptions + TIssueExtraOptions, } from "@plane/types"; export interface IIssueCycle { diff --git a/packages/types/src/project/projects.d.ts b/packages/types/src/project/projects.d.ts index 035762ab715..b04b9828468 100644 --- a/packages/types/src/project/projects.d.ts +++ b/packages/types/src/project/projects.d.ts @@ -1,3 +1,4 @@ +import { EUserProjectRoles } from "@plane/constants"; import type { IProjectViewProps, IUser, @@ -16,7 +17,7 @@ export interface IPartialProject { identifier: string; sort_order: number | null; logo_props: TLogoProps; - member_role: TUserPermissions | null; + member_role?: TUserPermissions | EUserProjectRoles | null; archived_at: string | null; workspace: IWorkspace | string; cycle_view: boolean; @@ -118,7 +119,7 @@ export interface IProjectMembership { } export interface IProjectBulkAddFormData { - members: { role: TUserPermissions; member_id: string }[]; + members: { role: TUserPermissions | EUserProjectRoles; member_id: string }[]; } export interface IGithubRepository { diff --git a/packages/types/src/views.d.ts b/packages/types/src/views.d.ts index 54e1a395c32..32b5889ca81 100644 --- a/packages/types/src/views.d.ts +++ b/packages/types/src/views.d.ts @@ -1,4 +1,4 @@ -import { EViewAccess } from "@/constants/views"; +import { EViewAccess } from "@plane/constants"; import { TLogoProps } from "./common"; import { IIssueDisplayFilterOptions, diff --git a/packages/types/src/workspace-views.d.ts b/packages/types/src/workspace-views.d.ts index 5bc90076761..eb207128d09 100644 --- a/packages/types/src/workspace-views.d.ts +++ b/packages/types/src/workspace-views.d.ts @@ -1,4 +1,4 @@ -import { EViewAccess } from "@/constants/views"; +import { EViewAccess } from "@plane/constants"; import { IWorkspaceViewProps, IIssueDisplayFilterOptions, diff --git a/packages/types/src/workspace.d.ts b/packages/types/src/workspace.d.ts index 500eaa7b513..021ea173e35 100644 --- a/packages/types/src/workspace.d.ts +++ b/packages/types/src/workspace.d.ts @@ -5,6 +5,7 @@ import type { IUserLite, IWorkspaceViewProps, } from "@plane/types"; +import { EUserWorkspaceRoles } from "@plane/constants"; // TODO: check if importing this over here causes circular dependency import { TUserPermissions } from "./enums"; export interface IWorkspace { @@ -69,7 +70,7 @@ export type Properties = { export interface IWorkspaceMember { id: string; member: IUserLite; - role: TUserPermissions; + role: TUserPermissions | EUserWorkspaceRoles; created_at?: string; avatar_url?: string; email?: string; @@ -87,7 +88,7 @@ export interface IWorkspaceMemberMe { default_props: IWorkspaceViewProps; id: string; member: string; - role: TUserPermissions; + role: TUserPermissions | EUserWorkspaceRoles; updated_at: Date; updated_by: string; view_props: IWorkspaceViewProps; diff --git a/space/app/layout.tsx b/space/app/layout.tsx index e457ae5d1d1..60a7287eb1c 100644 --- a/space/app/layout.tsx +++ b/space/app/layout.tsx @@ -15,7 +15,7 @@ export const metadata: Metadata = { url: "https://sites.plane.so/", }, keywords: - "software development, customer feedback, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration", + "software development, customer feedback, software, accelerate, code management, release management, project management, work item tracking, agile, scrum, kanban, collaboration", twitter: { site: "@planepowers", }, @@ -32,7 +32,9 @@ export default function RootLayout({ children }: { children: React.ReactNode }) - {children} + + <>{children} + ); diff --git a/space/app/provider.tsx b/space/app/provider.tsx index c3ab6673f5a..e8566bc9d92 100644 --- a/space/app/provider.tsx +++ b/space/app/provider.tsx @@ -2,6 +2,7 @@ import { FC, ReactNode } from "react"; // components +import { TranslationProvider } from "@plane/i18n"; import { InstanceProvider } from "@/lib/instance-provider"; import { StoreProvider } from "@/lib/store-provider"; import { ToastProvider } from "@/lib/toast-provider"; @@ -15,9 +16,11 @@ export const AppProvider: FC = (props) => { return ( - - {children} - + + + {children} + + ); }; diff --git a/space/core/components/account/auth-forms/auth-header.tsx b/space/core/components/account/auth-forms/auth-header.tsx index 8921c70b81c..95a539ddfc2 100644 --- a/space/core/components/account/auth-forms/auth-header.tsx +++ b/space/core/components/account/auth-forms/auth-header.tsx @@ -25,7 +25,7 @@ const Titles: TAuthHeaderDetails = { }, [EAuthModes.SIGN_UP]: { header: "View, comment, and do more", - subHeader: "Sign up or log in to work with Plane Issues and Pages.", + subHeader: "Sign up or log in to work with Plane work items and Pages.", }, }; @@ -38,7 +38,7 @@ export const AuthHeader: FC = (props) => { } return { - header: "Comment or react to issues", + header: "Comment or react to work itemss", subHeader: "Use plane to add your valuable inputs to features.", }; }; diff --git a/space/core/components/instance/instance-failure-view.tsx b/space/core/components/instance/instance-failure-view.tsx index 50f677c107c..ed4c36f3558 100644 --- a/space/core/components/instance/instance-failure-view.tsx +++ b/space/core/components/instance/instance-failure-view.tsx @@ -25,7 +25,7 @@ export const InstanceFailureView: FC = () => {

Unable to fetch instance details.

We were unable to fetch the details of the instance.
- Fret not, it might just be a connectivity issue. + Fret not, it might just be a connectivity work items.

diff --git a/space/core/components/issues/filters/applied-filters/filters-list.tsx b/space/core/components/issues/filters/applied-filters/filters-list.tsx index 4a4ed4eda22..65ac2960221 100644 --- a/space/core/components/issues/filters/applied-filters/filters-list.tsx +++ b/space/core/components/issues/filters/applied-filters/filters-list.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react"; import { X } from "lucide-react"; // types +import { useTranslation } from "@plane/i18n"; import { TFilters } from "@/types/issue"; // components import { AppliedPriorityFilters } from "./priority"; @@ -18,6 +19,7 @@ export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, " export const AppliedFiltersList: React.FC = observer((props) => { const { appliedFilters = {}, handleRemoveAllFilters, handleRemoveFilter } = props; + const { t } = useTranslation(); return (
@@ -72,7 +74,7 @@ export const AppliedFiltersList: React.FC = observer((props) => { onClick={handleRemoveAllFilters} className="flex items-center gap-2 rounded-md border border-custom-border-200 px-2 py-1 text-xs text-custom-text-300 hover:text-custom-text-200" > - Clear all + {t("common.clear_all")}
diff --git a/space/core/components/issues/filters/priority.tsx b/space/core/components/issues/filters/priority.tsx index 4f16f89ba83..d3b7dd67fbb 100644 --- a/space/core/components/issues/filters/priority.tsx +++ b/space/core/components/issues/filters/priority.tsx @@ -2,8 +2,9 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; -// ui import { ISSUE_PRIORITY_FILTERS } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; +// ui import { PriorityIcon } from "@plane/ui"; // components import { FilterHeader, FilterOption } from "./helpers"; @@ -18,6 +19,9 @@ type Props = { export const FilterPriority: React.FC = observer((props) => { const { appliedFilters, handleUpdate, searchQuery } = props; + // hooks + const { t } = useTranslation(); + const [previewEnabled, setPreviewEnabled] = useState(true); const appliedFiltersCount = appliedFilters?.length ?? 0; @@ -40,11 +44,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.titleTranslationKey)} /> )) ) : ( -

No matches found

+

{t("common.search.no_matches_found")}

)}
)} diff --git a/space/core/components/issues/issue-layouts/issue-layout-HOC.tsx b/space/core/components/issues/issue-layouts/issue-layout-HOC.tsx index cbb8aa551d6..193d7c55155 100644 --- a/space/core/components/issues/issue-layouts/issue-layout-HOC.tsx +++ b/space/core/components/issues/issue-layouts/issue-layout-HOC.tsx @@ -26,7 +26,7 @@ export const IssueLayoutHOC = observer((props: Props) => { } if (getGroupIssueCount(undefined, undefined, false) === 0) { - return
No Issues Found
; + return
No work items Found
; } return <>{props.children}; diff --git a/space/core/components/issues/issue-layouts/kanban/default.tsx b/space/core/components/issues/issue-layouts/kanban/default.tsx index 50f7b0f7138..6ad7b2e0f17 100644 --- a/space/core/components/issues/issue-layouts/kanban/default.tsx +++ b/space/core/components/issues/issue-layouts/kanban/default.tsx @@ -94,7 +94,7 @@ export const KanBan: React.FC = observer((props) => {
diff --git a/space/core/components/issues/issue-layouts/kanban/swimlanes.tsx b/space/core/components/issues/issue-layouts/kanban/swimlanes.tsx index 902dff670d0..48dd4047c34 100644 --- a/space/core/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/space/core/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -131,10 +131,14 @@ const SubGroupSwimlaneHeader: React.FC = observer( const subGroupByVisibilityToggle = visibilitySubGroupByGroupCount(groupCount, showEmptyGroup); if (subGroupByVisibilityToggle === false) return <>; - return (
- +
); })} @@ -262,7 +266,7 @@ const SubGroup: React.FC = observer((props) => {
{ } = props; const [isExpanded, setIsExpanded] = useState(true); const groupRef = useRef(null); + // hooks + const { t } = useTranslation(); const [intersectionElement, setIntersectionElement] = useState(null); @@ -85,7 +88,7 @@ export const ListGroup = observer((props: Props) => { } onClick={() => loadMoreIssues(group.id)} > - Load More ↓ + {t("common.load_more")} ↓
); diff --git a/space/core/components/issues/issue-layouts/properties/all-properties.tsx b/space/core/components/issues/issue-layouts/properties/all-properties.tsx index 66e9ab2960c..813a261ce23 100644 --- a/space/core/components/issues/issue-layouts/properties/all-properties.tsx +++ b/space/core/components/issues/issue-layouts/properties/all-properties.tsx @@ -137,7 +137,7 @@ export const IssueProperties: React.FC = observer((props) => { displayPropertyKey="sub_issue_count" shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!issue.sub_issues_count} > - +
{ + // hooks + const { t } = useTranslation(); const priority_detail = priority != null ? getIssuePriorityFilters(priority) : null; if (priority_detail === null) return <>; return ( - +
{priority_detail?.icon}
- {shouldShowName && {priority_detail?.title}} + {shouldShowName && {t(priority_detail?.titleTranslationKey || "")}}
); diff --git a/space/core/components/issues/issue-layouts/utils.tsx b/space/core/components/issues/issue-layouts/utils.tsx index 4bd85433ee8..97021feb4f5 100644 --- a/space/core/components/issues/issue-layouts/utils.tsx +++ b/space/core/components/issues/issue-layouts/utils.tsx @@ -50,7 +50,7 @@ export const getGroupByColumns = ( case "created_by": return getCreatedByColumns(member) as any; default: - if (includeNone) return [{ id: `All Issues`, name: `All Issues`, payload: {}, icon: undefined }]; + if (includeNone) return [{ id: `All Issues`, name: `All work items`, payload: {}, icon: undefined }]; } }; diff --git a/space/core/components/issues/navbar/layout-selection.tsx b/space/core/components/issues/navbar/layout-selection.tsx index 1b8344d0242..36c8f8e243d 100644 --- a/space/core/components/issues/navbar/layout-selection.tsx +++ b/space/core/components/issues/navbar/layout-selection.tsx @@ -5,6 +5,8 @@ import { observer } from "mobx-react"; import { useRouter, useSearchParams } from "next/navigation"; // ui import { SITES_ISSUE_LAYOUTS } from "@plane/constants"; +// plane i18n +import { useTranslation } from "@plane/i18n"; import { Tooltip } from "@plane/ui"; // helpers import { queryParamGenerator } from "@/helpers/query-param-generator"; @@ -19,6 +21,8 @@ type Props = { export const IssuesLayoutSelection: FC = observer((props) => { const { anchor } = props; + // hooks + const { t } = useTranslation(); // router const router = useRouter(); const searchParams = useSearchParams(); @@ -45,7 +49,7 @@ export const IssuesLayoutSelection: FC = observer((props) => { if (!layoutOptions[layout.key]) return; return ( - +
diff --git a/space/package.json b/space/package.json index ff174a1d333..207f95938d1 100644 --- a/space/package.json +++ b/space/package.json @@ -20,6 +20,7 @@ "@mui/material": "^5.14.1", "@plane/constants": "*", "@plane/editor": "*", + "@plane/i18n": "*", "@plane/types": "*", "@plane/ui": "*", "@plane/services": "*", diff --git a/space/public/site.webmanifest.json b/space/public/site.webmanifest.json index 4c32ec6e385..8885d137bbd 100644 --- a/space/public/site.webmanifest.json +++ b/space/public/site.webmanifest.json @@ -1,7 +1,7 @@ { "name": "Plane Space", "short_name": "Plane Space", - "description": "Plane helps you plan your issues, cycles, and product modules.", + "description": "Plane helps you plan your work items, cycles, and product modules.", "start_url": ".", "display": "standalone", "background_color": "#f9fafb", diff --git a/web/app/[workspaceSlug]/(projects)/analytics/header.tsx b/web/app/[workspaceSlug]/(projects)/analytics/header.tsx index fe55f8cbdc9..2c3247bd7f6 100644 --- a/web/app/[workspaceSlug]/(projects)/analytics/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/analytics/header.tsx @@ -41,7 +41,12 @@ export const WorkspaceAnalyticsHeader = observer(() => { } />} + link={ + } + /> + } /> {analytics_tab === "custom" ? ( diff --git a/web/app/[workspaceSlug]/(projects)/analytics/page.tsx b/web/app/[workspaceSlug]/(projects)/analytics/page.tsx index 5b5a3653081..8875e1465f3 100644 --- a/web/app/[workspaceSlug]/(projects)/analytics/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/analytics/page.tsx @@ -5,27 +5,40 @@ import { observer } from "mobx-react"; import { useSearchParams } from "next/navigation"; import { Tab } from "@headlessui/react"; // plane package imports -import { ANALYTICS_TABS } from "@plane/constants"; +import { ANALYTICS_TABS, EUserPermissionsLevel, EUserPermissions } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Header, EHeaderVariant } from "@plane/ui"; // components import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics"; import { PageHead } from "@/components/core"; -import { EmptyState } from "@/components/empty-state"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; +import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state"; // hooks -import { useCommandPalette, useEventTracker, useProject, useWorkspace } from "@/hooks/store"; +import { useCommandPalette, useEventTracker, useProject, useUserPermissions, useWorkspace } from "@/hooks/store"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; const AnalyticsPage = observer(() => { const searchParams = useSearchParams(); const analytics_tab = searchParams.get("analytics_tab"); + // plane imports + const { t } = useTranslation(); // store hooks const { toggleCreateProjectModal } = useCommandPalette(); const { setTrackElement } = useEventTracker(); const { workspaceProjectIds, loader } = useProject(); const { currentWorkspace } = useWorkspace(); + const { allowPermissions } = useUserPermissions(); + // helper hooks + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/onboarding/analytics" }); // derived values - const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Analytics` : undefined; + const pageTitle = currentWorkspace?.name + ? t(`workspace_analytics.page_label`, { workspace: currentWorkspace?.name }) + : undefined; + + // permissions + const canPerformEmptyStateActions = allowPermissions( + [EUserPermissions.ADMIN, EUserPermissions.MEMBER], + EUserPermissionsLevel.WORKSPACE + ); // TODO: refactor loader implementation return ( @@ -46,7 +59,7 @@ const AnalyticsPage = observer(() => { selected ? "text-custom-primary-100 " : "hover:text-custom-text-200" }`} > - {tab.title} + {t(tab.i18n_title)}
@@ -67,12 +80,22 @@ const AnalyticsPage = observer(() => {
) : ( - { - setTrackElement("Analytics empty state"); - toggleCreateProjectModal(true); - }} + { + setTrackElement("Analytics empty state"); + toggleCreateProjectModal(true); + }} + disabled={!canPerformEmptyStateActions} + /> + } /> )} diff --git a/web/app/[workspaceSlug]/(projects)/drafts/header.tsx b/web/app/[workspaceSlug]/(projects)/drafts/header.tsx index 68597b509a0..5e1cc66ef60 100644 --- a/web/app/[workspaceSlug]/(projects)/drafts/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/drafts/header.tsx @@ -3,7 +3,8 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { PenSquare } from "lucide-react"; -import { EIssuesStoreType } from "@plane/constants"; +import { EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // ui import { Breadcrumbs, Button, Header } from "@plane/ui"; // components @@ -12,8 +13,6 @@ import { CreateUpdateIssueModal } from "@/components/issues"; // hooks import { useProject, useUserPermissions, useWorkspaceDraftIssues } from "@/hooks/store"; -// plane-web -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; export const WorkspaceDraftHeader = observer(() => { // state @@ -22,7 +21,9 @@ export const WorkspaceDraftHeader = observer(() => { const { allowPermissions } = useUserPermissions(); const { paginationInfo } = useWorkspaceDraftIssues(); const { joinedProjectIds } = useProject(); - // check if user is authorized to create draft issue + + const { t } = useTranslation(); + // check if user is authorized to create draft work item const isAuthorizedUser = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.WORKSPACE @@ -42,7 +43,9 @@ export const WorkspaceDraftHeader = observer(() => { } />} + link={ + } /> + } /> {paginationInfo?.total_count && paginationInfo?.total_count > 0 ? ( @@ -62,7 +65,7 @@ export const WorkspaceDraftHeader = observer(() => { onClick={() => setIsDraftIssueModalOpen(true)} disabled={!isAuthorizedUser} > - Draft an issue + {t("workspace_draft_issues.draft_an_issue")} )} diff --git a/web/app/[workspaceSlug]/(projects)/header.tsx b/web/app/[workspaceSlug]/(projects)/header.tsx index a7a19afe6b0..16c106dad4a 100644 --- a/web/app/[workspaceSlug]/(projects)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/header.tsx @@ -7,11 +7,12 @@ import { Home } from "lucide-react"; import githubBlackImage from "/public/logos/github-black.png"; import githubWhiteImage from "/public/logos/github-white.png"; // ui +import { GITHUB_REDIRECTED } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, Header } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; // constants -import { GITHUB_REDIRECTED } from "@/constants/event-tracker"; // hooks import { useEventTracker } from "@/hooks/store"; @@ -19,6 +20,7 @@ export const WorkspaceDashboardHeader = () => { // hooks const { captureEvent } = useEventTracker(); const { resolvedTheme } = useTheme(); + const { t } = useTranslation(); return ( <> @@ -28,7 +30,9 @@ export const WorkspaceDashboardHeader = () => { } />} + link={ + } /> + } /> @@ -51,7 +55,7 @@ export const WorkspaceDashboardHeader = () => { width={16} alt="GitHub Logo" /> - Star us on GitHub + {t("home.star_us_on_github")} diff --git a/web/app/[workspaceSlug]/(projects)/notifications/page.tsx b/web/app/[workspaceSlug]/(projects)/notifications/page.tsx index 4ea0c8e4258..8afe768d80c 100644 --- a/web/app/[workspaceSlug]/(projects)/notifications/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/notifications/page.tsx @@ -4,21 +4,24 @@ import { useCallback, useEffect } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; +// plane imports +import { ENotificationLoader, ENotificationQueryParamType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // components import { LogoSpinner } from "@/components/common"; import { PageHead } from "@/components/core"; -import { EmptyState } from "@/components/empty-state"; +import { SimpleEmptyState } from "@/components/empty-state"; import { InboxContentRoot } from "@/components/inbox"; import { IssuePeekOverview } from "@/components/issues"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; -import { ENotificationLoader, ENotificationQueryParamType } from "@/constants/notification"; // hooks import { useIssueDetail, useUserPermissions, useWorkspace, useWorkspaceNotifications } from "@/hooks/store"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { useWorkspaceIssueProperties } from "@/hooks/use-workspace-issue-properties"; const WorkspaceDashboardPage = observer(() => { const { workspaceSlug } = useParams(); + // plane hooks + const { t } = useTranslation(); // hooks const { currentWorkspace } = useWorkspace(); const { @@ -31,11 +34,14 @@ const WorkspaceDashboardPage = observer(() => { const { fetchUserProjectInfo } = useUserPermissions(); const { setPeekIssue } = useIssueDetail(); // derived values - const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Inbox` : undefined; + const pageTitle = currentWorkspace?.name + ? t("notification.page_label", { workspace: currentWorkspace?.name }) + : undefined; const { workspace_slug, project_id, issue_id, is_inbox_issue } = notificationLiteByNotificationId(currentSelectedNotificationId); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/intake/issue-detail" }); - // fetching workspace issue properties + // fetching workspace work item properties useWorkspaceIssueProperties(workspaceSlug); // fetch workspace notifications @@ -82,7 +88,7 @@ const WorkspaceDashboardPage = observer(() => {
{!currentSelectedNotificationId ? (
- +
) : ( <> diff --git a/web/app/[workspaceSlug]/(projects)/page.tsx b/web/app/[workspaceSlug]/(projects)/page.tsx index 52f0faffefb..7a808decd2f 100644 --- a/web/app/[workspaceSlug]/(projects)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/page.tsx @@ -2,6 +2,7 @@ import { observer } from "mobx-react"; // components +import { useTranslation } from "@plane/i18n"; import { PageHead, AppHeader, ContentWrapper } from "@/components/core"; import { WorkspaceHomeView } from "@/components/home"; // hooks @@ -11,8 +12,9 @@ import { WorkspaceDashboardHeader } from "./header"; const WorkspaceDashboardPage = observer(() => { const { currentWorkspace } = useWorkspace(); + const { t } = useTranslation(); // derived values - const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Home` : undefined; + const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - ${t("home.title")}` : undefined; return ( <> diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx index bf1f88d15b4..f3b71990445 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/activity/page.tsx @@ -3,6 +3,8 @@ import { useState } from "react"; import { observer } from "mobx-react"; // ui +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Button } from "@plane/ui"; // components import { PageHead } from "@/components/core"; @@ -10,7 +12,6 @@ import { DownloadActivityButton, WorkspaceActivityListPage } from "@/components/ // hooks import { useUserPermissions } from "@/hooks/store"; // plane-web constants -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const PER_PAGE = 100; @@ -21,6 +22,8 @@ const ProfileActivityPage = observer(() => { const [resultsCount, setResultsCount] = useState(0); // router const { allowPermissions } = useUserPermissions(); + //hooks + const { t } = useTranslation(); const updateTotalPages = (count: number) => setTotalPages(count); @@ -50,7 +53,7 @@ const ProfileActivityPage = observer(() => {
-

Recent activity

+

{t("profile.stats.recent_activity.title")}

{canDownloadActivity && }
@@ -58,7 +61,7 @@ const ProfileActivityPage = observer(() => { {pageCount < totalPages && resultsCount !== 0 && (
)} diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/header.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/header.tsx index 13a944c8819..e97b4751f3a 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/header.tsx @@ -6,15 +6,15 @@ import { observer } from "mobx-react"; import Link from "next/link"; import { useParams } from "next/navigation"; import { ChevronDown, PanelRight } from "lucide-react"; +import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IUserProfileProjectSegregation } from "@plane/types"; import { Breadcrumbs, Header, CustomMenu, UserActivityIcon } from "@plane/ui"; import { BreadcrumbLink } from "@/components/common"; // components import { ProfileIssuesFilter } from "@/components/profile"; -import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile"; import { cn } from "@/helpers/common.helper"; import { useAppTheme, useUser, useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; type TUserProfileHeader = { userProjectsData: IUserProfileProjectSegregation | undefined; @@ -30,6 +30,7 @@ export const UserProfileHeader: FC = observer((props) => { const { toggleProfileSidebar, profileSidebarCollapsed } = useAppTheme(); const { data: currentUser } = useUser(); const { workspaceUserInfo, allowPermissions } = useUserPermissions(); + const { t } = useTranslation(); // derived values const isAuthorized = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], @@ -44,7 +45,7 @@ export const UserProfileHeader: FC = observer((props) => { const isCurrentUser = currentUser?.id === userId; - const breadcrumbLabel = `${isCurrentUser ? "Your" : userName} Work`; + const breadcrumbLabel = isCurrentUser ? t("profile.page_label") : `${userName} ${t("profile.work")}`; return (
@@ -86,7 +87,7 @@ export const UserProfileHeader: FC = observer((props) => { href={`/${workspaceSlug}/profile/${userId}/${tab.route}`} className="w-full text-custom-text-300" > - {tab.label} + {t(tab.i18n_label)} ))} diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx index d6743e8f2ba..fb223f2dd4d 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx @@ -4,16 +4,15 @@ import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; import useSWR from "swr"; // components +import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { AppHeader, ContentWrapper } from "@/components/core"; import { ProfileSidebar } from "@/components/profile"; // constants import { USER_PROFILE_PROJECT_SEGREGATION } from "@/constants/fetch-keys"; -import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile"; // hooks import { useUserPermissions } from "@/hooks/store"; import useSize from "@/hooks/use-window-size"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; // local components import { UserService } from "@/services/user.service"; import { UserProfileHeader } from "./header"; @@ -66,7 +65,7 @@ const UseProfileLayout: React.FC = observer((props) => { diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/mobile-header.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/mobile-header.tsx index 21d230347c9..49fa5d2e5a1 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/mobile-header.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/mobile-header.tsx @@ -6,21 +6,30 @@ import { useParams } from "next/navigation"; // icons import { ChevronDown } from "lucide-react"; // plane constants -import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants"; +import { + EIssueLayoutTypes, + EIssueFilterType, + EIssuesStoreType, + ISSUE_LAYOUTS, + ISSUE_DISPLAY_FILTERS_BY_PAGE, +} from "@plane/constants"; +// plane i18n +import { useTranslation } from "@plane/i18n"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions, TIssueLayouts } from "@plane/types"; // ui import { CustomMenu } from "@plane/ui"; // components -import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues"; -// constants -import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue"; +import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, IssueLayoutIcon } from "@/components/issues"; + // helpers import { isIssueFilterActive } from "@/helpers/filter.helper"; // hooks import { useIssues, useLabel } from "@/hooks/store"; export const ProfileIssuesMobileHeader = observer(() => { + // plane i18n + const { t } = useTranslation(); // router const { workspaceSlug, userId } = useParams(); // store hook @@ -112,7 +121,7 @@ export const ProfileIssuesMobileHeader = observer(() => { placement="bottom-start" customButton={
- Layout + {t("common.layout")}
} @@ -129,19 +138,19 @@ export const ProfileIssuesMobileHeader = observer(() => { }} className="flex items-center gap-2" > - -
{layout.title}
+ +
{t(layout.i18n_title)}
); })}
- Filters + {t("common.filters")}
} @@ -149,7 +158,7 @@ export const ProfileIssuesMobileHeader = observer(() => { > {
- Display + {t("common.display")}
} > = (props) => { : "border-transparent" }`} > - {t(tab.label)} + {t(tab.i18n_label)} ))} diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/page.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/page.tsx index 480e30aed37..0db37129c27 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/page.tsx @@ -3,6 +3,8 @@ import { useParams } from "next/navigation"; import useSWR from "swr"; // types +import { GROUP_CHOICES } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IUserStateDistribution, TStateGroups } from "@plane/types"; // components import { ContentWrapper } from "@plane/ui"; @@ -16,7 +18,6 @@ import { } from "@/components/profile"; // constants import { USER_PROFILE_DATA } from "@/constants/fetch-keys"; -import { GROUP_CHOICES } from "@/constants/project"; // services import { UserService } from "@/services/user.service"; @@ -26,6 +27,7 @@ const userService = new UserService(); export default function ProfileOverviewPage() { const { workspaceSlug, userId } = useParams(); + const { t } = useTranslation(); const { data: userProfile } = useSWR( workspaceSlug && userId ? USER_PROFILE_DATA(workspaceSlug.toString(), userId.toString()) : null, workspaceSlug && userId ? () => userService.getUserProfileData(workspaceSlug.toString(), userId.toString()) : null @@ -40,7 +42,7 @@ export default function ProfileOverviewPage() { return ( <> - + diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/header.tsx index 8ad2198e6bb..7f2799cd632 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/header.tsx @@ -27,7 +27,7 @@ const PROJECT_ARCHIVES_BREADCRUMB_LIST: { }; } = { issues: { - label: "Issues", + label: "Work items", href: "/issues", icon: LayersIcon, }, @@ -92,7 +92,7 @@ export const ProjectArchivesHeader: FC = observer((props: TProps) => { {activeTab === "issues" && issueCount && issueCount > 0 ? ( 1 ? "issues" : "issue"} in project's archived`} + tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "work items" : "work item"} in project's archived`} position="bottom" > diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx index c68185676f2..7b9e84a9e1f 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx @@ -52,7 +52,7 @@ export const ProjectArchivedIssueDetailsHeader = observer(() => { link={ } /> } diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/page.tsx index b2298d540c4..afa6c0d7241 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(list)/page.tsx @@ -15,7 +15,7 @@ const ProjectArchivedIssuesPage = observer(() => { const { getProjectById } = useProject(); // derived values const project = projectId ? getProjectById(projectId.toString()) : undefined; - const pageTitle = project?.name && `${project?.name} - Archived issues`; + const pageTitle = project?.name && `${project?.name} - Archived work items`; return ( <> 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 e93965a3c90..508da58a212 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 @@ -7,7 +7,16 @@ import { useParams } from "next/navigation"; // icons import { ArrowRight, PanelRight } from "lucide-react"; // plane constants -import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants"; +import { + EIssueLayoutTypes, + EIssueFilterType, + EIssuesStoreType, + ISSUE_DISPLAY_FILTERS_BY_PAGE, + EUserPermissions, + EUserPermissionsLevel, +} from "@plane/constants"; +// i18n +import { useTranslation } from "@plane/i18n"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui @@ -16,8 +25,6 @@ import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip, Header } from " import { ProjectAnalyticsModal } from "@/components/analytics"; import { BreadcrumbLink } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; -// constants -import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; // helpers import { cn } from "@/helpers/common.helper"; import { isIssueFilterActive } from "@/helpers/filter.helper"; @@ -39,7 +46,6 @@ import useLocalStorage from "@/hooks/use-local-storage"; import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => { // router @@ -72,6 +78,8 @@ export const CycleIssuesHeader: React.FC = observer(() => { projectId: string; cycleId: string; }; + // i18n + const { t } = useTranslation(); // store hooks const { issuesFilter: { issueFilters, updateFilters }, @@ -184,7 +192,7 @@ export const CycleIssuesHeader: React.FC = observer(() => { type="text" link={ } /> @@ -203,7 +211,7 @@ export const CycleIssuesHeader: React.FC = observer(() => { 1 ? "issues" : "issue" + issuesCount > 1 ? "work items" : "work item" } in this cycle`} position="bottom" > @@ -239,7 +247,7 @@ export const CycleIssuesHeader: React.FC = observer(() => { selectedLayout={activeLayout} /> @@ -247,7 +255,7 @@ export const CycleIssuesHeader: React.FC = observer(() => { filters={issueFilters?.filters ?? {}} handleFiltersUpdate={handleFiltersUpdate} layoutDisplayFiltersOptions={ - activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined + activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined } displayFilters={issueFilters?.displayFilters ?? {}} handleDisplayFiltersUpdate={handleDisplayFilters} @@ -258,10 +266,10 @@ 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 1f85665fa86..31eb5b249fd 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 @@ -5,32 +5,42 @@ import { useParams } from "next/navigation"; // icons import { Calendar, ChevronDown, Kanban, List } from "lucide-react"; // plane constants -import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants"; +import { + EIssueLayoutTypes, + EIssueFilterType, + EIssuesStoreType, + ISSUE_LAYOUTS, + ISSUE_DISPLAY_FILTERS_BY_PAGE, +} from "@plane/constants"; +// i18n +import { useTranslation } from "@plane/i18n"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui import { CustomMenu } from "@plane/ui"; // components import { ProjectAnalyticsModal } from "@/components/analytics"; -import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues"; -// constants -import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue"; +import { DisplayFiltersSelection, FilterSelection, FiltersDropdown, IssueLayoutIcon } from "@/components/issues"; // helpers import { isIssueFilterActive } from "@/helpers/filter.helper"; // hooks import { useIssues, useCycle, useProjectState, useLabel, useMember, useProject } from "@/hooks/store"; export const CycleIssuesMobileHeader = () => { + // i18n + const { t } = useTranslation(); + const [analyticsModal, setAnalyticsModal] = useState(false); const { getCycleById } = useCycle(); const layouts = [ - { key: "list", title: "List", icon: List }, - { key: "kanban", title: "Board", icon: Kanban }, - { key: "calendar", title: "Calendar", icon: Calendar }, + { key: "list", titleTranslationKey: "issue.layouts.list", icon: List }, + { key: "kanban", titleTranslationKey: "issue.layouts.kanban", icon: Kanban }, + { key: "calendar", titleTranslationKey: "issue.layouts.calendar", icon: Calendar }, ]; const { workspaceSlug, projectId, cycleId } = useParams(); const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined; + // store hooks const { currentProjectDetails } = useProject(); const { @@ -123,7 +133,9 @@ export const CycleIssuesMobileHeader = () => { maxHeight={"md"} className="flex flex-grow justify-center text-custom-text-200 text-sm" placement="bottom-start" - customButton={Layout} + customButton={ + {t("common.layout")} + } customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm" closeOnSelect > @@ -135,18 +147,18 @@ export const CycleIssuesMobileHeader = () => { }} className="flex items-center gap-2" > - -
{layout.title}
+ +
{t(layout.titleTranslationKey)}
))}
- Filters + {t("common.filters")} } @@ -156,7 +168,7 @@ export const CycleIssuesMobileHeader = () => { filters={issueFilters?.filters ?? {}} handleFiltersUpdate={handleFiltersUpdate} layoutDisplayFiltersOptions={ - activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined + activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined } displayFilters={issueFilters?.displayFilters ?? {}} handleDisplayFiltersUpdate={handleDisplayFilters} @@ -170,18 +182,18 @@ export const CycleIssuesMobileHeader = () => {
- Display + {t("common.display")} } > { onClick={() => setAnalyticsModal(true)} className="flex flex-grow justify-center text-custom-text-200 text-sm border-l border-custom-border-200" > - Analytics + {t("common.analytics")}
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx index 79540238318..2339c2731c2 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx @@ -3,6 +3,8 @@ import { FC } from "react"; import { observer } from "mobx-react"; // ui +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, Button, ContrastIcon, Header } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; @@ -13,7 +15,6 @@ import { useAppRouter } from "@/hooks/use-app-router"; // plane web import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; // constants -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; export const CyclesListHeader: FC = observer(() => { // router @@ -23,6 +24,7 @@ export const CyclesListHeader: FC = observer(() => { const { setTrackElement } = useEventTracker(); const { allowPermissions } = useUserPermissions(); const { currentProjectDetails, loader } = useProject(); + const { t } = useTranslation(); const canUserCreateCycle = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], @@ -36,7 +38,12 @@ export const CyclesListHeader: FC = observer(() => { } />} + link={ + } + /> + } /> @@ -51,7 +58,8 @@ export const CyclesListHeader: FC = observer(() => { toggleCreateCycleModal(true); }} > -
Add
Cycle +
{t("add")}
+
{t("project_cycles.add_cycle")}
) : ( diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx index 5b1793d5d1f..d6b4eea15bc 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx @@ -3,20 +3,22 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; -// types +// plane imports +import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TCycleFilters } from "@plane/types"; // components import { Header, EHeaderVariant } from "@plane/ui"; import { PageHead } from "@/components/core"; import { CyclesView, CycleCreateUpdateModal, CycleAppliedFiltersList } from "@/components/cycles"; -import { EmptyState } from "@/components/empty-state"; +import { ComicBoxButton, DetailedEmptyState } from "@/components/empty-state"; import { CycleModuleListLayout } from "@/components/ui"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; // helpers import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks -import { useEventTracker, useCycle, useProject, useCycleFilter } from "@/hooks/store"; +import { useEventTracker, useCycle, useProject, useCycleFilter, useUserPermissions } from "@/hooks/store"; +import { useAppRouter } from "@/hooks/use-app-router"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; const ProjectCyclesPage = observer(() => { // states @@ -26,13 +28,23 @@ const ProjectCyclesPage = observer(() => { const { currentProjectCycleIds, loader } = useCycle(); const { getProjectById, currentProjectDetails } = useProject(); // router + const router = useAppRouter(); const { workspaceSlug, projectId } = useParams(); + // plane hooks + const { t } = useTranslation(); // cycle filters hook const { clearAllFilters, currentProjectFilters, updateFilters } = useCycleFilter(); + const { allowPermissions } = useUserPermissions(); // derived values const totalCycles = currentProjectCycleIds?.length ?? 0; const project = projectId ? getProjectById(projectId?.toString()) : undefined; - const pageTitle = project?.name ? `${project?.name} - Cycles` : undefined; + const pageTitle = project?.name ? `${project?.name} - ${t("cycles.label", { count: 2 })}` : undefined; + const hasAdminLevelPermission = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); + const hasMemberLevelPermission = allowPermissions( + [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER], + EUserPermissionsLevel.PROJECT + ); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/cycles" }); const handleRemoveFilter = (key: keyof TCycleFilters, value: string | null) => { if (!projectId) return; @@ -50,9 +62,17 @@ const ProjectCyclesPage = observer(() => { if (currentProjectDetails?.cycle_view === false) return (
- { + router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`); + }, + disabled: !hasAdminLevelPermission, + }} />
); @@ -71,12 +91,22 @@ const ProjectCyclesPage = observer(() => { /> {totalCycles === 0 ? (
- { - setTrackElement("Cycle empty state"); - setCreateModal(true); - }} + { + setTrackElement("Cycle empty state"); + setCreateModal(true); + }} + disabled={!hasMemberLevelPermission} + /> + } />
) : ( 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 c4cdb3bdcb1..2200b31f123 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 @@ -4,7 +4,9 @@ import { FC, useCallback } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // plane constants -import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants"; +import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants"; +// i18n +import { useTranslation } from "@plane/i18n"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui @@ -12,8 +14,6 @@ import { Breadcrumbs, LayersIcon, Tooltip } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; -// constants -import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; // helpers import { isIssueFilterActive } from "@/helpers/filter.helper"; // hooks @@ -24,6 +24,8 @@ import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; // FIXME: Deprecated. Remove it export const ProjectDraftIssueHeader: FC = observer(() => { + // i18n + const { t } = useTranslation(); // router const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string }; // store hooks @@ -96,14 +98,17 @@ export const ProjectDraftIssueHeader: FC = observer(() => { } /> + } + /> } /> {issueCount && issueCount > 0 ? ( 1 ? "issues" : "issue"} in project's draft`} + tooltipContent={`There are ${issueCount} ${issueCount > 1 ? "work items" : "work item"} in project's draft`} position="bottom" > @@ -119,14 +124,18 @@ export const ProjectDraftIssueHeader: FC = observer(() => { onChange={(layout) => handleLayoutChange(layout)} selectedLayout={activeLayout} /> - + { moduleViewDisabled={!currentProjectDetails?.module_view} /> - + { const { getProjectById } = useProject(); // derived values const project = projectId ? getProjectById(projectId.toString()) : undefined; - const pageTitle = project?.name ? `${project?.name} - Draft Issues` : undefined; + const pageTitle = project?.name ? `${project?.name} - Draft work items` : undefined; return ( <> @@ -30,7 +30,7 @@ const ProjectDraftIssuesPage = observer(() => { className="flex items-center gap-1.5 rounded-full border border-custom-border-200 px-3 py-1.5 text-xs" > - Draft Issues + Draft work items
@@ -40,4 +40,4 @@ const ProjectDraftIssuesPage = observer(() => { ); }); -export default ProjectDraftIssuesPage; \ No newline at end of file +export default ProjectDraftIssuesPage; diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/page.tsx index 36aa37e302c..5cb8509e0a3 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/page.tsx @@ -2,41 +2,62 @@ import { observer } from "mobx-react"; // components import { useParams, useSearchParams } from "next/navigation"; +import { EUserPermissionsLevel } from "@plane/constants"; +import { EUserProjectRoles } from "@plane/constants/src/user"; +import { useTranslation } from "@plane/i18n"; import { PageHead } from "@/components/core"; -import { EmptyState } from "@/components/empty-state"; +import { DetailedEmptyState } from "@/components/empty-state"; import { InboxIssueRoot } from "@/components/inbox"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; // helpers import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper"; // hooks -import { useProject } from "@/hooks/store"; +import { useProject, useUserPermissions } from "@/hooks/store"; +import { useAppRouter } from "@/hooks/use-app-router"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; const ProjectInboxPage = observer(() => { /// router + const router = useAppRouter(); const { workspaceSlug, projectId } = useParams(); - const searchParams = useSearchParams(); - const navigationTab = searchParams.get("currentTab"); const inboxIssueId = searchParams.get("inboxIssueId"); - + // plane hooks + const { t } = useTranslation(); // hooks const { currentProjectDetails } = useProject(); + const { allowPermissions } = useUserPermissions(); + // derived values + const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/intake" }); // No access to inbox if (currentProjectDetails?.inbox_view === false) return (
- { + router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`); + }, + disabled: !canPerformEmptyStateActions, + }} />
); // derived values - const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Intake` : "Plane - Intake"; + const pageTitle = currentProjectDetails?.name + ? t("inbox_issue.page_label", { + workspace: currentProjectDetails?.name, + }) + : t("inbox_issue.page_label", { + workspace: "Plane", + }); const currentNavigationTab = navigationTab ? navigationTab === "open" diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx index 5b68ae688ed..ffc50ccb472 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx @@ -5,6 +5,8 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { useTheme } from "next-themes"; import useSWR from "swr"; +// i18n +import { useTranslation } from "@plane/i18n"; // ui import { Loader } from "@plane/ui"; // components @@ -19,6 +21,8 @@ import emptyIssueDark from "@/public/empty-state/search/issues-dark.webp"; import emptyIssueLight from "@/public/empty-state/search/issues-light.webp"; const IssueDetailsPage = observer(() => { + // i18n + const { t } = useTranslation(); // router const router = useAppRouter(); const { workspaceSlug, projectId, issueId } = useParams(); @@ -31,7 +35,7 @@ const IssueDetailsPage = observer(() => { } = useIssueDetail(); const { getProjectById } = useProject(); const { toggleIssueDetailSidebar, issueDetailSidebarCollapsed } = useAppTheme(); - // fetching issue details + // fetching work item details const { isLoading, error } = useSWR( workspaceSlug && projectId && issueId ? `ISSUE_DETAIL_${workspaceSlug}_${projectId}_${issueId}` : null, workspaceSlug && projectId && issueId @@ -64,10 +68,10 @@ const IssueDetailsPage = observer(() => { {error ? ( router.push(`/${workspaceSlug}/projects/${projectId}/issues`), }} /> diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/header.tsx index 699c9865fff..c737e151847 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/header.tsx @@ -2,6 +2,8 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; +// i18n +import { useTranslation } from "@plane/i18n"; // ui import { Breadcrumbs, LayersIcon, Header } from "@plane/ui"; // components @@ -14,6 +16,7 @@ import { useAppRouter } from "@/hooks/use-app-router"; import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; export const ProjectIssueDetailsHeader = observer(() => { + const { t } = useTranslation(); // router const router = useAppRouter(); const { workspaceSlug, projectId, issueId } = useParams(); @@ -37,7 +40,7 @@ export const ProjectIssueDetailsHeader = observer(() => { link={ } /> } 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 29f2637b76e..6b86cd88d95 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 @@ -6,26 +6,39 @@ import { useParams } from "next/navigation"; // icons import { Calendar, ChevronDown, Kanban, List } from "lucide-react"; // plane constants -import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants"; +import { + EIssueLayoutTypes, + EIssueFilterType, + EIssuesStoreType, + ISSUE_LAYOUTS, + ISSUE_DISPLAY_FILTERS_BY_PAGE, +} from "@plane/constants"; +// i18n +import { useTranslation } from "@plane/i18n"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui import { CustomMenu } from "@plane/ui"; // components import { ProjectAnalyticsModal } from "@/components/analytics"; -import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues/issue-layouts"; -// constants -import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue"; +import { + DisplayFiltersSelection, + FilterSelection, + FiltersDropdown, + IssueLayoutIcon, +} from "@/components/issues/issue-layouts"; // helpers import { isIssueFilterActive } from "@/helpers/filter.helper"; // hooks import { useIssues, useLabel, useMember, useProject, useProjectState } from "@/hooks/store"; export const ProjectIssuesMobileHeader = observer(() => { + // i18n + const { t } = useTranslation(); const layouts = [ - { key: "list", title: "List", icon: List }, - { key: "kanban", title: "Board", icon: Kanban }, - { key: "calendar", title: "Calendar", icon: Calendar }, + { key: "list", titleTranslationKey: "issue.layouts.list", icon: List }, + { key: "kanban", titleTranslationKey: "issue.layouts.kanban", icon: Kanban }, + { key: "calendar", titleTranslationKey: "issue.layouts.calendar", icon: Calendar }, ]; const [analyticsModal, setAnalyticsModal] = useState(false); const { workspaceSlug, projectId } = useParams() as { @@ -104,7 +117,7 @@ export const ProjectIssuesMobileHeader = observer(() => { placement="bottom-start" customButton={
- Layout + {t("common.layout")}
} @@ -119,18 +132,18 @@ export const ProjectIssuesMobileHeader = observer(() => { }} className="flex items-center gap-2" > - -
{layout.title}
+ +
{t(layout.titleTranslationKey)}
))}
- Filters + {t("common.filters")} } @@ -142,7 +155,7 @@ export const ProjectIssuesMobileHeader = observer(() => { displayFilters={issueFilters?.displayFilters ?? {}} handleDisplayFiltersUpdate={handleDisplayFilters} layoutDisplayFiltersOptions={ - activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined + activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined } labels={projectLabels} memberIds={projectMemberIds ?? undefined} @@ -154,18 +167,18 @@ export const ProjectIssuesMobileHeader = observer(() => {
- Display + {t("common.display")} } > { onClick={() => setAnalyticsModal(true)} className="flex flex-grow justify-center border-l border-custom-border-200 text-sm text-custom-text-200" > - Analytics + {t("common.analytics")}
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx index 7ea73bb1c94..6b83f367b1e 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/page.tsx @@ -3,6 +3,8 @@ import { observer } from "mobx-react"; import Head from "next/head"; import { useParams } from "next/navigation"; +// i18n +import { useTranslation } from "@plane/i18n"; // components import { PageHead } from "@/components/core"; import { ProjectLayoutRoot } from "@/components/issues"; @@ -11,6 +13,8 @@ import { useProject } from "@/hooks/store"; const ProjectIssuesPage = observer(() => { const { projectId } = useParams(); + // i18n + const { t } = useTranslation(); // store const { getProjectById } = useProject(); @@ -20,13 +24,15 @@ const ProjectIssuesPage = observer(() => { // derived values const project = getProjectById(projectId.toString()); - const pageTitle = project?.name ? `${project?.name} - Issues` : undefined; + const pageTitle = project?.name ? `${project?.name} - ${t("issue.label", { count: 2 })}` : undefined; // Count is for pluralization return ( <> - {project?.name} - Issues + + {project?.name} - {t("issue.label", { count: 2 })} +
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 0107d7bfa97..3c5eb7cc729 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 @@ -7,7 +7,14 @@ import { useParams } from "next/navigation"; // icons import { ArrowRight, PanelRight } from "lucide-react"; // plane constants -import { EIssueLayoutTypes, EIssuesStoreType, EIssueFilterType } from "@plane/constants"; +import { + EIssueLayoutTypes, + EIssuesStoreType, + EIssueFilterType, + ISSUE_DISPLAY_FILTERS_BY_PAGE, + EUserPermissions, + EUserPermissionsLevel, +} from "@plane/constants"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui @@ -16,8 +23,6 @@ import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip, Header } from "@pla import { ProjectAnalyticsModal } from "@/components/analytics"; import { BreadcrumbLink } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; -// constants -import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; // helpers import { cn } from "@/helpers/common.helper"; import { isIssueFilterActive } from "@/helpers/filter.helper"; @@ -40,7 +45,6 @@ import useLocalStorage from "@/hooks/use-local-storage"; import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const ModuleDropdownOption: React.FC<{ moduleId: string }> = ({ moduleId }) => { // router @@ -202,7 +206,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => { 1 ? "issues" : "issue" + issuesCount > 1 ? "work items" : "work item" } in this module`} position="bottom" > @@ -247,7 +251,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => { displayFilters={issueFilters?.displayFilters ?? {}} handleDisplayFiltersUpdate={handleDisplayFilters} layoutDisplayFiltersOptions={ - activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined + activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined } labels={projectLabels} memberIds={projectMemberIds ?? undefined} @@ -259,7 +263,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => { { ) : ( 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 63cf32ef6ef..1f000bae2e4 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 @@ -6,16 +6,27 @@ import { useParams } from "next/navigation"; // icons import { Calendar, ChevronDown, Kanban, List } from "lucide-react"; // plane constants -import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants"; +import { + EIssueLayoutTypes, + EIssueFilterType, + EIssuesStoreType, + ISSUE_LAYOUTS, + ISSUE_DISPLAY_FILTERS_BY_PAGE, +} from "@plane/constants"; +// plane i18n +import { useTranslation } from "@plane/i18n"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui import { CustomMenu } from "@plane/ui"; // components import { ProjectAnalyticsModal } from "@/components/analytics"; -import { DisplayFiltersSelection, FilterSelection, FiltersDropdown } from "@/components/issues/issue-layouts"; -// constants -import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT, ISSUE_LAYOUTS } from "@/constants/issue"; +import { + DisplayFiltersSelection, + FilterSelection, + FiltersDropdown, + IssueLayoutIcon, +} from "@/components/issues/issue-layouts"; // helpers import { isIssueFilterActive } from "@/helpers/filter.helper"; // hooks @@ -25,10 +36,11 @@ export const ModuleIssuesMobileHeader = observer(() => { const [analyticsModal, setAnalyticsModal] = useState(false); const { currentProjectDetails } = useProject(); const { getModuleById } = useModule(); + const { t } = useTranslation(); const layouts = [ - { key: "list", title: "List", icon: List }, - { key: "kanban", title: "Board", icon: Kanban }, - { key: "calendar", title: "Calendar", icon: Calendar }, + { key: "list", i18n_title: "issue.layouts.list", icon: List }, + { key: "kanban", i18n_title: "issue.layouts.kanban", icon: Kanban }, + { key: "calendar", i18n_title: "issue.layouts.calendar", icon: Calendar }, ]; const { workspaceSlug, projectId, moduleId } = useParams() as { workspaceSlug: string; @@ -116,8 +128,8 @@ export const ModuleIssuesMobileHeader = observer(() => { }} className="flex items-center gap-2" > - -
{layout.title}
+ +
{t(layout.i18n_title)}
))} @@ -139,7 +151,7 @@ export const ModuleIssuesMobileHeader = observer(() => { displayFilters={issueFilters?.displayFilters ?? {}} handleDisplayFiltersUpdate={handleDisplayFilters} layoutDisplayFiltersOptions={ - activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined + activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined } labels={projectLabels} memberIds={projectMemberIds ?? undefined} @@ -162,7 +174,7 @@ export const ModuleIssuesMobileHeader = observer(() => { > { // router @@ -24,6 +26,8 @@ export const ModulesListHeader: React.FC = observer(() => { const { loader } = useProject(); + const { t } = useTranslation(); + // auth const canUserCreateModule = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], @@ -38,7 +42,9 @@ export const ModulesListHeader: React.FC = observer(() => { } />} + link={ + } /> + } />
@@ -54,7 +60,8 @@ export const ModulesListHeader: React.FC = observer(() => { toggleCreateModuleModal(true); }} > -
Add
Module +
{t("add")}
+
{t("project_module.add_module")}
) : ( <> diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/mobile-header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/mobile-header.tsx index 12301c9d44d..629dca36a1c 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/mobile-header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/mobile-header.tsx @@ -2,13 +2,16 @@ import { observer } from "mobx-react"; import { ChevronDown } from "lucide-react"; +import { MODULE_VIEW_LAYOUTS } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { CustomMenu, Row } from "@plane/ui"; -import { MODULE_VIEW_LAYOUTS } from "@/constants/module"; +import { ModuleLayoutIcon } from "@/components/modules"; import { useModuleFilter, useProject } from "@/hooks/store"; export const ModulesListMobileHeader = observer(() => { const { currentProjectDetails } = useProject(); const { updateDisplayFilters } = useModuleFilter(); + const { t } = useTranslation(); return (
@@ -34,8 +37,8 @@ export const ModulesListMobileHeader = observer(() => { }} className="flex items-center gap-2" > - -
{layout.title}
+ +
{t(layout.i18n_title)}
); })} diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx index 9417016e385..572cb3862f7 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx @@ -4,27 +4,36 @@ import { useCallback } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // types +import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TModuleFilters } from "@plane/types"; // components import { PageHead } from "@/components/core"; -import { EmptyState } from "@/components/empty-state"; +import { DetailedEmptyState } from "@/components/empty-state"; import { ModuleAppliedFiltersList, ModulesListView } from "@/components/modules"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; // helpers import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks -import { useModuleFilter, useProject } from "@/hooks/store"; +import { useModuleFilter, useProject, useUserPermissions } from "@/hooks/store"; +import { useAppRouter } from "@/hooks/use-app-router"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; const ProjectModulesPage = observer(() => { + // router + const router = useAppRouter(); const { workspaceSlug, projectId } = useParams(); + // plane hooks + const { t } = useTranslation(); // store const { getProjectById, currentProjectDetails } = useProject(); const { currentProjectFilters, currentProjectDisplayFilters, clearAllFilters, updateFilters, updateDisplayFilters } = useModuleFilter(); + const { allowPermissions } = useUserPermissions(); // derived values const project = projectId ? getProjectById(projectId.toString()) : undefined; const pageTitle = project?.name ? `${project?.name} - Modules` : undefined; + const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/modules" }); const handleRemoveFilter = useCallback( (key: keyof TModuleFilters, value: string | null) => { @@ -45,9 +54,17 @@ const ProjectModulesPage = observer(() => { if (currentProjectDetails?.module_view === false) return (
- { + router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`); + }, + disabled: !canPerformEmptyStateActions, + }} />
); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx index 1588e69de88..0173148df19 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx @@ -4,14 +4,14 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import { FileText } from "lucide-react"; +// constants +import { EPageAccess } from "@plane/constants"; // plane types import { TPage } from "@plane/types"; // plane ui import { Breadcrumbs, Button, Header, setToast, TOAST_TYPE } from "@plane/ui"; // helpers import { BreadcrumbLink } from "@/components/common"; -// constants -import { EPageAccess } from "@/constants/page"; // hooks import { useEventTracker, useProject, useProjectPages } from "@/hooks/store"; // plane web diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx index 93f37ea83b0..547584f51a1 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/page.tsx @@ -2,27 +2,35 @@ import { observer } from "mobx-react"; import { useParams, useSearchParams } from "next/navigation"; -// types +// plane imports +import { EUserPermissionsLevel, EUserProjectRoles } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TPageNavigationTabs } from "@plane/types"; // components import { PageHead } from "@/components/core"; -import { EmptyState } from "@/components/empty-state"; +import { DetailedEmptyState } from "@/components/empty-state"; import { PagesListRoot, PagesListView } from "@/components/pages"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; // hooks -import { useProject } from "@/hooks/store"; +import { useProject, useUserPermissions } from "@/hooks/store"; +import { useAppRouter } from "@/hooks/use-app-router"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; const ProjectPagesPage = observer(() => { // router + const router = useAppRouter(); const searchParams = useSearchParams(); const type = searchParams.get("type"); const { workspaceSlug, projectId } = useParams(); + // plane hooks + const { t } = useTranslation(); // store hooks const { getProjectById, currentProjectDetails } = useProject(); + const { allowPermissions } = useUserPermissions(); // derived values const project = projectId ? getProjectById(projectId.toString()) : undefined; const pageTitle = project?.name ? `${project?.name} - Pages` : undefined; + const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/pages" }); const currentPageType = (): TPageNavigationTabs => { const pageType = type?.toString(); @@ -37,9 +45,17 @@ const ProjectPagesPage = observer(() => { if (currentProjectDetails?.page_view === false) return (
- { + router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`); + }, + disabled: !canPerformEmptyStateActions, + }} />
); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/automations/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/automations/page.tsx index 141cc39feda..5fc536d91be 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/automations/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/automations/page.tsx @@ -3,6 +3,8 @@ import React from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IProject } from "@plane/types"; // ui import { TOAST_TYPE, setToast } from "@plane/ui"; @@ -12,7 +14,6 @@ import { AutoArchiveAutomation, AutoCloseAutomation } from "@/components/automat import { PageHead } from "@/components/core"; // hooks import { useProject, useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const AutomationSettingsPage = observer(() => { // router @@ -21,6 +22,8 @@ const AutomationSettingsPage = observer(() => { const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { currentProjectDetails: projectDetails, updateProject } = useProject(); + const { t } = useTranslation(); + // derived values const canPerformProjectAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT); @@ -48,7 +51,7 @@ const AutomationSettingsPage = observer(() => {
-

Automations

+

{t("project_settings.automations.label")}

diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/estimates/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/estimates/page.tsx index 14f7707672b..0a19713e880 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/estimates/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/estimates/page.tsx @@ -3,12 +3,12 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // components +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { EstimateRoot } from "@/components/estimates"; // hooks import { useProject, useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const EstimatesSettingsPage = observer(() => { const { workspaceSlug, projectId } = useParams(); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/features/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/features/page.tsx index 05bde8c9e1a..23aa8ad45cb 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/features/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/features/page.tsx @@ -3,12 +3,12 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // components +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { ProjectFeaturesList } from "@/components/project"; // hooks import { useProject, useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const FeaturesSettingsPage = observer(() => { const { workspaceSlug, projectId } = useParams(); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/header.tsx index ecc7146ba92..6fa36db34e1 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/header.tsx @@ -5,6 +5,8 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // ui import { Settings } from "lucide-react"; +import { EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, CustomMenu, Header } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; @@ -14,7 +16,6 @@ import { useAppRouter } from "@/hooks/use-app-router"; // plane web import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; import { PROJECT_SETTINGS_LINKS } from "@/plane-web/constants/project"; -import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; export const ProjectSettingHeader: FC = observer(() => { // router @@ -24,6 +25,8 @@ export const ProjectSettingHeader: FC = observer(() => { const { allowPermissions } = useUserPermissions(); const { loader } = useProject(); + const { t } = useTranslation(); + return (
@@ -65,7 +68,7 @@ export const ProjectSettingHeader: FC = observer(() => { key={item.key} onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}${item.href}`)} > - {item.label} + {t(item.i18n_label)} ) )} diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/labels/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/labels/page.tsx index 2705ff4901d..17a466a8010 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/labels/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/labels/page.tsx @@ -5,12 +5,12 @@ import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element"; import { observer } from "mobx-react"; // components +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { ProjectSettingsLabelList } from "@/components/labels"; // hooks import { useProject, useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const LabelsSettingsPage = observer(() => { // store hooks diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/members/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/members/page.tsx index 0d05187b189..565e187545c 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/members/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/members/page.tsx @@ -2,12 +2,12 @@ import { observer } from "mobx-react"; // components +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { ProjectMemberList, ProjectSettingsMemberDefaults } from "@/components/project"; // hooks import { useProject, useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const MembersSettingsPage = observer(() => { // store diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/page.tsx index e21532c643a..96ff1bcc3f6 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/page.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; // components +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { PageHead } from "@/components/core"; import { ArchiveRestoreProjectModal, @@ -16,7 +17,6 @@ import { } from "@/components/project"; // hooks import { useProject, useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const GeneralSettingsPage = observer(() => { // states @@ -43,8 +43,6 @@ const GeneralSettingsPage = observer(() => { ); const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - General Settings` : undefined; - // const currentNetwork = NETWORK_CHOICES.find((n) => n.key === projectDetails?.network); - // const selectedNetwork = NETWORK_CHOICES.find((n) => n.key === watch("network")); return ( <> diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/sidebar.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/sidebar.tsx index 7a352ce0fe5..7bb1984c89f 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/sidebar.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/sidebar.tsx @@ -5,6 +5,8 @@ import range from "lodash/range"; import { observer } from "mobx-react"; import Link from "next/link"; import { useParams, usePathname } from "next/navigation"; +import { EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // ui import { Loader } from "@plane/ui"; // components @@ -13,7 +15,6 @@ import { SidebarNavItem } from "@/components/sidebar"; import { useUserPermissions } from "@/hooks/store"; // plane web constants import { PROJECT_SETTINGS_LINKS } from "@/plane-web/constants/project"; -import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; export const ProjectSettingsSidebar = observer(() => { const { workspaceSlug, projectId } = useParams(); @@ -21,6 +22,8 @@ export const ProjectSettingsSidebar = observer(() => { // mobx store const { allowPermissions, projectUserInfo } = useUserPermissions(); + const { t } = useTranslation(); + // derived values const currentProjectRole = projectUserInfo?.[workspaceSlug?.toString()]?.[projectId?.toString()]?.role; @@ -58,7 +61,7 @@ export const ProjectSettingsSidebar = observer(() => { isActive={link.highlight(pathname, `/${workspaceSlug}/projects/${projectId}`)} className="text-sm font-medium px-4 py-2" > - {link.label} + {t(link.i18n_label)} ) diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/states/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/states/page.tsx index af9bf5618e4..54fca1c0821 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/states/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/states/page.tsx @@ -2,13 +2,14 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // components import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { ProjectStateRoot } from "@/components/project-states"; // hook import { useProject, useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const StatesSettingsPage = observer(() => { const { workspaceSlug, projectId } = useParams(); @@ -16,6 +17,8 @@ const StatesSettingsPage = observer(() => { const { currentProjectDetails } = useProject(); const { workspaceUserInfo, allowPermissions } = useUserPermissions(); + const { t } = useTranslation(); + // derived values const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - States` : undefined; // derived values @@ -32,7 +35,7 @@ const StatesSettingsPage = observer(() => { <>
-

States

+

{t("common.states")}

{workspaceSlug && projectId && ( 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 b4de9b64040..cc9a8fff6eb 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 @@ -6,7 +6,15 @@ import Link from "next/link"; import { useParams } from "next/navigation"; import { Layers, Lock } from "lucide-react"; // plane constants -import { EIssueLayoutTypes, EIssueFilterType, EIssuesStoreType } from "@plane/constants"; +import { + EIssueLayoutTypes, + EIssueFilterType, + EIssuesStoreType, + ISSUE_DISPLAY_FILTERS_BY_PAGE, + EViewAccess, + EUserPermissions, + EUserPermissionsLevel, +} from "@plane/constants"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui @@ -16,8 +24,6 @@ import { BreadcrumbLink, Logo } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; // constants import { ViewQuickActions } from "@/components/views"; -import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; -import { EViewAccess } from "@/constants/views"; // helpers import { isIssueFilterActive } from "@/helpers/filter.helper"; import { truncateText } from "@/helpers/string.helper"; @@ -35,7 +41,6 @@ import { } from "@/hooks/store"; // plane web import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; export const ProjectViewIssuesHeader: React.FC = observer(() => { // refs @@ -242,7 +247,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => { displayFilters={issueFilters?.displayFilters ?? {}} handleDisplayFiltersUpdate={handleDisplayFilters} layoutDisplayFiltersOptions={ - activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined + activeLayout ? ISSUE_DISPLAY_FILTERS_BY_PAGE.issues[activeLayout] : undefined } projectId={projectId.toString()} labels={projectLabels} @@ -255,7 +260,7 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => { { }} size="sm" > - Add issue + Add work item ) : ( <> 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 387f6e5b423..d4a3051ec94 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 @@ -4,29 +4,37 @@ import { useCallback } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // components +import { EUserPermissionsLevel, EUserProjectRoles, EViewAccess } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TViewFilterProps } from "@plane/types"; import { Header, EHeaderVariant } from "@plane/ui"; import { PageHead } from "@/components/core"; -import { EmptyState } from "@/components/empty-state"; +import { DetailedEmptyState } from "@/components/empty-state"; import { ProjectViewsList } from "@/components/views"; import { ViewAppliedFiltersList } from "@/components/views/applied-filters"; -import { EmptyStateType } from "@/constants/empty-state"; // constants -import { EViewAccess } from "@/constants/views"; // helpers import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks -import { useProject, useProjectView } from "@/hooks/store"; +import { useProject, useProjectView, useUserPermissions } from "@/hooks/store"; +import { useAppRouter } from "@/hooks/use-app-router"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; const ProjectViewsPage = observer(() => { // router + const router = useAppRouter(); const { workspaceSlug, projectId } = useParams(); + // plane hooks + const { t } = useTranslation(); // store const { getProjectById, currentProjectDetails } = useProject(); const { filters, updateFilters, clearAllFilters } = useProjectView(); + const { allowPermissions } = useUserPermissions(); // derived values const project = projectId ? getProjectById(projectId.toString()) : undefined; const pageTitle = project?.name ? `${project?.name} - Views` : undefined; + const canPerformEmptyStateActions = allowPermissions([EUserProjectRoles.ADMIN], EUserPermissionsLevel.PROJECT); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/disabled-feature/views" }); const handleRemoveFilter = useCallback( (key: keyof TViewFilterProps, value: string | EViewAccess | null) => { @@ -53,9 +61,17 @@ const ProjectViewsPage = observer(() => { if (currentProjectDetails?.issue_views_view === false) return (
- { + router.push(`/${workspaceSlug}/projects/${projectId}/settings/features`); + }, + disabled: !canPerformEmptyStateActions, + }} />
); diff --git a/web/app/[workspaceSlug]/(projects)/settings/api-tokens/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/api-tokens/page.tsx index fd2ed9669b2..21334ff23cd 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/api-tokens/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/api-tokens/page.tsx @@ -4,20 +4,20 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; -// ui +// plane imports +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Button } from "@plane/ui"; // component import { ApiTokenListItem, CreateApiTokenModal } from "@/components/api-token"; import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; -import { EmptyState } from "@/components/empty-state"; +import { DetailedEmptyState } from "@/components/empty-state"; import { APITokenSettingsLoader } from "@/components/ui"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; import { API_TOKENS_LIST } from "@/constants/fetch-keys"; // store hooks import { useUserPermissions, useWorkspace } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; // services import { APITokenService } from "@/services/api_token.service"; @@ -28,11 +28,14 @@ const ApiTokensPage = observer(() => { const [isCreateTokenModalOpen, setIsCreateTokenModalOpen] = useState(false); // router const { workspaceSlug } = useParams(); + // plane hooks + const { t } = useTranslation(); // store hooks const { currentWorkspace } = useWorkspace(); const { workspaceUserInfo, allowPermissions } = useUserPermissions(); // derived values const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/workspace-settings/api-tokens" }); const { data: tokens } = useSWR( workspaceSlug && canPerformWorkspaceAdminActions ? API_TOKENS_LIST(workspaceSlug.toString()) : null, @@ -40,7 +43,9 @@ const ApiTokensPage = observer(() => { workspaceSlug && canPerformWorkspaceAdminActions ? apiTokenService.getApiTokens(workspaceSlug.toString()) : null ); - const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - API Tokens` : undefined; + const pageTitle = currentWorkspace?.name + ? `${currentWorkspace.name} - ${t("workspace_settings.settings.api_tokens.title")}` + : undefined; if (workspaceUserInfo && !canPerformWorkspaceAdminActions) { return ; @@ -58,9 +63,9 @@ const ApiTokensPage = observer(() => { {tokens.length > 0 ? ( <>
-

API tokens

+

{t("workspace_settings.settings.api_tokens.title")}

@@ -72,13 +77,17 @@ const ApiTokensPage = observer(() => { ) : (
-

API tokens

+

{t("workspace_settings.settings.api_tokens.title")}

- +
)} diff --git a/web/app/[workspaceSlug]/(projects)/settings/billing/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/billing/page.tsx index 03f0cce5ecf..801b04b3783 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/billing/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/billing/page.tsx @@ -2,13 +2,13 @@ import { observer } from "mobx-react"; // component +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; // hooks import { useUserPermissions, useWorkspace } from "@/hooks/store"; // plane web components import { BillingRoot } from "@/plane-web/components/workspace"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const BillingSettingsPage = observer(() => { // store hooks diff --git a/web/app/[workspaceSlug]/(projects)/settings/exports/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/exports/page.tsx index dc3f3aafce7..1b63406de34 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/exports/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/exports/page.tsx @@ -2,6 +2,8 @@ import { observer } from "mobx-react"; // components +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import ExportGuide from "@/components/exporter/guide"; @@ -9,19 +11,21 @@ import ExportGuide from "@/components/exporter/guide"; import { cn } from "@/helpers/common.helper"; // hooks import { useUserPermissions, useWorkspace } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const ExportsPage = observer(() => { // store hooks const { workspaceUserInfo, allowPermissions } = useUserPermissions(); const { currentWorkspace } = useWorkspace(); + const { t } = useTranslation(); // derived values const canPerformWorkspaceMemberActions = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.WORKSPACE ); - const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Exports` : undefined; + const pageTitle = currentWorkspace?.name + ? `${currentWorkspace.name} - ${t("workspace_settings.settings.exports.title")}` + : undefined; // if user is not authorized to view this page if (workspaceUserInfo && !canPerformWorkspaceMemberActions) { @@ -37,7 +41,7 @@ const ExportsPage = observer(() => { })} >
-

Exports

+

{t("workspace_settings.settings.exports.title")}

diff --git a/web/app/[workspaceSlug]/(projects)/settings/header.tsx b/web/app/[workspaceSlug]/(projects)/settings/header.tsx index d98db1a65b4..003e7274319 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/header.tsx @@ -4,6 +4,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { Settings } from "lucide-react"; // ui +import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, Header } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; @@ -12,6 +13,7 @@ import { useWorkspace } from "@/hooks/store"; export const WorkspaceSettingHeader: FC = observer(() => { const { currentWorkspace, loader } = useWorkspace(); + const { t } = useTranslation(); return (
@@ -27,7 +29,7 @@ export const WorkspaceSettingHeader: FC = observer(() => { /> } /> - } /> + } />
diff --git a/web/app/[workspaceSlug]/(projects)/settings/imports/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/imports/page.tsx index dc0815751a4..718742804ad 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/imports/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/imports/page.tsx @@ -2,11 +2,11 @@ import { observer } from "mobx-react"; // components +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { PageHead } from "@/components/core"; import IntegrationGuide from "@/components/integration/guide"; // hooks import { useUserPermissions, useWorkspace } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const ImportsPage = observer(() => { // store hooks diff --git a/web/app/[workspaceSlug]/(projects)/settings/integrations/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/integrations/page.tsx index 290eb24ca3d..ef31bd82feb 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/integrations/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/integrations/page.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; // components +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { PageHead } from "@/components/core"; import { SingleIntegrationCard } from "@/components/integration"; import { IntegrationAndImportExportBanner, IntegrationsSettingsLoader } from "@/components/ui"; @@ -10,7 +11,6 @@ import { IntegrationAndImportExportBanner, IntegrationsSettingsLoader } from "@/ import { APP_INTEGRATIONS } from "@/constants/fetch-keys"; // hooks import { useUserPermissions, useWorkspace } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; // services import { IntegrationService } from "@/services/integrations"; diff --git a/web/app/[workspaceSlug]/(projects)/settings/layout.tsx b/web/app/[workspaceSlug]/(projects)/settings/layout.tsx index 9bd133cc147..788ca02ae83 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/layout.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/layout.tsx @@ -3,12 +3,12 @@ import { FC, ReactNode } from "react"; import { observer } from "mobx-react"; // components +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { NotAuthorizedView } from "@/components/auth-screens"; import { AppHeader } from "@/components/core"; // hooks import { useUserPermissions } from "@/hooks/store"; // plane web constants -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; // local components import { WorkspaceSettingHeader } from "./header"; import { MobileWorkspaceSettingsTabs } from "./mobile-header-tabs"; diff --git a/web/app/[workspaceSlug]/(projects)/settings/members/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/members/page.tsx index ab23261d914..936eda0ba18 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/members/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/members/page.tsx @@ -5,6 +5,8 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { Search } from "lucide-react"; // types +import { MEMBER_INVITED, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IWorkspaceBulkInviteFormData } from "@plane/types"; // ui import { Button, TOAST_TYPE, setToast } from "@plane/ui"; @@ -13,13 +15,11 @@ import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; import { SendWorkspaceInvitationModal, WorkspaceMembersList } from "@/components/workspace"; // constants -import { MEMBER_INVITED } from "@/constants/event-tracker"; // helpers import { cn } from "@/helpers/common.helper"; import { getUserRole } from "@/helpers/user.helper"; // hooks import { useEventTracker, useMember, useUserPermissions, useWorkspace } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const WorkspaceMembersSettingsPage = observer(() => { // states @@ -34,6 +34,7 @@ const WorkspaceMembersSettingsPage = observer(() => { workspace: { inviteMembersToWorkspace }, } = useMember(); const { currentWorkspace } = useWorkspace(); + const { t } = useTranslation(); // derived values const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE); @@ -62,7 +63,7 @@ const WorkspaceMembersSettingsPage = observer(() => { setToast({ type: TOAST_TYPE.SUCCESS, title: "Success!", - message: "Invitations sent successfully.", + message: t("workspace_settings.settings.members.invitations_sent_successfully"), }); }) .catch((err) => { @@ -80,7 +81,7 @@ const WorkspaceMembersSettingsPage = observer(() => { setToast({ type: TOAST_TYPE.ERROR, title: "Error!", - message: `${err.error ?? "Something went wrong. Please try again."}`, + message: `${err.error ?? t("something_went_wrong_please_try_again")}`, }); }); }; @@ -107,12 +108,12 @@ const WorkspaceMembersSettingsPage = observer(() => { })} >
-

Members

+

{t("workspace_settings.settings.members.title")}

setSearchQuery(e.target.value)} @@ -120,7 +121,7 @@ const WorkspaceMembersSettingsPage = observer(() => {
{canPerformWorkspaceAdminActions && ( )}
diff --git a/web/app/[workspaceSlug]/(projects)/settings/mobile-header-tabs.tsx b/web/app/[workspaceSlug]/(projects)/settings/mobile-header-tabs.tsx index ee3035220c8..b699a1ff260 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/mobile-header-tabs.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/mobile-header-tabs.tsx @@ -1,11 +1,10 @@ import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; +import { WORKSPACE_SETTINGS_LINKS, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // hooks import { useUserPermissions } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; -// plane web constants -import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; -import { WORKSPACE_SETTINGS_LINKS } from "@/plane-web/constants/workspace"; // plane web helpers import { shouldRenderSettingLink } from "@/plane-web/helpers/workspace.helper"; @@ -13,6 +12,7 @@ export const MobileWorkspaceSettingsTabs = observer(() => { const router = useAppRouter(); const { workspaceSlug } = useParams(); const pathname = usePathname(); + const { t } = useTranslation(); // mobx store const { allowPermissions } = useUserPermissions(); @@ -31,7 +31,7 @@ export const MobileWorkspaceSettingsTabs = observer(() => { key={index} onClick={() => router.push(`/${workspaceSlug}${item.href}`)} > - {item.label} + {t(item.i18n_label)}
) )} diff --git a/web/app/[workspaceSlug]/(projects)/settings/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/page.tsx index c628e313c5c..6088cf0a50f 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/page.tsx @@ -2,6 +2,7 @@ import { observer } from "mobx-react"; // components +import { useTranslation } from "@plane/i18n"; import { PageHead } from "@/components/core"; import { WorkspaceDetails } from "@/components/workspace"; // hooks @@ -10,8 +11,11 @@ import { useWorkspace } from "@/hooks/store"; const WorkspaceSettingsPage = observer(() => { // store hooks const { currentWorkspace } = useWorkspace(); + const { t } = useTranslation(); // derived values - const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - General Settings` : undefined; + const pageTitle = currentWorkspace?.name + ? t("workspace_settings.page_label", { workspace: currentWorkspace.name }) + : undefined; return ( <> diff --git a/web/app/[workspaceSlug]/(projects)/settings/sidebar.tsx b/web/app/[workspaceSlug]/(projects)/settings/sidebar.tsx index 6bce5daa325..a120655364a 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/sidebar.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/sidebar.tsx @@ -4,13 +4,12 @@ import React from "react"; import { observer } from "mobx-react"; import Link from "next/link"; import { useParams, usePathname } from "next/navigation"; +import { WORKSPACE_SETTINGS_LINKS, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // components import { SidebarNavItem } from "@/components/sidebar"; // hooks import { useUserPermissions } from "@/hooks/store"; -// plane web constants -import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; -import { WORKSPACE_SETTINGS_LINKS } from "@/plane-web/constants/workspace"; // plane web helpers import { shouldRenderSettingLink } from "@/plane-web/helpers/workspace.helper"; @@ -19,12 +18,13 @@ export const WorkspaceSettingsSidebar = observer(() => { const { workspaceSlug } = useParams(); const pathname = usePathname(); // mobx store + const { t } = useTranslation(); const { allowPermissions } = useUserPermissions(); return (
- SETTINGS + {t("settings")}
{WORKSPACE_SETTINGS_LINKS.map( (link) => @@ -36,7 +36,7 @@ export const WorkspaceSettingsSidebar = observer(() => { isActive={link.highlight(pathname, `/${workspaceSlug}`)} className="text-sm font-medium px-4 py-2" > - {link.label} + {t(link.i18n_label)} ) diff --git a/web/app/[workspaceSlug]/(projects)/settings/webhooks/[webhookId]/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/webhooks/[webhookId]/page.tsx index 4669f406494..5edc914e908 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/webhooks/[webhookId]/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/webhooks/[webhookId]/page.tsx @@ -4,6 +4,7 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { IWebhook } from "@plane/types"; // ui import { TOAST_TYPE, setToast } from "@plane/ui"; @@ -13,7 +14,6 @@ import { PageHead } from "@/components/core"; import { DeleteWebhookModal, WebhookDeleteSection, WebhookForm } from "@/components/web-hooks"; // hooks import { useUserPermissions, useWebhook, useWorkspace } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; const WebhookDetailsPage = observer(() => { // states diff --git a/web/app/[workspaceSlug]/(projects)/settings/webhooks/page.tsx b/web/app/[workspaceSlug]/(projects)/settings/webhooks/page.tsx index 86c922f07fb..2623660da88 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/webhooks/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/webhooks/page.tsx @@ -4,39 +4,43 @@ import React, { useEffect, useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; -// ui +// plane imports +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Button } from "@plane/ui"; // components import { NotAuthorizedView } from "@/components/auth-screens"; import { PageHead } from "@/components/core"; -import { EmptyState } from "@/components/empty-state"; +import { DetailedEmptyState } from "@/components/empty-state"; import { WebhookSettingsLoader } from "@/components/ui"; import { WebhooksList, CreateWebhookModal } from "@/components/web-hooks"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; // hooks import { useUserPermissions, useWebhook, useWorkspace } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; const WebhooksListPage = observer(() => { // states const [showCreateWebhookModal, setShowCreateWebhookModal] = useState(false); // router const { workspaceSlug } = useParams(); + // plane hooks + const { t } = useTranslation(); // mobx store const { workspaceUserInfo, allowPermissions } = useUserPermissions(); - const { fetchWebhooks, webhooks, clearSecretKey, webhookSecretKey, createWebhook } = useWebhook(); const { currentWorkspace } = useWorkspace(); - + // derived values const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/workspace-settings/webhooks" }); useSWR( workspaceSlug && canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null, workspaceSlug && canPerformWorkspaceAdminActions ? () => fetchWebhooks(workspaceSlug.toString()) : null ); - const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Webhooks` : undefined; + const pageTitle = currentWorkspace?.name + ? `${currentWorkspace.name} - ${t("workspace_settings.settings.webhooks.title")}` + : undefined; // clear secret key when modal is closed. useEffect(() => { @@ -65,9 +69,9 @@ const WebhooksListPage = observer(() => { {Object.keys(webhooks).length > 0 ? (
-
Webhooks
+
{t("workspace_settings.settings.webhooks.title")}
@@ -75,13 +79,17 @@ const WebhooksListPage = observer(() => { ) : (
-
Webhooks
+
{t("workspace_settings.settings.webhooks.title")}
- +
)} diff --git a/web/app/[workspaceSlug]/(projects)/sidebar.tsx b/web/app/[workspaceSlug]/(projects)/sidebar.tsx index 11d23e35f05..6b98e6bdbff 100644 --- a/web/app/[workspaceSlug]/(projects)/sidebar.tsx +++ b/web/app/[workspaceSlug]/(projects)/sidebar.tsx @@ -2,6 +2,7 @@ import { FC, useEffect, useRef } from "react"; import isEmpty from "lodash/isEmpty"; import { observer } from "mobx-react"; // plane helpers +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useOutsideClickDetector } from "@plane/hooks"; // components import { @@ -22,7 +23,6 @@ import useSize from "@/hooks/use-window-size"; // plane web components import { SidebarAppSwitcher } from "@/plane-web/components/sidebar"; import { SidebarTeamsList } from "@/plane-web/components/workspace/sidebar/teams-sidebar-list"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; export const AppSidebar: FC = observer(() => { // store hooks diff --git a/web/app/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx b/web/app/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx index b75ff3ab179..bf7d78a6da9 100644 --- a/web/app/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/workspace-views/[globalViewId]/page.tsx @@ -3,12 +3,13 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; +// plane imports +import { DEFAULT_GLOBAL_VIEWS_LIST } from "@plane/constants"; // components import { PageHead } from "@/components/core"; import { AllIssueLayoutRoot, GlobalViewsAppliedFiltersRoot } from "@/components/issues"; import { GlobalViewsHeader } from "@/components/workspace"; // constants -import { DEFAULT_GLOBAL_VIEWS_LIST } from "@/constants/workspace"; // hooks import { useWorkspace } from "@/hooks/store"; diff --git a/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx b/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx index e1ec683ec3a..5a5ee0a6ef9 100644 --- a/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx @@ -5,7 +5,8 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { Layers } from "lucide-react"; // plane constants -import { EIssueFilterType, EIssuesStoreType } from "@plane/constants"; +import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui @@ -14,8 +15,6 @@ import { Breadcrumbs, Button, Header } from "@plane/ui"; import { BreadcrumbLink } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "@/components/issues"; import { CreateUpdateWorkspaceViewModal } from "@/components/workspace"; -// constants -import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue"; // helpers import { isIssueFilterActive } from "@/helpers/filter.helper"; // hooks @@ -35,6 +34,7 @@ export const GlobalIssuesHeader = observer(() => { const { workspace: { workspaceMemberIds }, } = useMember(); + const { t } = useTranslation(); const issueFilters = globalViewId ? filters[globalViewId.toString()] : undefined; @@ -105,7 +105,7 @@ export const GlobalIssuesHeader = observer(() => { } />} + link={} />} /> @@ -114,12 +114,12 @@ export const GlobalIssuesHeader = observer(() => { {!isLocked ? ( <> { 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..87d0cfc58d5 100644 --- a/web/app/[workspaceSlug]/(projects)/workspace-views/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/workspace-views/page.tsx @@ -4,13 +4,15 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; // icons import { Search } from "lucide-react"; +// plane imports +import { DEFAULT_GLOBAL_VIEWS_LIST } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // ui import { Input } from "@plane/ui"; // components import { PageHead } from "@/components/core"; import { GlobalDefaultViewListItem, GlobalViewsList } from "@/components/workspace"; // constants -import { DEFAULT_GLOBAL_VIEWS_LIST } from "@/constants/workspace"; // hooks import { useWorkspace } from "@/hooks/store"; @@ -18,6 +20,7 @@ const WorkspaceViewsPage = observer(() => { const [query, setQuery] = useState(""); // store const { currentWorkspace } = useWorkspace(); + const { t } = useTranslation(); // derived values const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - All Views` : undefined; @@ -36,7 +39,7 @@ const WorkspaceViewsPage = observer(() => { />
- {DEFAULT_GLOBAL_VIEWS_LIST.filter((v) => v.label.toLowerCase().includes(query.toLowerCase())).map( + {DEFAULT_GLOBAL_VIEWS_LIST.filter((v) => t(v.i18n_label).toLowerCase().includes(query.toLowerCase())).map( (option) => ( ) diff --git a/web/app/accounts/forgot-password/page.tsx b/web/app/accounts/forgot-password/page.tsx index 91516c5b95e..22a241db659 100644 --- a/web/app/accounts/forgot-password/page.tsx +++ b/web/app/accounts/forgot-password/page.tsx @@ -1,5 +1,6 @@ "use client"; +import { observer } from "mobx-react"; import Image from "next/image"; import Link from "next/link"; import { useSearchParams } from "next/navigation"; @@ -7,10 +8,10 @@ import { useTheme } from "next-themes"; import { Controller, useForm } from "react-hook-form"; // icons import { CircleCheck } from "lucide-react"; -// ui +// plane imports +import { FORGOT_PASS_LINK, NAVIGATE_TO_SIGNUP } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Button, Input, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui"; -// constants -import { FORGOT_PASS_LINK, NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker"; // helpers import { EPageTypes } from "@/helpers/authentication.helper"; import { cn } from "@/helpers/common.helper"; @@ -20,12 +21,12 @@ import { useEventTracker } from "@/hooks/store"; import useTimer from "@/hooks/use-timer"; // wrappers import { AuthenticationWrapper } from "@/lib/wrappers"; -// services // images import PlaneBackgroundPatternDark from "@/public/auth/background-pattern-dark.svg"; import PlaneBackgroundPattern from "@/public/auth/background-pattern.svg"; import BlackHorizontalLogo from "@/public/plane-logos/black-horizontal-with-blue-logo.png"; import WhiteHorizontalLogo from "@/public/plane-logos/white-horizontal-with-blue-logo.png"; +// services import { AuthService } from "@/services/auth.service"; type TForgotPasswordFormValues = { @@ -39,10 +40,12 @@ const defaultValues: TForgotPasswordFormValues = { // services const authService = new AuthService(); -export default function ForgotPasswordPage() { +const ForgotPasswordPage = observer(() => { // search params const searchParams = useSearchParams(); const email = searchParams.get("email"); + // plane hooks + const { t } = useTranslation(); // store hooks const { captureEvent } = useEventTracker(); // hooks @@ -73,9 +76,8 @@ export default function ForgotPasswordPage() { }); setToast({ type: TOAST_TYPE.SUCCESS, - title: "Email sent", - message: - "Check your inbox for a link to reset your password. If it doesn't appear within a few minutes, check your spam folder.", + title: t("auth.forgot_password.toast.success.title"), + message: t("auth.forgot_password.toast.success.message"), }); setResendCodeTimer(30); }) @@ -85,8 +87,8 @@ export default function ForgotPasswordPage() { }); setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: err?.error ?? "Something went wrong. Please try again.", + title: t("auth.forgot_password.toast.error.title"), + message: err?.error ?? t("auth.forgot_password.toast.error.message"), }); }); }; @@ -111,13 +113,13 @@ export default function ForgotPasswordPage() {
- New to Plane?{" "} + {t("auth.common.new_to_plane")} captureEvent(NAVIGATE_TO_SIGNUP, {})} className="font-semibold text-custom-primary-100 hover:underline" > - Create an account + {t("auth.common.create_account")}
@@ -125,23 +127,21 @@ export default function ForgotPasswordPage() {

- Reset your password + {t("auth.forgot_password.title")}

-

- Enter your user account{"'"}s verified email address and we will send you a password reset link. -

+

{t("auth.forgot_password.description")}

checkEmailValidity(value) || "Email is invalid", + required: t("auth.common.email.errors.required"), + validate: (value) => checkEmailValidity(value) || t("auth.common.email.errors.invalid"), }} render={({ field: { value, onChange, ref } }) => ( 0} @@ -162,7 +162,7 @@ export default function ForgotPasswordPage() { {resendTimerCode > 0 && (

- We sent the reset link to your email address + {t("auth.forgot_password.email_sent")}

)}
@@ -174,10 +174,12 @@ export default function ForgotPasswordPage() { disabled={!isValid} loading={isSubmitting || resendTimerCode > 0} > - {resendTimerCode > 0 ? `Resend in ${resendTimerCode} seconds` : "Send reset link"} + {resendTimerCode > 0 + ? t("auth.common.resend_in", { seconds: resendTimerCode }) + : t("auth.forgot_password.send_reset_link")} - Back to sign in + {t("auth.common.back_to_sign_in")}
@@ -186,4 +188,6 @@ export default function ForgotPasswordPage() {
); -} +}); + +export default ForgotPasswordPage; diff --git a/web/app/accounts/reset-password/page.tsx b/web/app/accounts/reset-password/page.tsx index 04d6e3115b7..e0230f205f5 100644 --- a/web/app/accounts/reset-password/page.tsx +++ b/web/app/accounts/reset-password/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; +import { observer } from "mobx-react"; import Image from "next/image"; import Link from "next/link"; import { useSearchParams } from "next/navigation"; @@ -8,6 +9,7 @@ import { useSearchParams } from "next/navigation"; import { useTheme } from "next-themes"; import { Eye, EyeOff } from "lucide-react"; // ui +import { useTranslation } from "@plane/i18n"; import { Button, Input } from "@plane/ui"; // components import { AuthBanner, PasswordStrengthMeter } from "@/components/account"; @@ -45,7 +47,7 @@ const defaultValues: TResetPasswordFormValues = { // services const authService = new AuthService(); -export default function ResetPasswordPage() { +const ResetPasswordPage = observer(() => { // search params const searchParams = useSearchParams(); const uidb64 = searchParams.get("uidb64"); @@ -65,7 +67,8 @@ export default function ResetPasswordPage() { const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false); const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false); const [errorInfo, setErrorInfo] = useState(undefined); - + // plane hooks + const { t } = useTranslation(); // hooks const { resolvedTheme } = useTheme(); @@ -127,9 +130,9 @@ export default function ResetPasswordPage() {

- Set new password + {t("auth.reset_password.title")}

-

Secure your account with a strong password

+

{t("auth.reset_password.description")}

{errorInfo && errorInfo?.type === EErrorAlertType.BANNER_ALERT && ( setErrorInfo(value)} /> @@ -142,7 +145,7 @@ export default function ResetPasswordPage() {
handleFormChange("password", e.target.value)} //hasError={Boolean(errors.password)} - placeholder="Enter password" + placeholder={t("auth.common.password.placeholder")} className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" minLength={8} onFocus={() => setIsPasswordInputFocused(true)} @@ -193,7 +196,7 @@ export default function ResetPasswordPage() {
handleFormChange("confirm_password", e.target.value)} - placeholder="Confirm password" + placeholder={t("auth.common.password.confirm_password.placeholder")} className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" onFocus={() => setIsRetryPasswordInputFocused(true)} onBlur={() => setIsRetryPasswordInputFocused(false)} @@ -220,10 +223,12 @@ export default function ResetPasswordPage() {
{!!resetFormData.confirm_password && resetFormData.password !== resetFormData.confirm_password && - renderPasswordMatchError && Passwords don{"'"}t match} + renderPasswordMatchError && ( + {t("auth.common.password.errors.match")} + )}
@@ -232,4 +237,6 @@ export default function ResetPasswordPage() {
); -} +}); + +export default ResetPasswordPage; diff --git a/web/app/accounts/set-password/page.tsx b/web/app/accounts/set-password/page.tsx index f3ac35b76f5..5bfa7c08f5f 100644 --- a/web/app/accounts/set-password/page.tsx +++ b/web/app/accounts/set-password/page.tsx @@ -8,7 +8,8 @@ import { useSearchParams } from "next/navigation"; // icons import { useTheme } from "next-themes"; import { Eye, EyeOff } from "lucide-react"; -// ui +// plane imports +import { useTranslation } from "@plane/i18n"; import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui"; // components import { PasswordStrengthMeter } from "@/components/account"; @@ -60,9 +61,10 @@ const SetPasswordPage = observer(() => { const [csrfToken, setCsrfToken] = useState(undefined); const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false); const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false); + // plane hooks + const { t } = useTranslation(); // hooks const { resolvedTheme } = useTheme(); - // hooks const { data: user, handleSetPassword } = useUser(); useEffect(() => { @@ -95,8 +97,8 @@ const SetPasswordPage = observer(() => { } catch (err: any) { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: err?.error ?? "Something went wrong. Please try again.", + title: t("common.errors.default.title"), + message: err?.error ?? t("common.errors.default.message"), }); } }; @@ -108,7 +110,8 @@ const SetPasswordPage = observer(() => { const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo; return ( - + // TODO: change to EPageTypes.SET_PASSWORD +
{

- Secure your account + {t("auth.set_password.title")}

-

Setting password helps you login securely

+

{t("auth.set_password.description")}

handleSubmit(e)}>
{ type="email" value={user?.email} //hasError={Boolean(errors.email)} - placeholder="name@company.com" + placeholder={t("auth.common.email.placeholder")} className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 text-onboarding-text-400 cursor-not-allowed" autoComplete="on" disabled @@ -154,7 +157,7 @@ const SetPasswordPage = observer(() => {
{ value={passwordFormData.password} onChange={(e) => handleFormChange("password", e.target.value)} //hasError={Boolean(errors.password)} - placeholder="Enter password" + placeholder={t("auth.common.password.placeholder")} className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" minLength={8} onFocus={() => setIsPasswordInputFocused(true)} @@ -187,7 +190,7 @@ const SetPasswordPage = observer(() => {
{ name="confirm_password" value={passwordFormData.confirm_password} onChange={(e) => handleFormChange("confirm_password", e.target.value)} - placeholder="Confirm password" + placeholder={t("auth.common.password.confirm_password.placeholder")} className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" onFocus={() => setIsRetryPasswordInputFocused(true)} onBlur={() => setIsRetryPasswordInputFocused(false)} @@ -214,10 +217,12 @@ const SetPasswordPage = observer(() => {
{!!passwordFormData.confirm_password && passwordFormData.password !== passwordFormData.confirm_password && - renderPasswordMatchError && Passwords don{"'"}t match} + renderPasswordMatchError && ( + {t("auth.common.password.errors.match")} + )}
diff --git a/web/app/create-workspace/page.tsx b/web/app/create-workspace/page.tsx index 77b71492f3a..8425320345a 100644 --- a/web/app/create-workspace/page.tsx +++ b/web/app/create-workspace/page.tsx @@ -42,11 +42,12 @@ const CreateWorkspacePage = observer(() => { // methods const getMailtoHref = () => { - const subject = t("workspace_request_subject"); - const body = t("workspace_request_body") - .replace("{{firstName}}", currentUser?.first_name || "") - .replace("{{lastName}}", currentUser?.last_name || "") - .replace("{{email}}", currentUser?.email || ""); + const subject = t("workspace_creation.request_email.subject"); + const body = t("workspace_creation.request_email.body", { + firstName: currentUser?.first_name || "", + lastName: currentUser?.last_name || "", + email: currentUser?.email || "", + }); return `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; }; @@ -67,7 +68,7 @@ const CreateWorkspacePage = observer(() => { href="/" >
- {t("plane_logo")} + Plane logo
@@ -77,30 +78,25 @@ const CreateWorkspacePage = observer(() => {
{isWorkspaceCreationDisabled ? (
- {t("workspace_creation_disabled")} + Workspace creation disabled
- {t("only_your_instance_admin_can_create_workspaces")} + {t("workspace_creation.errors.creation_disabled.title")}

- {t("only_your_instance_admin_can_create_workspaces_description")} + {t("workspace_creation.errors.creation_disabled.description")}

) : (
-

{t("create_your_workspace")}

+

{t("workspace_creation.heading")}

{ const { resolvedTheme } = useTheme(); + // plane hooks + const { t } = useTranslation(); // hooks const { captureEvent } = useEventTracker(); @@ -37,7 +40,7 @@ const HomePage = observer(() => { <>
- +
{
- New to Plane?{" "} + {t("auth.common.new_to_plane")} captureEvent(NAVIGATE_TO_SIGNUP, {})} className="font-semibold text-custom-primary-100 hover:underline" > - Create an account + {t("auth.common.create_account")}
diff --git a/web/app/profile/activity/page.tsx b/web/app/profile/activity/page.tsx index a2a8cad851f..ca6c9511d06 100644 --- a/web/app/profile/activity/page.tsx +++ b/web/app/profile/activity/page.tsx @@ -7,24 +7,27 @@ import { useTranslation } from "@plane/i18n"; import { Button } from "@plane/ui"; // components import { PageHead } from "@/components/core"; -import { EmptyState } from "@/components/empty-state"; +import { DetailedEmptyState } from "@/components/empty-state"; import { ProfileActivityListPage, ProfileSettingContentHeader, ProfileSettingContentWrapper, } from "@/components/profile"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; +// hooks +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; const PER_PAGE = 100; const ProfileActivityPage = observer(() => { - const { t } = useTranslation(); // states const [pageCount, setPageCount] = useState(1); const [totalPages, setTotalPages] = useState(0); const [resultsCount, setResultsCount] = useState(0); const [isEmpty, setIsEmpty] = useState(false); + // plane hooks + const { t } = useTranslation(); + // derived values + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/profile/activity" }); const updateTotalPages = (count: number) => setTotalPages(count); @@ -50,7 +53,13 @@ const ProfileActivityPage = observer(() => { const isLoadMoreVisible = pageCount < totalPages && resultsCount !== 0; if (isEmpty) { - return ; + return ( + + ); } return ( diff --git a/web/app/profile/appearance/page.tsx b/web/app/profile/appearance/page.tsx index 5b1a96c5be1..c89bcdf3c33 100644 --- a/web/app/profile/appearance/page.tsx +++ b/web/app/profile/appearance/page.tsx @@ -3,6 +3,8 @@ import { useEffect, useState } from "react"; import { observer } from "mobx-react"; import { useTheme } from "next-themes"; +// plane imports +import { I_THEME_OPTION, THEME_OPTIONS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { IUserTheme } from "@plane/types"; import { setPromiseToast } from "@plane/ui"; @@ -11,7 +13,6 @@ import { LogoSpinner } from "@/components/common"; import { CustomThemeSelector, ThemeSwitch, PageHead } from "@/components/core"; import { ProfileSettingContentHeader, ProfileSettingContentWrapper } from "@/components/profile"; // constants -import { I_THEME_OPTION, THEME_OPTIONS } from "@/constants/themes"; // helpers import { applyTheme, unsetCustomCssVariables } from "@/helpers/theme.helper"; // hooks diff --git a/web/app/profile/notifications/page.tsx b/web/app/profile/notifications/page.tsx index cbdcd147d73..5e154fdffc1 100644 --- a/web/app/profile/notifications/page.tsx +++ b/web/app/profile/notifications/page.tsx @@ -25,7 +25,7 @@ export default function ProfileNotificationPage() { return ( <> - + { return ( <> - + diff --git a/web/app/profile/security/page.tsx b/web/app/profile/security/page.tsx index 48996de34f0..86098f122bf 100644 --- a/web/app/profile/security/page.tsx +++ b/web/app/profile/security/page.tsx @@ -79,16 +79,16 @@ const SecurityPage = observer(() => { setShowPassword(defaultShowPassword); setToast({ type: TOAST_TYPE.SUCCESS, - title: t("success"), - message: t("password_changed_successfully"), + title: t("auth.common.password.toast.change_password.success.title"), + message: t("auth.common.password.toast.change_password.success.message"), }); } catch (err: any) { const errorInfo = authErrorHandler(err.error_code?.toString()); setToast({ type: TOAST_TYPE.ERROR, - title: errorInfo?.title ?? "Error!", + title: errorInfo?.title ?? t("auth.common.password.toast.error.title"), message: - typeof errorInfo?.message === "string" ? errorInfo.message : t("something_went_wrong_please_try_again"), + typeof errorInfo?.message === "string" ? errorInfo.message : t("auth.common.password.toast.error.message"), }); } }; @@ -112,17 +112,17 @@ const SecurityPage = observer(() => { <> - +
-

{t("current_password")}

+

{t("auth.common.password.current_password.label")}

( { {errors.old_password && {errors.old_password.message}}
-

{t("new_password")}

+

{t("auth.common.password.new_password.label")}

( { )}
-

{t("confirm_password")}

+

{t("auth.common.password.confirm_password.label")}

( { )}
{!!confirmPassword && password !== confirmPassword && renderPasswordMatchError && ( - {t("passwords_dont_match")} + {t("auth.common.password.errors.match")} )}
diff --git a/web/app/profile/sidebar.tsx b/web/app/profile/sidebar.tsx index d3b98642161..07fd746c640 100644 --- a/web/app/profile/sidebar.tsx +++ b/web/app/profile/sidebar.tsx @@ -5,15 +5,26 @@ import { observer } from "mobx-react"; import Link from "next/link"; import { usePathname } from "next/navigation"; // icons -import { ChevronLeft, LogOut, MoveLeft, Plus, UserPlus } from "lucide-react"; +import { + ChevronLeft, + LogOut, + MoveLeft, + Plus, + UserPlus, + Activity, + Bell, + CircleUser, + KeyRound, + Settings2, +} from "lucide-react"; // plane imports +import { PROFILE_ACTION_LINKS } from "@plane/constants"; import { useOutsideClickDetector } from "@plane/hooks"; import { useTranslation } from "@plane/i18n"; import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; // components import { SidebarNavItem } from "@/components/sidebar"; // constants -import { PROFILE_ACTION_LINKS } from "@/constants/profile"; // helpers import { cn } from "@/helpers/common.helper"; import { getFileURL } from "@/helpers/file.helper"; @@ -36,6 +47,19 @@ const WORKSPACE_ACTION_LINKS = [ }, ]; +export const ProjectActionIcons = ({ type, size, className }: { type: string; size?: number; className?: string }) => { + const icons = { + profile: CircleUser, + security: KeyRound, + activity: Activity, + appearance: Settings2, + notifications: Bell, + }; + + if (type === undefined) return null; + const Icon = icons[type as keyof typeof icons]; + return ; +}; export const ProfileLayoutSidebar = observer(() => { // states const [isSigningOut, setIsSigningOut] = useState(false); @@ -92,8 +116,8 @@ export const ProfileLayoutSidebar = observer(() => { .catch(() => setToast({ type: TOAST_TYPE.ERROR, - title: t("error"), - message: t("failed_to_sign_out_please_try_again"), + title: t("sign_out.toast.error.title"), + message: t("sign_out.toast.error.message"), }) ) .finally(() => setIsSigningOut(false)); @@ -145,8 +169,9 @@ export const ProfileLayoutSidebar = observer(() => { isActive={link.highlight(pathname)} >
- - {!sidebarCollapsed &&

{t(link.key)}

} + + + {!sidebarCollapsed &&

{t(link.i18n_label)}

}
diff --git a/web/app/provider.tsx b/web/app/provider.tsx index 34526ffd14c..b120882f7d1 100644 --- a/web/app/provider.tsx +++ b/web/app/provider.tsx @@ -5,10 +5,9 @@ import dynamic from "next/dynamic"; import { useTheme, ThemeProvider } from "next-themes"; import { SWRConfig } from "swr"; // Plane Imports +import { WEB_SWR_CONFIG } from "@plane/constants"; import { TranslationProvider } from "@plane/i18n"; import { Toast } from "@plane/ui"; -// constants -import { SWR_CONFIG } from "@/constants/swr-config"; //helpers import { resolveGeneralTheme } from "@/helpers/theme.helper"; // nprogress @@ -47,7 +46,7 @@ export const AppProvider: FC = (props) => { - {children} + {children} diff --git a/web/app/sign-up/page.tsx b/web/app/sign-up/page.tsx index f08ccbae7b7..aa18cf085a8 100644 --- a/web/app/sign-up/page.tsx +++ b/web/app/sign-up/page.tsx @@ -6,9 +6,10 @@ import Link from "next/link"; // ui import { useTheme } from "next-themes"; // components +import { NAVIGATE_TO_SIGNIN } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { AuthRoot } from "@/components/account"; // constants -import { NAVIGATE_TO_SIGNIN } from "@/constants/event-tracker"; // helpers import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper"; // hooks @@ -23,6 +24,8 @@ import WhiteHorizontalLogo from "@/public/plane-logos/white-horizontal-with-blue export type AuthType = "sign-in" | "sign-up"; const SignInPage = observer(() => { + // plane hooks + const { t } = useTranslation(); // store hooks const { captureEvent } = useEventTracker(); // hooks @@ -48,13 +51,13 @@ const SignInPage = observer(() => {
- Already have an account?{" "} + {t("auth.common.already_have_an_account")} captureEvent(NAVIGATE_TO_SIGNIN, {})} className="font-semibold text-custom-primary-100 hover:underline" > - Log in + {t("auth.common.login")}
diff --git a/web/app/workspace-invitations/page.tsx b/web/app/workspace-invitations/page.tsx index a6829019892..8d981fff42d 100644 --- a/web/app/workspace-invitations/page.tsx +++ b/web/app/workspace-invitations/page.tsx @@ -82,7 +82,7 @@ const WorkspaceInvitationPage = observer(() => { ) : ( @@ -92,14 +92,14 @@ const WorkspaceInvitationPage = observer(() => { invitationDetail?.accepted ? ( ) : ( {!currentUser ? ( diff --git a/web/ce/components/active-cycles/workspace-active-cycles-upgrade.tsx b/web/ce/components/active-cycles/workspace-active-cycles-upgrade.tsx index b832aa5952f..e8a13f0361b 100644 --- a/web/ce/components/active-cycles/workspace-active-cycles-upgrade.tsx +++ b/web/ce/components/active-cycles/workspace-active-cycles-upgrade.tsx @@ -38,9 +38,9 @@ export const WORKSPACE_ACTIVE_CYCLES_DETAILS = [ }, { key: "quickly_see_make_or_break_issues", - title: "Quickly see make-or-break issues. ", + title: "Quickly see make-or-break work items. ", description: - "Preview high-priority issues for each cycle against due dates. See all of them per cycle in one click.", + "Preview high-priority work items for each cycle against due dates. See all of them per cycle in one click.", icon: AlertOctagon, }, { diff --git a/web/ce/components/cycles/active-cycle/root.tsx b/web/ce/components/cycles/active-cycle/root.tsx index 5ebddc63f23..6b2ecb928be 100644 --- a/web/ce/components/cycles/active-cycle/root.tsx +++ b/web/ce/components/cycles/active-cycle/root.tsx @@ -3,7 +3,8 @@ import { useMemo } from "react"; import { observer } from "mobx-react"; import { Disclosure } from "@headlessui/react"; -// ui +// plane imports +import { useTranslation } from "@plane/i18n"; import { Row } from "@plane/ui"; // components import { @@ -14,10 +15,10 @@ import { CyclesListItem, } from "@/components/cycles"; import useCyclesDetails from "@/components/cycles/active-cycle/use-cycles-details"; -import { EmptyState } from "@/components/empty-state"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; +import { DetailedEmptyState } from "@/components/empty-state"; +// hooks import { useCycle } from "@/hooks/store"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { ActiveCycleIssueDetails } from "@/store/issue/cycle"; interface IActiveCycleDetails { @@ -29,9 +30,13 @@ interface IActiveCycleDetails { export const ActiveCycleRoot: React.FC = observer((props) => { const { workspaceSlug, projectId, cycleId: propsCycleId, showHeader = true } = props; + // plane hooks + const { t } = useTranslation(); + // store hooks const { currentProjectActiveCycleId } = useCycle(); // derived values const cycleId = propsCycleId ?? currentProjectActiveCycleId; + const activeCycleResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/cycle/active" }); // fetch cycle details const { handleFiltersUpdate, @@ -43,7 +48,11 @@ export const ActiveCycleRoot: React.FC = observer((props) = () => ( <> {!cycleId || !activeCycle ? ( - + ) : (
{cycleId && ( @@ -88,7 +97,7 @@ export const ActiveCycleRoot: React.FC = observer((props) = {({ open }) => ( <> - + {ActiveCyclesComponent} diff --git a/web/ce/components/cycles/analytics-sidebar/base.tsx b/web/ce/components/cycles/analytics-sidebar/base.tsx index 87f07e387c0..c259a4a81d2 100644 --- a/web/ce/components/cycles/analytics-sidebar/base.tsx +++ b/web/ce/components/cycles/analytics-sidebar/base.tsx @@ -2,6 +2,7 @@ import { FC, Fragment } from "react"; import { observer } from "mobx-react"; // plane ui +import { useTranslation } from "@plane/i18n"; import { TCycleEstimateType } from "@plane/types"; import { Loader } from "@plane/ui"; // components @@ -23,6 +24,7 @@ export const SidebarChart: FC = observer((props) => { // hooks const { getEstimateTypeByCycleId, getCycleById, fetchCycleDetails, fetchArchivedCycleDetails, setEstimateType } = useCycle(); + const { t } = useTranslation(); // derived data const cycleDetails = validateCycleSnapshot(getCycleById(cycleId)); @@ -66,11 +68,11 @@ export const SidebarChart: FC = observer((props) => {
- Ideal + {t("ideal")}
- Current + {t("current")}
{cycleStartDate && cycleEndDate && completionChartDistributionData ? ( @@ -80,7 +82,7 @@ export const SidebarChart: FC = observer((props) => { startDate={cycleStartDate} endDate={cycleEndDate} totalIssues={estimateType === "points" ? totalEstimatePoints : totalIssues} - plotTitle={estimateType === "points" ? "points" : "issues"} + plotTitle={estimateType === "points" ? t("points") : t("work_items")} /> ) : ( diff --git a/web/ce/components/issues/header.tsx b/web/ce/components/issues/header.tsx index e231096ce61..abe44d506f0 100644 --- a/web/ce/components/issues/header.tsx +++ b/web/ce/components/issues/header.tsx @@ -5,7 +5,8 @@ import { useParams } from "next/navigation"; // icons import { Circle, ExternalLink } from "lucide-react"; // plane constants -import { EIssuesStoreType } from "@plane/constants"; +import { EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // ui import { Breadcrumbs, Button, LayersIcon, Tooltip, Header } from "@plane/ui"; // components @@ -21,7 +22,6 @@ import { useAppRouter } from "@/hooks/use-app-router"; import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; export const IssuesHeader = observer(() => { // router @@ -31,6 +31,8 @@ export const IssuesHeader = observer(() => { const { issues: { getGroupIssueCount }, } = useIssues(EIssuesStoreType.PROJECT); + // i18n + const { t } = useTranslation(); const { currentProjectDetails, loader } = useProject(); @@ -57,13 +59,18 @@ export const IssuesHeader = observer(() => { } />} + link={ + } + /> + } /> {issuesCount && issuesCount > 0 ? ( 1 ? "issues" : "issue"} in this project`} + tooltipContent={`There are ${issuesCount} ${issuesCount > 1 ? "work items" : "work item"} in this project`} position="bottom" > @@ -78,7 +85,7 @@ export const IssuesHeader = observer(() => { rel="noopener noreferrer" > - Public + {t("workspace_projects.network.public.title")} ) : ( @@ -97,12 +104,13 @@ export const IssuesHeader = observer(() => { {canUserCreateIssue ? ( ) : ( <> diff --git a/web/ce/components/issues/issue-details/issue-identifier.tsx b/web/ce/components/issues/issue-details/issue-identifier.tsx index fbd9439842b..1e87d387ec9 100644 --- a/web/ce/components/issues/issue-details/issue-identifier.tsx +++ b/web/ce/components/issues/issue-details/issue-identifier.tsx @@ -50,7 +50,7 @@ export const IdentifierText: React.FC = (props) => { navigator.clipboard.writeText(identifier).then(() => { setToast({ type: TOAST_TYPE.SUCCESS, - title: "Issue ID copied to clipboard", + title: "Work item ID copied to clipboard", }); }); } diff --git a/web/ce/components/issues/worklog/activity/filter-root.tsx b/web/ce/components/issues/worklog/activity/filter-root.tsx index 2d11ae34148..a4dc62a6d51 100644 --- a/web/ce/components/issues/worklog/activity/filter-root.tsx +++ b/web/ce/components/issues/worklog/activity/filter-root.tsx @@ -2,9 +2,9 @@ import { FC } from "react"; // components +import { TActivityFilters, ACTIVITY_FILTER_TYPE_OPTIONS, TActivityFilterOption } from "@plane/constants"; import { ActivityFilter } from "@/components/issues"; // plane web constants -import { TActivityFilters, ACTIVITY_FILTER_TYPE_OPTIONS, TActivityFilterOption } from "@/plane-web/constants/issues"; export type TActivityFilterRoot = { selectedFilters: TActivityFilters[]; @@ -20,7 +20,7 @@ export const ActivityFilterRoot: FC = (props) => { const filterKey = key as TActivityFilters; return { key: filterKey, - label: value.label, + labelTranslationKey: value.labelTranslationKey, isSelected: selectedFilters.includes(filterKey), onClick: () => toggleFilter(filterKey), }; diff --git a/web/ce/components/pages/editor/embed/issue-embed-upgrade-card.tsx b/web/ce/components/pages/editor/embed/issue-embed-upgrade-card.tsx index d9ea1b8a071..b4f68028d84 100644 --- a/web/ce/components/pages/editor/embed/issue-embed-upgrade-card.tsx +++ b/web/ce/components/pages/editor/embed/issue-embed-upgrade-card.tsx @@ -18,7 +18,7 @@ export const IssueEmbedUpgradeCard: React.FC = (props) => (

- Embed and access issues in pages seamlessly, upgrade to Plane Pro now. + Embed and access work items in pages seamlessly, upgrade to Plane Pro now.

= (props) => {
{currentNetwork ? ( <> - - {currentNetwork.label} + + {t(currentNetwork.i18n_label)} ) : ( {t("select_network")} @@ -56,10 +55,10 @@ const ProjectAttributes: FC = (props) => { {NETWORK_CHOICES.map((network) => (
- +
-

{network.label}

-

{network.description}

+

{t(network.i18n_label)}

+

{t(network.description)}

diff --git a/web/ce/components/projects/create/root.tsx b/web/ce/components/projects/create/root.tsx index 8d77d2c0150..c8ba67d503a 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 { PROJECT_UNSPLASH_COVERS, PROJECT_CREATED } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // ui import { setToast, TOAST_TYPE } from "@plane/ui"; @@ -10,8 +11,6 @@ import { setToast, TOAST_TYPE } from "@plane/ui"; import ProjectCommonAttributes from "@/components/project/create/common-attributes"; import ProjectCreateHeader from "@/components/project/create/header"; import ProjectCreateButtons from "@/components/project/create/project-create-buttons"; -import { PROJECT_CREATED } from "@/constants/event-tracker"; -import { PROJECT_UNSPLASH_COVERS } from "@/constants/project"; // helpers import { getRandomEmoji } from "@/helpers/emoji.helper"; // hooks diff --git a/web/ce/components/projects/mobile-header.tsx b/web/ce/components/projects/mobile-header.tsx index 3804721600e..9e51c679991 100644 --- a/web/ce/components/projects/mobile-header.tsx +++ b/web/ce/components/projects/mobile-header.tsx @@ -4,6 +4,8 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // icons import { ChevronDown, ListFilter } from "lucide-react"; +// i18n +import { useTranslation } from "@plane/i18n"; // types import { TProjectFilters } from "@plane/types"; // hooks @@ -15,6 +17,8 @@ import { calculateTotalFilters } from "@/helpers/filter.helper"; import { useMember, useProjectFilter } from "@/hooks/store"; export const ProjectsListMobileHeader = observer(() => { + // i18n + const { t } = useTranslation(); // router const { workspaceSlug } = useParams(); const { @@ -63,12 +67,12 @@ export const ProjectsListMobileHeader = observer(() => {
} - title="Filters" + title={t("common.filters")} placement="bottom-end" menuButton={
- Filters + {t("common.filters")}
} diff --git a/web/ce/components/projects/settings/intake/header.tsx b/web/ce/components/projects/settings/intake/header.tsx index 38270f3c91d..32a93894f1e 100644 --- a/web/ce/components/projects/settings/intake/header.tsx +++ b/web/ce/components/projects/settings/intake/header.tsx @@ -5,6 +5,8 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { RefreshCcw } from "lucide-react"; // ui +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Breadcrumbs, Button, Intake, Header } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; @@ -13,7 +15,6 @@ import { InboxIssueCreateModalRoot } from "@/components/inbox"; import { useProject, useProjectInbox, useUserPermissions } from "@/hooks/store"; // plane web import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; export const ProjectInboxHeader: FC = observer(() => { // states @@ -22,6 +23,7 @@ export const ProjectInboxHeader: FC = observer(() => { const { workspaceSlug, projectId } = useParams(); // store hooks const { allowPermissions } = useUserPermissions(); + const { t } = useTranslation(); const { currentProjectDetails, loader: currentProjectDetailsLoader } = useProject(); const { loader } = useProjectInbox(); @@ -41,14 +43,14 @@ export const ProjectInboxHeader: FC = observer(() => { } />} + link={} />} /> {loader === "pagination-loading" && (
-

Syncing...

+

{t("syncing")}...

)}
@@ -64,7 +66,7 @@ export const ProjectInboxHeader: FC = observer(() => { />
) : ( diff --git a/web/ce/components/projects/settings/useProjectColumns.tsx b/web/ce/components/projects/settings/useProjectColumns.tsx index 8103e9eefaf..bd9e589ad17 100644 --- a/web/ce/components/projects/settings/useProjectColumns.tsx +++ b/web/ce/components/projects/settings/useProjectColumns.tsx @@ -1,9 +1,9 @@ import { useState } from "react"; import { useParams } from "next/navigation"; +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { IWorkspaceMember } from "@plane/types"; import { AccountTypeColumn, NameColumn } from "@/components/project/settings/member-columns"; import { useUser, useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; export interface RowData { member: IWorkspaceMember; diff --git a/web/ce/components/relations/activity.ts b/web/ce/components/relations/activity.ts index ad0d4f30521..30f08cb7492 100644 --- a/web/ce/components/relations/activity.ts +++ b/web/ce/components/relations/activity.ts @@ -5,15 +5,15 @@ export const getRelationActivityContent = (activity: TIssueActivity | undefined) switch (activity.field) { case "blocking": - return activity.old_value === "" ? `marked this issue is blocking issue ` : `removed the blocking issue `; + return activity.old_value === "" ? `marked this work item is blocking work item ` : `removed the blocking work item `; case "blocked_by": return activity.old_value === "" - ? `marked this issue is being blocked by ` - : `removed this issue being blocked by issue `; + ? `marked this work item is being blocked by ` + : `removed this work item being blocked by work item `; case "duplicate": - return activity.old_value === "" ? `marked this issue as duplicate of ` : `removed this issue as a duplicate of `; + return activity.old_value === "" ? `marked this work item as duplicate of ` : `removed this work item as a duplicate of `; case "relates_to": - return activity.old_value === "" ? `marked that this issue relates to ` : `removed the relation from `; + return activity.old_value === "" ? `marked that this work item relates to ` : `removed the relation from `; } return; diff --git a/web/ce/components/relations/index.tsx b/web/ce/components/relations/index.tsx index 5517ac06359..7f906564a0b 100644 --- a/web/ce/components/relations/index.tsx +++ b/web/ce/components/relations/index.tsx @@ -8,28 +8,28 @@ export * from "./activity"; export const ISSUE_RELATION_OPTIONS: Record = { relates_to: { key: "relates_to", - label: "Relates to", + i18n_label: "issue.relation.relates_to", className: "bg-custom-background-80 text-custom-text-200", icon: (size) => , - placeholder: "Add related issues", + placeholder: "Add related work items", }, duplicate: { key: "duplicate", - label: "Duplicate of", + i18n_label: "issue.relation.duplicate", className: "bg-custom-background-80 text-custom-text-200", icon: (size) => , placeholder: "None", }, blocked_by: { key: "blocked_by", - label: "Blocked by", + i18n_label: "issue.relation.blocked_by", className: "bg-red-500/20 text-red-700", icon: (size) => , placeholder: "None", }, blocking: { key: "blocking", - label: "Blocking", + i18n_label: "issue.relation.blocking", className: "bg-yellow-500/20 text-yellow-700", icon: (size) => , placeholder: "None", diff --git a/web/ce/components/workflow/state-item-child.tsx b/web/ce/components/workflow/state-item-child.tsx index 2c07fd9ff00..af686bbe57b 100644 --- a/web/ce/components/workflow/state-item-child.tsx +++ b/web/ce/components/workflow/state-item-child.tsx @@ -1,11 +1,11 @@ import { SetStateAction } from "react"; import { observer } from "mobx-react"; // Plane +import { DISPLAY_WORKFLOW_PRO_CTA } from "@plane/constants"; import { IState } from "@plane/types"; // components import { StateItemTitle } from "@/components/project-states/state-item-title"; // constants -import { DISPLAY_WORKFLOW_PRO_CTA } from "@/constants/state"; // import { AddStateTransition } from "./add-state-transition"; diff --git a/web/ce/components/workspace-notifications/notification-card/root.tsx b/web/ce/components/workspace-notifications/notification-card/root.tsx index bfff113bafe..2aff9edfea8 100644 --- a/web/ce/components/workspace-notifications/notification-card/root.tsx +++ b/web/ce/components/workspace-notifications/notification-card/root.tsx @@ -2,10 +2,12 @@ import { FC } from "react"; import { observer } from "mobx-react"; +// plane imports +import { ENotificationLoader, ENotificationQueryParamType } from "@plane/constants"; // components +import { useTranslation } from "@plane/i18n"; import { NotificationItem } from "@/components/workspace-notifications"; // constants -import { ENotificationLoader, ENotificationQueryParamType } from "@/constants/notification"; // hooks import { useWorkspaceNotifications } from "@/hooks/store"; @@ -19,6 +21,7 @@ export const NotificationCardListRoot: FC = observer( // hooks const { loader, paginationInfo, getNotifications, notificationIdsByWorkspaceId } = useWorkspaceNotifications(); const notificationIds = notificationIdsByWorkspaceId(workspaceId); + const { t } = useTranslation(); const getNextNotifications = async () => { try { @@ -40,12 +43,12 @@ export const NotificationCardListRoot: FC = observer( <> {loader === ENotificationLoader.PAGINATION_LOADER ? (
-
Loading...
+
{t("loading")}...
) : (
- Load more + {t("load_more")}
)} diff --git a/web/ce/components/workspace/billing/root.tsx b/web/ce/components/workspace/billing/root.tsx index 4acba0af06d..e9c6bd8f7c0 100644 --- a/web/ce/components/workspace/billing/root.tsx +++ b/web/ce/components/workspace/billing/root.tsx @@ -1,21 +1,27 @@ import { MARKETING_PRICING_PAGE_LINK } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Button } from "@plane/ui"; -export const BillingRoot = () => ( -
-
-
-

Billing and Plans

-
-
-
+export const BillingRoot = () => { + const { t } = useTranslation(); + return ( +
-

Current plan

-

You are currently using the free plan

-
- - +
+

{t("workspace_settings.settings.billing_and_plans.title")}

+
+
+
+
+

{t("workspace_settings.settings.billing_and_plans.current_plan")}

+

+ {t("workspace_settings.settings.billing_and_plans.free_plan")} +

+ + + +
-
-
-); + + ); +}; diff --git a/web/ce/components/workspace/delete-workspace-section.tsx b/web/ce/components/workspace/delete-workspace-section.tsx index 93836284f8f..81fc754593f 100644 --- a/web/ce/components/workspace/delete-workspace-section.tsx +++ b/web/ce/components/workspace/delete-workspace-section.tsx @@ -2,6 +2,7 @@ import { FC, useState } from "react"; import { observer } from "mobx-react"; import { ChevronDown, ChevronUp } from "lucide-react"; // types +import { useTranslation } from "@plane/i18n"; import { IWorkspace } from "@plane/types"; // ui import { Button, Collapsible } from "@plane/ui"; @@ -17,6 +18,7 @@ export const DeleteWorkspaceSection: FC = observer((props) => // states const [isOpen, setIsOpen] = useState(false); const [deleteWorkspaceModal, setDeleteWorkspaceModal] = useState(false); + const { t } = useTranslation(); return ( <> @@ -34,19 +36,20 @@ export const DeleteWorkspaceSection: FC = observer((props) => buttonClassName="flex w-full items-center justify-between py-4" title={ <> - Delete workspace + + {t("workspace_settings.settings.general.delete_workspace")} + {isOpen ? : } } >
- When deleting a workspace, all of the data and resources within that workspace will be permanently - removed and cannot be recovered. + {t("workspace_settings.settings.general.delete_workspace_description")}
diff --git a/web/ce/components/workspace/edition-badge.tsx b/web/ce/components/workspace/edition-badge.tsx index 82d4f47d244..effb5501b4b 100644 --- a/web/ce/components/workspace/edition-badge.tsx +++ b/web/ce/components/workspace/edition-badge.tsx @@ -1,12 +1,12 @@ import { useState } from "react"; import { observer } from "mobx-react"; +import packageJson from "package.json"; import { useTranslation } from "@plane/i18n"; // ui import { Button, Tooltip } from "@plane/ui"; // hooks import { usePlatformOS } from "@/hooks/use-platform-os"; // assets -import packageJson from "package.json"; // local components import { PaidPlanUpgradeModal } from "./upgrade"; @@ -29,7 +29,7 @@ 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)} > - {t("upgrade")} + {t("sidebar.upgrade")}
diff --git a/web/ce/components/workspace/settings/useMemberColumns.tsx b/web/ce/components/workspace/settings/useMemberColumns.tsx index 64c92cf52ab..c3405187842 100644 --- a/web/ce/components/workspace/settings/useMemberColumns.tsx +++ b/web/ce/components/workspace/settings/useMemberColumns.tsx @@ -1,8 +1,9 @@ import { useState } from "react"; import { useParams } from "next/navigation"; +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { AccountTypeColumn, NameColumn, RowData } from "@/components/workspace/settings/member-columns"; import { useUser, useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; export const useMemberColumns = () => { // states @@ -12,6 +13,7 @@ export const useMemberColumns = () => { const { data: currentUser } = useUser(); const { allowPermissions } = useUserPermissions(); + const { t } = useTranslation(); const getFormattedDate = (dateStr: string) => { const date = new Date(dateStr); @@ -26,7 +28,7 @@ export const useMemberColumns = () => { const columns = [ { key: "Full name", - content: "Full name", + content: t("workspace_settings.settings.members.details.full_name"), thClassName: "text-left", tdRender: (rowData: RowData) => ( { { key: "Display name", - content: "Display name", + content: t("workspace_settings.settings.members.details.display_name"), tdRender: (rowData: RowData) =>
{rowData.member.display_name}
, }, { key: "Email address", - content: "Email address", + content: t("workspace_settings.settings.members.details.email_address"), tdRender: (rowData: RowData) =>
{rowData.member.email}
, }, { key: "Account type", - content: "Account type", + content: t("workspace_settings.settings.members.details.account_type"), tdRender: (rowData: RowData) => , }, { key: "Authentication", - content: "Authentication", + content: t("workspace_settings.settings.members.details.authentication"), tdRender: (rowData: RowData) => (
{rowData.member.last_login_medium?.replace("-", " ")}
), @@ -67,7 +69,7 @@ export const useMemberColumns = () => { { key: "Joining date", - content: "Joining date", + content: t("workspace_settings.settings.members.details.joining_date"), tdRender: (rowData: RowData) =>
{getFormattedDate(rowData?.member?.joining_date || "")}
, }, ]; diff --git a/web/ce/components/workspace/upgrade-badge.tsx b/web/ce/components/workspace/upgrade-badge.tsx index 3fc3654cfba..1abb731e7ff 100644 --- a/web/ce/components/workspace/upgrade-badge.tsx +++ b/web/ce/components/workspace/upgrade-badge.tsx @@ -1,5 +1,6 @@ import { FC } from "react"; // helpers +import { useTranslation } from "@plane/i18n"; import { cn } from "@/helpers/common.helper"; type TUpgradeBadge = { @@ -10,6 +11,8 @@ type TUpgradeBadge = { export const UpgradeBadge: FC = (props) => { const { className, size = "sm" } = props; + const { t } = useTranslation(); + return (
= (props) => { className )} > - Pro + {t("sidebar.pro")}
); }; diff --git a/web/ce/components/workspace/upgrade/paid-plans-upgrade-modal.tsx b/web/ce/components/workspace/upgrade/paid-plans-upgrade-modal.tsx index 0f6d0681eb5..88201af63c0 100644 --- a/web/ce/components/workspace/upgrade/paid-plans-upgrade-modal.tsx +++ b/web/ce/components/workspace/upgrade/paid-plans-upgrade-modal.tsx @@ -22,7 +22,7 @@ const ONE_PLAN_FEATURES = [ "OIDC + SAML for SSO", "Active Cycles", "Real-time collab + public views and page", - "Link pages in issues and vice-versa", + "Link pages in work items and vice-versa", "Time-tracking + limited bulk ops", "Docker, Kubernetes and more", ]; diff --git a/web/ce/constants/index.ts b/web/ce/constants/index.ts index 123db122c8a..895c18c3dbe 100644 --- a/web/ce/constants/index.ts +++ b/web/ce/constants/index.ts @@ -1,7 +1,4 @@ export * from "./ai"; export * from "./estimates"; export * from "./gantt-chart"; -export * from "./issues"; export * from "./project"; -export * from "./user-permissions"; -export * from "./workspace"; diff --git a/web/ce/constants/issues.ts b/web/ce/constants/issues.ts deleted file mode 100644 index 70a34577f73..00000000000 --- a/web/ce/constants/issues.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ILayoutDisplayFiltersOptions, TIssueActivityComment } from "@plane/types"; - -export enum EActivityFilterType { - ACTIVITY = "ACTIVITY", - COMMENT = "COMMENT", -} - -export type TActivityFilters = EActivityFilterType; - -export const ACTIVITY_FILTER_TYPE_OPTIONS: Record = { - [EActivityFilterType.ACTIVITY]: { - label: "Updates", - }, - [EActivityFilterType.COMMENT]: { - label: "Comments", - }, -}; - -export const defaultActivityFilters: TActivityFilters[] = [EActivityFilterType.ACTIVITY, EActivityFilterType.COMMENT]; - -export type TActivityFilterOption = { - key: TActivityFilters; - label: string; - isSelected: boolean; - onClick: () => void; -}; - -export const filterActivityOnSelectedFilters = ( - activity: TIssueActivityComment[], - filter: TActivityFilters[] -): TIssueActivityComment[] => - activity.filter((activity) => filter.includes(activity.activity_type as TActivityFilters)); - -export const ENABLE_ISSUE_DEPENDENCIES = false; - -export const ADDITIONAL_ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { - [pageType: string]: { [layoutType: string]: ILayoutDisplayFiltersOptions }; -} = {}; diff --git a/web/ce/constants/project/settings/features.tsx b/web/ce/constants/project/settings/features.tsx index 786767c015c..99e6101d57c 100644 --- a/web/ce/constants/project/settings/features.tsx +++ b/web/ce/constants/project/settings/features.tsx @@ -34,7 +34,7 @@ export type TProjectFeatures = { export const PROJECT_FEATURES_LIST: TProjectFeatures = { project_features: { key: "projects_and_issues", - title: "Projects and issues", + title: "Projects and work items", description: "Toggle these on or off this project.", featureList: { cycles: { @@ -77,7 +77,7 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { key: "intake", property: "inbox_view", title: "Intake", - description: "Consider and discuss issues before you add them to your project.", + description: "Consider and discuss work items before you add them to your project.", icon: , isPro: false, isEnabled: true, diff --git a/web/ce/constants/project/settings/tabs.ts b/web/ce/constants/project/settings/tabs.ts index 4d9207cc60a..50bff11151e 100644 --- a/web/ce/constants/project/settings/tabs.ts +++ b/web/ce/constants/project/settings/tabs.ts @@ -1,14 +1,14 @@ // icons +import { EUserPermissions } from "@plane/constants"; import { SettingIcon } from "@/components/icons/attachment"; // types import { Props } from "@/components/icons/types"; // constants -import { EUserPermissions } from "../../user-permissions"; export const PROJECT_SETTINGS = { general: { key: "general", - label: "General", + i18n_label: "common.general", href: `/settings`, access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/`, @@ -16,7 +16,7 @@ export const PROJECT_SETTINGS = { }, members: { key: "members", - label: "Members", + i18n_label: "members", href: `/settings/members`, access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER], highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members/`, @@ -24,7 +24,7 @@ export const PROJECT_SETTINGS = { }, features: { key: "features", - label: "Features", + i18n_label: "common.features", href: `/settings/features`, access: [EUserPermissions.ADMIN], highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/features/`, @@ -32,7 +32,7 @@ export const PROJECT_SETTINGS = { }, states: { key: "states", - label: "States", + i18n_label: "common.states", href: `/settings/states`, access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER], highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/states/`, @@ -40,7 +40,7 @@ export const PROJECT_SETTINGS = { }, labels: { key: "labels", - label: "Labels", + i18n_label: "common.labels", href: `/settings/labels`, access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER], highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/labels/`, @@ -48,7 +48,7 @@ export const PROJECT_SETTINGS = { }, estimates: { key: "estimates", - label: "Estimates", + i18n_label: "common.estimates", href: `/settings/estimates`, access: [EUserPermissions.ADMIN], highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/estimates/`, @@ -56,7 +56,7 @@ export const PROJECT_SETTINGS = { }, automations: { key: "automations", - label: "Automations", + i18n_label: "project_settings.automations.label", href: `/settings/automations`, access: [EUserPermissions.ADMIN], highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/automations/`, @@ -66,7 +66,7 @@ export const PROJECT_SETTINGS = { export const PROJECT_SETTINGS_LINKS: { key: string; - label: string; + i18n_label: string; href: string; access: EUserPermissions[]; highlight: (pathname: string, baseUrl: string) => boolean; diff --git a/web/ce/constants/user-permissions/index.ts b/web/ce/constants/user-permissions/index.ts deleted file mode 100644 index e37a2aae929..00000000000 --- a/web/ce/constants/user-permissions/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -export enum EUserPermissionsLevel { - WORKSPACE = "WORKSPACE", - PROJECT = "PROJECT", -} -export type TUserPermissionsLevel = EUserPermissionsLevel; - -export enum EUserPermissions { - ADMIN = 20, - MEMBER = 15, - GUEST = 5, -} -export type TUserPermissions = EUserPermissions; - -export type TUserAllowedPermissionsObject = { - create: TUserPermissions[]; - update: TUserPermissions[]; - delete: TUserPermissions[]; - read: TUserPermissions[]; -}; -export type TUserAllowedPermissions = { - workspace: { - [key: string]: Partial; - }; - project: { - [key: string]: Partial; - }; -}; - -export const USER_ALLOWED_PERMISSIONS: TUserAllowedPermissions = { - workspace: { - dashboard: { - read: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], - }, - }, - project: {}, -}; diff --git a/web/ce/constants/workspace.ts b/web/ce/constants/workspace.ts deleted file mode 100644 index b976111b559..00000000000 --- a/web/ce/constants/workspace.ts +++ /dev/null @@ -1,72 +0,0 @@ -// icons -import { SettingIcon } from "@/components/icons/attachment"; -import { Props } from "@/components/icons/types"; -import { EUserPermissions } from "./user-permissions"; -// constants - -export const WORKSPACE_SETTINGS = { - general: { - key: "general", - label: "General", - href: `/settings`, - access: [EUserPermissions.ADMIN], - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/`, - Icon: SettingIcon, - }, - members: { - key: "members", - label: "Members", - href: `/settings/members`, - access: [EUserPermissions.ADMIN], - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members/`, - Icon: SettingIcon, - }, - "billing-and-plans": { - key: "billing-and-plans", - label: "Billing and plans", - href: `/settings/billing`, - access: [EUserPermissions.ADMIN], - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/billing/`, - Icon: SettingIcon, - }, - export: { - key: "export", - label: "Exports", - href: `/settings/exports`, - access: [EUserPermissions.ADMIN], - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/exports/`, - Icon: SettingIcon, - }, - webhooks: { - key: "webhooks", - label: "Webhooks", - href: `/settings/webhooks`, - access: [EUserPermissions.ADMIN], - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/webhooks/`, - Icon: SettingIcon, - }, - "api-tokens": { - key: "api-tokens", - label: "API tokens", - href: `/settings/api-tokens`, - access: [EUserPermissions.ADMIN], - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/api-tokens/`, - Icon: SettingIcon, - }, -}; - -export const WORKSPACE_SETTINGS_LINKS: { - key: string; - label: string; - href: string; - access: EUserPermissions[]; - highlight: (pathname: string, baseUrl: string) => boolean; - Icon: React.FC; -}[] = [ - WORKSPACE_SETTINGS["general"], - WORKSPACE_SETTINGS["members"], - WORKSPACE_SETTINGS["billing-and-plans"], - WORKSPACE_SETTINGS["export"], - WORKSPACE_SETTINGS["webhooks"], - WORKSPACE_SETTINGS["api-tokens"], -]; diff --git a/web/ce/helpers/command-palette.ts b/web/ce/helpers/command-palette.ts index fccfcbaa4dd..28576735f72 100644 --- a/web/ce/helpers/command-palette.ts +++ b/web/ce/helpers/command-palette.ts @@ -8,8 +8,8 @@ export const getGlobalShortcutsList: () => TCommandPaletteActionList = () => { return { c: { - title: "Create a new issue", - description: "Create a new issue in the current project", + title: "Create a new work item", + description: "Create a new work item in the current project", action: () => toggleCreateIssueModal(true), }, }; @@ -58,13 +58,13 @@ export const getProjectShortcutsList: () => TCommandPaletteActionList = () => { action: () => toggleCreateViewModal(true), }, backspace: { - title: "Bulk delete issues", - description: "Bulk delete issues in the current project", + title: "Bulk delete work items", + description: "Bulk delete work items in the current project", action: () => toggleBulkDeleteIssueModal(true), }, delete: { - title: "Bulk delete issues", - description: "Bulk delete issues in the current project", + title: "Bulk delete work items", + description: "Bulk delete work items in the current project", action: () => toggleBulkDeleteIssueModal(true), }, }; @@ -79,16 +79,16 @@ export const getNavigationShortcutsList = (): TCommandPaletteShortcut[] => [ export const getCommonShortcutsList = (platform: string): TCommandPaletteShortcut[] => [ { keys: "P", description: "Create project" }, - { keys: "C", description: "Create issue" }, + { keys: "C", description: "Create work item" }, { keys: "Q", description: "Create cycle" }, { keys: "M", description: "Create module" }, { keys: "V", description: "Create view" }, { keys: "D", description: "Create page" }, - { keys: "Delete", description: "Bulk delete issues" }, + { keys: "Delete", description: "Bulk delete work items" }, { keys: "Shift,/", description: "Open shortcuts guide" }, { keys: platform === "MacOS" ? "Ctrl,control,C" : "Ctrl,Alt,C", - description: "Copy issue URL from the issue details page", + description: "Copy work item URL from the work item details page", }, ]; diff --git a/web/ce/hooks/use-file-size.ts b/web/ce/hooks/use-file-size.ts index c19df40e55a..025c8ddfd17 100644 --- a/web/ce/hooks/use-file-size.ts +++ b/web/ce/hooks/use-file-size.ts @@ -1,5 +1,5 @@ -// constants -import { MAX_STATIC_FILE_SIZE } from "@/constants/common"; +// plane imports +import { MAX_FILE_SIZE } from "@plane/constants"; // hooks import { useInstance } from "@/hooks/store"; @@ -12,6 +12,6 @@ export const useFileSize = (): TReturnProps => { const { config } = useInstance(); return { - maxFileSize: config?.file_size_limit ?? MAX_STATIC_FILE_SIZE, + maxFileSize: config?.file_size_limit ?? MAX_FILE_SIZE, }; }; diff --git a/web/ce/services/project/view.service.ts b/web/ce/services/project/view.service.ts index 6cb76222add..9a6ee12a226 100644 --- a/web/ce/services/project/view.service.ts +++ b/web/ce/services/project/view.service.ts @@ -1,5 +1,5 @@ +import { EViewAccess } from "@plane/constants"; import { TPublishViewSettings } from "@plane/types"; -import { EViewAccess } from "@/constants/views"; import { API_BASE_URL } from "@/helpers/common.helper"; import { ViewService as CoreViewService } from "@/services/view.service"; diff --git a/web/ce/services/workspace.service.ts b/web/ce/services/workspace.service.ts index 59fe39c69ce..9dcfb7f6c5e 100644 --- a/web/ce/services/workspace.service.ts +++ b/web/ce/services/workspace.service.ts @@ -1,4 +1,4 @@ -import { EViewAccess } from "@/constants/views"; +import { EViewAccess } from "@plane/constants"; import { API_BASE_URL } from "@/helpers/common.helper"; import { WorkspaceService as CoreWorkspaceService } from "@/services/workspace.service"; diff --git a/web/ce/store/issue/issue-details/activity.store.ts b/web/ce/store/issue/issue-details/activity.store.ts index 72c725010b8..93b925aba9d 100644 --- a/web/ce/store/issue/issue-details/activity.store.ts +++ b/web/ce/store/issue/issue-details/activity.store.ts @@ -8,7 +8,7 @@ import update from "lodash/update"; import { action, makeObservable, observable, runInAction } from "mobx"; import { computedFn } from "mobx-utils"; // plane package imports -import { EIssueServiceType, E_SORT_ORDER } from "@plane/constants"; +import { EIssueServiceType, E_SORT_ORDER, EActivityFilterType } from "@plane/constants"; import { TIssueActivityComment, TIssueActivity, @@ -17,7 +17,6 @@ import { TIssueServiceType, } from "@plane/types"; // plane web constants -import { EActivityFilterType } from "@/plane-web/constants/issues"; // services import { IssueActivityService } from "@/services/issue"; // store diff --git a/web/core/components/account/auth-forms/auth-header.tsx b/web/core/components/account/auth-forms/auth-header.tsx index 4f0949aa28c..b609fe0a5c5 100644 --- a/web/core/components/account/auth-forms/auth-header.tsx +++ b/web/core/components/account/auth-forms/auth-header.tsx @@ -1,5 +1,7 @@ import { FC, ReactNode } from "react"; +import { observer } from "mobx-react"; import useSWR from "swr"; +import { useTranslation } from "@plane/i18n"; import { IWorkspaceMemberInvitation } from "@plane/types"; // components import { LogoSpinner } from "@/components/common"; @@ -21,38 +23,40 @@ type TAuthHeader = { const Titles = { [EAuthModes.SIGN_IN]: { [EAuthSteps.EMAIL]: { - header: "Log in or sign up", + header: "auth.sign_in.header.step.email.header", subHeader: "", }, [EAuthSteps.PASSWORD]: { - header: "Log in or sign up", - subHeader: "Use your email-password combination to log in.", + header: "auth.sign_in.header.step.password.header", + subHeader: "auth.sign_in.header.step.password.sub_header", }, [EAuthSteps.UNIQUE_CODE]: { - header: "Log in or Sign up", - subHeader: "Log in using a unique code sent to the email address above.", + header: "auth.sign_in.header.step.unique_code.header", + subHeader: "auth.sign_in.header.step.unique_code.sub_header", }, }, [EAuthModes.SIGN_UP]: { [EAuthSteps.EMAIL]: { - header: "Sign up", + header: "auth.sign_up.header.step.email.header", subHeader: "", }, [EAuthSteps.PASSWORD]: { - header: "Sign up", - subHeader: "Sign up using an email-password combination.", + header: "auth.sign_up.header.step.password.header", + subHeader: "auth.sign_up.header.step.password.sub_header", }, [EAuthSteps.UNIQUE_CODE]: { - header: "Sign up", - subHeader: "Sign up using a unique code sent to the email address above.", + header: "auth.sign_up.header.step.unique_code.header", + subHeader: "auth.sign_up.header.step.unique_code.sub_header", }, }, }; const workSpaceService = new WorkspaceService(); -export const AuthHeader: FC = (props) => { +export const AuthHeader: FC = observer((props) => { const { workspaceSlug, invitationId, invitationEmail, authMode, currentAuthStep, children } = props; + // plane imports + const { t } = useTranslation(); const { data: invitation, isLoading } = useSWR( workspaceSlug && invitationId ? `WORKSPACE_INVITATION_${workspaceSlug}_${invitationId}` : null, @@ -74,13 +78,12 @@ export const AuthHeader: FC = (props) => { return { header: (
- Join {" "} + {t("common.join")}{" "} + {" "} {workspace.name}
), - subHeader: `${ - mode == EAuthModes.SIGN_UP ? "Create an account" : "Sign in" - } to start managing work with your team.`, + subHeader: mode == EAuthModes.SIGN_UP ? "auth.sign_up.header.label" : "auth.sign_in.header.label", }; } @@ -99,10 +102,12 @@ export const AuthHeader: FC = (props) => { return ( <>
-

{header}

-

{subHeader}

+

+ {typeof header === "string" ? t(header) : header} +

+

{t(subHeader)}

{children} ); -}; +}); diff --git a/web/core/components/account/auth-forms/auth-root.tsx b/web/core/components/account/auth-forms/auth-root.tsx index d3fa45fa74c..004e81682b7 100644 --- a/web/core/components/account/auth-forms/auth-root.tsx +++ b/web/core/components/account/auth-forms/auth-root.tsx @@ -1,6 +1,7 @@ import React, { FC, useEffect, useState } from "react"; import { observer } from "mobx-react"; import { useSearchParams } from "next/navigation"; +import { useTranslation } from "@plane/i18n"; import { IEmailCheckData } from "@plane/types"; // components import { @@ -51,6 +52,8 @@ export const AuthRoot: FC = observer((props) => { const [email, setEmail] = useState(emailParam ? emailParam.toString() : ""); const [errorInfo, setErrorInfo] = useState(undefined); const [isExistingEmail, setIsExistingEmail] = useState(false); + // plane hooks + const { t } = useTranslation(); // hooks const { config } = useInstance(); diff --git a/web/core/components/account/auth-forms/email.tsx b/web/core/components/account/auth-forms/email.tsx index f0b88407a83..724f524421e 100644 --- a/web/core/components/account/auth-forms/email.tsx +++ b/web/core/components/account/auth-forms/email.tsx @@ -4,9 +4,9 @@ import { FC, FormEvent, useMemo, useRef, useState } from "react"; import { observer } from "mobx-react"; // icons import { CircleAlert, XCircle } from "lucide-react"; -// types +// plane imports +import { useTranslation } from "@plane/i18n"; import { IEmailCheckData } from "@plane/types"; -// ui import { Button, Input, Spinner } from "@plane/ui"; // helpers import { cn } from "@/helpers/common.helper"; @@ -22,9 +22,10 @@ export const AuthEmailForm: FC = observer((props) => { // states const [isSubmitting, setIsSubmitting] = useState(false); const [email, setEmail] = useState(defaultEmail); - + // plane hooks + const { t } = useTranslation(); const emailError = useMemo( - () => (email && !checkEmailValidity(email) ? { email: "Email is invalid" } : undefined), + () => (email && !checkEmailValidity(email) ? { email: "auth.common.email.errors.invalid" } : undefined), [email] ); @@ -40,14 +41,14 @@ export const AuthEmailForm: FC = observer((props) => { const isButtonDisabled = email.length === 0 || Boolean(emailError?.email) || isSubmitting; - const [isFocused, setIsFocused] = useState(true) + const [isFocused, setIsFocused] = useState(true); const inputRef = useRef(null); return (
= observer((props) => { !isFocused && Boolean(emailError?.email) ? `border-red-500` : `border-onboarding-border-100` )} tabIndex={-1} - onFocus={() => {setIsFocused(true)}} - onBlur={() => {setIsFocused(false)}} + onFocus={() => { + setIsFocused(true); + }} + onBlur={() => { + setIsFocused(false); + }} > = observer((props) => { type="email" value={email} onChange={(e) => setEmail(e.target.value)} - placeholder="name@company.com" + placeholder={t("auth.common.email.placeholder")} className={`disable-autofill-style h-[46px] w-full placeholder:text-onboarding-text-400 autofill:bg-red-500 border-0 focus:bg-none active:bg-transparent`} autoComplete="on" autoFocus ref={inputRef} /> - {email.length > 0 && ( + {email.length > 0 && ( { @@ -83,13 +88,13 @@ export const AuthEmailForm: FC = observer((props) => { {emailError?.email && !isFocused && (

- {emailError.email} + {t(emailError.email)}

)}
); -}); \ No newline at end of file +}); diff --git a/web/core/components/account/auth-forms/forgot-password-popover.tsx b/web/core/components/account/auth-forms/forgot-password-popover.tsx index 31bafce26b5..4062d9a68c4 100644 --- a/web/core/components/account/auth-forms/forgot-password-popover.tsx +++ b/web/core/components/account/auth-forms/forgot-password-popover.tsx @@ -2,6 +2,8 @@ import { Fragment, useState } from "react"; import { usePopper } from "react-popper"; import { X } from "lucide-react"; import { Popover } from "@headlessui/react"; +// plane imports +import { useTranslation } from "@plane/i18n"; export const ForgotPasswordPopover = () => { // popper-js refs @@ -19,6 +21,8 @@ export const ForgotPasswordPopover = () => { }, ], }); + // plane hooks + const { t } = useTranslation(); return ( @@ -28,7 +32,7 @@ export const ForgotPasswordPopover = () => { ref={setReferenceElement} className="text-xs font-medium text-custom-primary-100 outline-none" > - Forgot your password? + {t("auth.common.forgot_password")} @@ -40,9 +44,7 @@ export const ForgotPasswordPopover = () => { {...attributes.popper} > 🤥 -

- We see that your god hasn{"'"}t enabled SMTP, we will not be able to send a password reset link -

+

{t("auth.forgot_password.errors.smtp_not_enabled")}

diff --git a/web/core/components/account/auth-forms/password.tsx b/web/core/components/account/auth-forms/password.tsx index 088dc31949f..979899679a3 100644 --- a/web/core/components/account/auth-forms/password.tsx +++ b/web/core/components/account/auth-forms/password.tsx @@ -5,17 +5,13 @@ import { observer } from "mobx-react"; import Link from "next/link"; // icons import { Eye, EyeOff, Info, X, XCircle } from "lucide-react"; -// ui +// plane imports +import { FORGOT_PASSWORD, SIGN_IN_WITH_CODE, SIGN_IN_WITH_PASSWORD, SIGN_UP_WITH_PASSWORD } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Button, Input, Spinner } from "@plane/ui"; // components import { ForgotPasswordPopover, PasswordStrengthMeter } from "@/components/account"; // constants -import { - FORGOT_PASSWORD, - SIGN_IN_WITH_CODE, - SIGN_IN_WITH_PASSWORD, - SIGN_UP_WITH_PASSWORD, -} from "@/constants/event-tracker"; // helpers import { EAuthModes, EAuthSteps } from "@/helpers/authentication.helper"; import { API_BASE_URL } from "@/helpers/common.helper"; @@ -49,6 +45,8 @@ const authService = new AuthService(); export const AuthPasswordForm: React.FC = observer((props: Props) => { const { email, isSMTPConfigured, handleAuthStep, handleEmailClear, mode, nextPath } = props; + // plane imports + const { t } = useTranslation(); // hooks const { captureEvent } = useEventTracker(); // ref @@ -92,7 +90,7 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => { href={`/accounts/forgot-password?email=${encodeURIComponent(email)}`} className="text-xs font-medium text-custom-primary-100" > - Forgot your password? + {t("auth.common.forgot_password")} ) : ( @@ -134,7 +132,7 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => {
-
Try setting-up a strong password to proceed
+
{t("auth.sign_up.errors.password.strength")}
setBannerMessage(false)} @@ -158,7 +156,7 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => { if (isPasswordValid) { setIsSubmitting(true); captureEvent(mode === EAuthModes.SIGN_IN ? SIGN_IN_WITH_PASSWORD : SIGN_UP_WITH_PASSWORD); - formRef.current && formRef.current.submit(); // Manually submit the form if the condition is met + if (formRef.current) formRef.current.submit(); // Manually submit the form if the condition is met } else { setBannerMessage(true); } @@ -170,7 +168,7 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => { {nextPath && }
= observer((props: Props) => { type="email" value={passwordFormData.email} onChange={(e) => handleFormChange("email", e.target.value)} - placeholder="name@company.com" + placeholder={t("auth.common.email.placeholder")} className={`disable-autofill-style h-[46px] w-full placeholder:text-onboarding-text-400 border-0`} disabled /> @@ -196,7 +194,7 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => {
= observer((props: Props) => { name="password" value={passwordFormData.password} onChange={(e) => handleFormChange("password", e.target.value)} - placeholder="Enter password" + placeholder={t("auth.common.password.placeholder")} className="disable-autofill-style h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" onFocus={() => setIsPasswordInputFocused(true)} onBlur={() => setIsPasswordInputFocused(false)} @@ -229,7 +227,7 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => { {mode === EAuthModes.SIGN_UP && (
= observer((props: Props) => { name="confirm_password" value={passwordFormData.confirm_password} onChange={(e) => handleFormChange("confirm_password", e.target.value)} - placeholder="Confirm password" + placeholder={t("auth.common.password.confirm_password.placeholder")} className="disable-autofill-style h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" onFocus={() => setIsRetryPasswordInputFocused(true)} onBlur={() => setIsRetryPasswordInputFocused(false)} @@ -256,7 +254,9 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => {
{!!passwordFormData.confirm_password && passwordFormData.password !== passwordFormData.confirm_password && - renderPasswordMatchError && Passwords don{"'"}t match} + renderPasswordMatchError && ( + {t("auth.common.password.errors.match")} + )}
)} @@ -267,9 +267,9 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => { {isSubmitting ? ( ) : isSMTPConfigured ? ( - "Continue" + t("common.continue") ) : ( - "Go to workspace" + t("common.go_to_workspace") )} {isSMTPConfigured && ( @@ -280,7 +280,7 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => { className="w-full" size="lg" > - Sign in with unique code + {t("auth.common.sign_in_with_unique_code")} )} diff --git a/web/core/components/account/auth-forms/unique-code.tsx b/web/core/components/account/auth-forms/unique-code.tsx index 530874eb9c8..b301f268b12 100644 --- a/web/core/components/account/auth-forms/unique-code.tsx +++ b/web/core/components/account/auth-forms/unique-code.tsx @@ -2,9 +2,10 @@ import React, { useEffect, useState } from "react"; import { CircleCheck, XCircle } from "lucide-react"; +import { CODE_VERIFIED } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Button, Input, Spinner } from "@plane/ui"; // constants -import { CODE_VERIFIED } from "@/constants/event-tracker"; // helpers import { EAuthModes } from "@/helpers/authentication.helper"; import { API_BASE_URL } from "@/helpers/common.helper"; @@ -49,6 +50,8 @@ export const AuthUniqueCodeForm: React.FC = (props) => { const [isSubmitting, setIsSubmitting] = useState(false); // timer const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(0); + // plane hooks + const { t } = useTranslation(); const handleFormChange = (key: keyof TUniqueCodeFormValues, value: string) => setUniqueCodeFormData((prev) => ({ ...prev, [key]: value })); @@ -94,7 +97,7 @@ export const AuthUniqueCodeForm: React.FC = (props) => { {nextPath && }
= (props) => { type="email" value={uniqueCodeFormData.email} onChange={(e) => handleFormChange("email", e.target.value)} - placeholder="name@company.com" + placeholder={t("auth.common.email.placeholder")} className={`disable-autofill-style h-[46px] w-full placeholder:text-onboarding-text-400 border-0`} autoComplete="on" disabled @@ -121,20 +124,20 @@ export const AuthUniqueCodeForm: React.FC = (props) => {
handleFormChange("code", e.target.value)} - placeholder="gets-sets-flys" + placeholder={t("auth.common.unique_code.placeholder")} className="disable-autofill-style h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" autoFocus />

- Paste the code sent to your email + {t("auth.common.unique_code.paste_code")}

diff --git a/web/core/components/account/password-strength-meter.tsx b/web/core/components/account/password-strength-meter.tsx index 358320eb518..1f0abae9c91 100644 --- a/web/core/components/account/password-strength-meter.tsx +++ b/web/core/components/account/password-strength-meter.tsx @@ -26,35 +26,35 @@ 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: t("please_enter_your_password"), + text: t("auth.common.password.errors.empty"), 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: t("password_length_should_me_more_than_8_characters"), + text: t("auth.common.password.errors.length"), 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: t("password_is_weak"), + text: t("auth.common.password.errors.strength.weak"), textColor: "text-red-500", }; } case E_PASSWORD_STRENGTH.STRENGTH_VALID: { return { bars: [`bg-green-500`, `bg-green-500`, `bg-green-500`], - text: t("password_is_strong"), + text: t("auth.common.password.errors.strength.strong"), textColor: "text-green-500", }; } default: { return { bars: [`bg-custom-text-100`, `bg-custom-text-100`, `bg-custom-text-100`], - text: t("please_enter_your_password"), + text: t("auth.common.password.errors.empty"), textColor: "text-custom-text-100", }; } diff --git a/web/core/components/analytics/custom-analytics/graph/custom-tooltip.tsx b/web/core/components/analytics/custom-analytics/graph/custom-tooltip.tsx index 10b63df3d34..853c541e717 100644 --- a/web/core/components/analytics/custom-analytics/graph/custom-tooltip.tsx +++ b/web/core/components/analytics/custom-analytics/graph/custom-tooltip.tsx @@ -43,7 +43,7 @@ export const CustomTooltip: React.FC = ({ datum, analytics, params }) => } else tooltipValue = datum.id; } else { if (ANALYTICS_DATE_KEYS.includes(params.x_axis)) tooltipValue = datum.indexValue; - else tooltipValue = datum.id === "count" ? "Issue count" : "Estimate"; + else tooltipValue = datum.id === "count" ? "Work item count" : "Estimate"; } return ( diff --git a/web/core/components/analytics/custom-analytics/graph/index.tsx b/web/core/components/analytics/custom-analytics/graph/index.tsx index 57531e373eb..87627bba373 100644 --- a/web/core/components/analytics/custom-analytics/graph/index.tsx +++ b/web/core/components/analytics/custom-analytics/graph/index.tsx @@ -32,7 +32,7 @@ export const AnalyticsGraph: React.FC = ({ analytics, barGraphData, param let data: number[] = []; if (params.segment) - // find the total no of issues in each segment + // find the total no of work items in each segment data = Object.keys(analytics.distribution).map((segment) => { let totalSegmentIssues = 0; diff --git a/web/core/components/analytics/custom-analytics/main-content.tsx b/web/core/components/analytics/custom-analytics/main-content.tsx index 8c1f8451f3e..b990c53bc76 100644 --- a/web/core/components/analytics/custom-analytics/main-content.tsx +++ b/web/core/components/analytics/custom-analytics/main-content.tsx @@ -46,7 +46,7 @@ export const CustomAnalyticsMainContent: React.FC = (props) => { ) : (
-

No matching issues found. Try changing the parameters.

+

No matching work items found. Try changing the parameters.

) diff --git a/web/core/components/analytics/custom-analytics/sidebar/projects-list.tsx b/web/core/components/analytics/custom-analytics/sidebar/projects-list.tsx index ff6a11ce0b6..c07867378ad 100644 --- a/web/core/components/analytics/custom-analytics/sidebar/projects-list.tsx +++ b/web/core/components/analytics/custom-analytics/sidebar/projects-list.tsx @@ -2,6 +2,7 @@ import { observer } from "mobx-react"; // icons import { Contrast, LayoutGrid, Users, Loader as Spinner } from "lucide-react"; // plane imports +import { useTranslation } from "@plane/i18n"; import { Loader } from "@plane/ui"; // components import { Logo } from "@/components/common"; @@ -20,11 +21,12 @@ export const CustomAnalyticsSidebarProjectsList: React.FC = observer((pro const { projectIds, isLoading, isUpdating } = props; // store hooks const { getProjectById, getProjectAnalyticsCountById } = useProject(); + const { t } = useTranslation(); return (
-

Selected Projects

+

{t("workspace_analytics.selected_projects")}

{isUpdating && }
@@ -57,21 +59,21 @@ export const CustomAnalyticsSidebarProjectsList: React.FC = observer((pro
-
Total members
+
{t("workspace_analytics.total_members")}
{projectAnalyticsCount?.total_members}
-
Total cycles
+
{t("workspace_analytics.total_cycles")}
{projectAnalyticsCount?.total_cycles}
-
Total modules
+
{t("workspace_analytics.total_modules")}
{projectAnalyticsCount?.total_modules}
diff --git a/web/core/components/analytics/custom-analytics/sidebar/sidebar-header.tsx b/web/core/components/analytics/custom-analytics/sidebar/sidebar-header.tsx index 9873d08a879..707a149221b 100644 --- a/web/core/components/analytics/custom-analytics/sidebar/sidebar-header.tsx +++ b/web/core/components/analytics/custom-analytics/sidebar/sidebar-header.tsx @@ -1,9 +1,10 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // components +import { NETWORK_CHOICES } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { Logo } from "@/components/common"; // constants -import { NETWORK_CHOICES } from "@/constants/project"; // helpers import { renderFormattedDate } from "@/helpers/date-time.helper"; // hooks @@ -16,6 +17,7 @@ export const CustomAnalyticsSidebarHeader = observer(() => { const { getCycleById } = useCycle(); const { getModuleById } = useModule(); const { getUserDetails } = useMember(); + const { t } = useTranslation(); const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined; const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined; @@ -91,7 +93,7 @@ export const CustomAnalyticsSidebarHeader = observer(() => {
Network
- {NETWORK_CHOICES.find((n) => n.key === projectDetails?.network)?.label ?? ""} + {t(NETWORK_CHOICES.find((n) => n.key === projectDetails?.network)?.i18n_label ?? "")}
diff --git a/web/core/components/analytics/custom-analytics/sidebar/sidebar.tsx b/web/core/components/analytics/custom-analytics/sidebar/sidebar.tsx index 170d3ed680f..5df139205a8 100644 --- a/web/core/components/analytics/custom-analytics/sidebar/sidebar.tsx +++ b/web/core/components/analytics/custom-analytics/sidebar/sidebar.tsx @@ -7,6 +7,7 @@ import useSWR, { mutate } from "swr"; // icons import { CalendarDays, Download, RefreshCw } from "lucide-react"; // types +import { useTranslation } from "@plane/i18n"; import { IAnalyticsParams, IAnalyticsResponse, IExportAnalyticsFormData, IWorkspace } from "@plane/types"; // ui import { Button, LayersIcon, TOAST_TYPE, setToast } from "@plane/ui"; @@ -42,6 +43,7 @@ export const CustomAnalyticsSidebar: React.FC = observer((props) => { const { data: currentUser } = useUser(); const { workspaceProjectIds, getProjectById, fetchProjectAnalyticsCount } = useProject(); const { getWorkspaceById } = useWorkspace(); + const { t } = useTranslation(); const { fetchCycleDetails, getCycleById } = useCycle(); const { fetchModuleDetails, getModuleById } = useModule(); // fetch project analytics count @@ -160,7 +162,7 @@ export const CustomAnalyticsSidebar: React.FC = observer((props) => {
{analytics ? analytics.total : "..."} -
Issues
+
{t("work_items")}
{isProjectLevel && (
@@ -199,10 +201,10 @@ export const CustomAnalyticsSidebar: React.FC = observer((props) => { mutate(ANALYTICS(workspaceSlug.toString(), params)); }} > -
Refresh
+
{t("refresh")}
diff --git a/web/core/components/analytics/project-modal/main-content.tsx b/web/core/components/analytics/project-modal/main-content.tsx index b572efdcb0c..cd3813f80cf 100644 --- a/web/core/components/analytics/project-modal/main-content.tsx +++ b/web/core/components/analytics/project-modal/main-content.tsx @@ -3,6 +3,7 @@ import { observer } from "mobx-react"; import { Tab } from "@headlessui/react"; // plane package imports import { ANALYTICS_TABS } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { ICycle, IModule, IProject } from "@plane/types"; // components import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics"; @@ -16,7 +17,7 @@ type Props = { export const ProjectAnalyticsModalMainContent: React.FC = observer((props) => { const { fullScreen, cycleDetails, moduleDetails } = props; - + const { t } = useTranslation(); return ( @@ -28,7 +29,7 @@ export const ProjectAnalyticsModalMainContent: React.FC = observer((props selected ? "text-custom-primary-100 " : "hover:text-custom-text-200" }`} > - {tab.title} + {t(tab.i18n_title)}
diff --git a/web/core/components/analytics/scope-and-demand/demand.tsx b/web/core/components/analytics/scope-and-demand/demand.tsx index 39a04cf9bde..fefdc8fc6ee 100644 --- a/web/core/components/analytics/scope-and-demand/demand.tsx +++ b/web/core/components/analytics/scope-and-demand/demand.tsx @@ -1,52 +1,58 @@ +// plane imports +import { STATE_GROUPS } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; // types import { IDefaultAnalyticsResponse, TStateGroups } from "@plane/types"; // constants import { Card } from "@plane/ui"; -import { STATE_GROUPS } from "@/constants/state"; type Props = { defaultAnalytics: IDefaultAnalyticsResponse; }; -export const AnalyticsDemand: React.FC = ({ defaultAnalytics }) => ( - -
-

Total open tasks

-

{defaultAnalytics.open_issues}

-
-
- {defaultAnalytics?.open_issues_classified.map((group) => { - const percentage = ((group.state_count / defaultAnalytics.total_issues) * 100).toFixed(0); +export const AnalyticsDemand: React.FC = ({ defaultAnalytics }) => { + const { t } = useTranslation(); - return ( -
-
-
- +
+

{t("workspace_analytics.open_tasks")}

+

{defaultAnalytics.open_issues}

+
+
+ {defaultAnalytics?.open_issues_classified.map((group) => { + const percentage = ((group.state_count / defaultAnalytics.total_issues) * 100).toFixed(0); + + return ( +
+
+
+ +
{group.state_group}
+ + {group.state_count} + +
+

{percentage}%

+
+
+
-
{group.state_group}
- - {group.state_count} -
-

{percentage}%

-
-
-
-
- ); - })} -
- -); + ); + })} +
+ + ); +}; diff --git a/web/core/components/analytics/scope-and-demand/leaderboard.tsx b/web/core/components/analytics/scope-and-demand/leaderboard.tsx index 69b1c5d67b8..76f96be4a27 100644 --- a/web/core/components/analytics/scope-and-demand/leaderboard.tsx +++ b/web/core/components/analytics/scope-and-demand/leaderboard.tsx @@ -1,4 +1,5 @@ // plane ui +import { useTranslation } from "@plane/i18n"; import { Card } from "@plane/ui"; // components import { ProfileEmptyState } from "@/components/ui"; @@ -21,45 +22,48 @@ type Props = { workspaceSlug: string; }; -export const AnalyticsLeaderBoard: React.FC = ({ users, title, emptyStateMessage, workspaceSlug }) => ( - -
{title}
- {users.length > 0 ? ( - - ) : ( -
- -
- )} -
-); +export const AnalyticsLeaderBoard: React.FC = ({ users, title, emptyStateMessage, workspaceSlug }) => { + const { t } = useTranslation(); + return ( + +
{title}
+ {users.length > 0 ? ( + + ) : ( +
+ +
+ )} +
+ ); +}; diff --git a/web/core/components/analytics/scope-and-demand/scope-and-demand.tsx b/web/core/components/analytics/scope-and-demand/scope-and-demand.tsx index 6aac07c6a29..ae51727aa01 100644 --- a/web/core/components/analytics/scope-and-demand/scope-and-demand.tsx +++ b/web/core/components/analytics/scope-and-demand/scope-and-demand.tsx @@ -2,6 +2,7 @@ import { useParams } from "next/navigation"; import useSWR from "swr"; // ui +import { useTranslation } from "@plane/i18n"; import { Button, ContentWrapper, Loader } from "@plane/ui"; // components import { AnalyticsDemand, AnalyticsLeaderBoard, AnalyticsScope, AnalyticsYearWiseIssues } from "@/components/analytics"; @@ -21,6 +22,7 @@ export const ScopeAndDemand: React.FC = (props) => { const { fullScreen = true } = props; const { workspaceSlug, projectId, cycleId, moduleId } = useParams(); + const { t } = useTranslation(); const isProjectLevel = projectId ? true : false; @@ -66,8 +68,8 @@ export const ScopeAndDemand: React.FC = (props) => { count: user?.count, id: user?.created_by__id, }))} - title="Most issues created" - emptyStateMessage="Co-workers and the number of issues created by them appears here." + title={t("workspace_analytics.most_work_items_created.title")} + emptyStateMessage={t("workspace_analytics.most_work_items_created.empty_state")} workspaceSlug={workspaceSlug?.toString() ?? ""} /> = (props) => { count: user?.count, id: user?.assignees__id, }))} - title="Most issues closed" - emptyStateMessage="Co-workers and the number of issues closed by them appears here." + title={t("workspace_analytics.most_work_items_closed.title")} + emptyStateMessage={t("workspace_analytics.most_work_items_closed.empty_state")} workspaceSlug={workspaceSlug?.toString() ?? ""} />
@@ -99,10 +101,10 @@ export const ScopeAndDemand: React.FC = (props) => { ) : (
-

There was some error in fetching the data.

+

{t("workspace_analytics.error")}

diff --git a/web/core/components/analytics/scope-and-demand/scope.tsx b/web/core/components/analytics/scope-and-demand/scope.tsx index 6711150975f..13cd60d5661 100644 --- a/web/core/components/analytics/scope-and-demand/scope.tsx +++ b/web/core/components/analytics/scope-and-demand/scope.tsx @@ -1,4 +1,5 @@ // plane types +import { useTranslation } from "@plane/i18n"; import { IDefaultAnalyticsUser } from "@plane/types"; // plane ui import { Card } from "@plane/ui"; @@ -14,82 +15,85 @@ type Props = { pendingAssignedIssues: IDefaultAnalyticsUser[]; }; -export const AnalyticsScope: React.FC = ({ pendingUnAssignedIssuesUser, pendingAssignedIssues }) => ( - -
-
-
-
Pending issues
- {pendingUnAssignedIssuesUser && ( -
- Unassigned: {pendingUnAssignedIssuesUser.count} -
- )} -
+export const AnalyticsScope: React.FC = ({ pendingUnAssignedIssuesUser, pendingAssignedIssues }) => { + const { t } = useTranslation(); + return ( + +
+
+
+
{t("workspace_analytics.pending_work_items.title")}
+ {pendingUnAssignedIssuesUser && ( +
+ {t("unassigned")}: {pendingUnAssignedIssuesUser.count} +
+ )} +
- {pendingAssignedIssues && pendingAssignedIssues.length > 0 ? ( - `#f97316`} - customYAxisTickValues={pendingAssignedIssues.map((d) => (d.count > 0 ? d.count : 50))} - tooltip={(datum) => { - const assignee = pendingAssignedIssues.find((a) => a.assignees__id === `${datum.indexValue}`); + {pendingAssignedIssues && pendingAssignedIssues.length > 0 ? ( + `#f97316`} + customYAxisTickValues={pendingAssignedIssues.map((d) => (d.count > 0 ? d.count : 50))} + tooltip={(datum) => { + const assignee = pendingAssignedIssues.find((a) => a.assignees__id === `${datum.indexValue}`); - return ( -
- - {assignee ? assignee.assignees__display_name : "No assignee"}:{" "} - - {datum.value} -
- ); - }} - axisBottom={{ - renderTick: (datum) => { - const assignee = pendingAssignedIssues[datum.tickIndex] ?? ""; + return ( +
+ + {assignee ? assignee.assignees__display_name : "No assignee"}:{" "} + + {datum.value} +
+ ); + }} + axisBottom={{ + renderTick: (datum) => { + const assignee = pendingAssignedIssues[datum.tickIndex] ?? ""; - if (assignee && assignee?.assignees__avatar_url && assignee?.assignees__avatar_url !== "") - return ( - - - - ); - else - return ( - - - - {datum.value ? `${assignee.assignees__display_name}`.toUpperCase()[0] : "?"} - - - ); - }, - }} - margin={{ top: 20 }} - theme={{ - axis: {}, - }} - /> - ) : ( -
- + + + ); + else + return ( + + + + {datum.value ? `${assignee.assignees__display_name}`.toUpperCase()[0] : "?"} + + + ); + }, + }} + margin={{ top: 20 }} + theme={{ + axis: {}, + }} /> -
- )} + ) : ( +
+ +
+ )} +
-
- -); + + ); +}; diff --git a/web/core/components/analytics/scope-and-demand/year-wise-issues.tsx b/web/core/components/analytics/scope-and-demand/year-wise-issues.tsx index b2c5805dd7a..0f469db7022 100644 --- a/web/core/components/analytics/scope-and-demand/year-wise-issues.tsx +++ b/web/core/components/analytics/scope-and-demand/year-wise-issues.tsx @@ -1,4 +1,5 @@ // ui +import { useTranslation } from "@plane/i18n"; import { IDefaultAnalyticsResponse } from "@plane/types"; import { Card } from "@plane/ui"; import { LineGraph, ProfileEmptyState } from "@/components/ui"; @@ -12,49 +13,52 @@ type Props = { defaultAnalytics: IDefaultAnalyticsResponse; }; -export const AnalyticsYearWiseIssues: React.FC = ({ defaultAnalytics }) => ( - -

Issues closed in a year

- {defaultAnalytics.issue_completed_month_wise.length > 0 ? ( - ({ - x: month.shortTitle, - y: - defaultAnalytics.issue_completed_month_wise.find((data) => data.month === parseInt(index, 10))?.count || - 0, - })), - }, - ]} - customYAxisTickValues={defaultAnalytics.issue_completed_month_wise.map((data) => data.count)} - height="300px" - colors={(datum) => datum.color} - curve="monotoneX" - margin={{ top: 20 }} - enableSlices="x" - sliceTooltip={(datum) => ( -
- {datum.slice.points[0].data.yFormatted} - issues closed in - {datum.slice.points[0].data.xFormatted} -
- )} - theme={{ - background: "rgb(var(--color-background-100))", - }} - enableArea - /> - ) : ( -
- = ({ defaultAnalytics }) => { + const { t } = useTranslation(); + return ( + +

{t("workspace_analytics.work_items_closed_in_a_year.title")}

+ {defaultAnalytics.issue_completed_month_wise.length > 0 ? ( + ({ + x: t(month.shortTitle), + y: + defaultAnalytics.issue_completed_month_wise.find((data) => data.month === parseInt(index, 10)) + ?.count || 0, + })), + }, + ]} + customYAxisTickValues={defaultAnalytics.issue_completed_month_wise.map((data) => data.count)} + height="300px" + colors={(datum) => datum.color} + curve="monotoneX" + margin={{ top: 20 }} + enableSlices="x" + sliceTooltip={(datum) => ( +
+ {datum.slice.points[0].data.yFormatted} + {t("workspace_analytics.work_items_closed_in")} + {datum.slice.points[0].data.xFormatted} +
+ )} + theme={{ + background: "rgb(var(--color-background-100))", + }} + enableArea /> -
- )} -
-); + ) : ( +
+ +
+ )} + + ); +}; diff --git a/web/core/components/api-token/delete-token-modal.tsx b/web/core/components/api-token/delete-token-modal.tsx index 4e7d15fdff1..ecc85a55832 100644 --- a/web/core/components/api-token/delete-token-modal.tsx +++ b/web/core/components/api-token/delete-token-modal.tsx @@ -4,6 +4,7 @@ import { useState, FC } from "react"; import { useParams } from "next/navigation"; import { mutate } from "swr"; // types +import { useTranslation } from "@plane/i18n"; import { IApiToken } from "@plane/types"; // ui import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui"; @@ -26,6 +27,7 @@ export const DeleteApiTokenModal: FC = (props) => { const [deleteLoading, setDeleteLoading] = useState(false); // router params const { workspaceSlug } = useParams(); + const { t } = useTranslation(); const handleClose = () => { onClose(); @@ -42,8 +44,8 @@ export const DeleteApiTokenModal: FC = (props) => { .then(() => { setToast({ type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Token deleted successfully.", + title: t("workspace_settings.settings.api_tokens.delete.success.title"), + message: t("workspace_settings.settings.api_tokens.delete.success.message"), }); mutate( @@ -57,8 +59,8 @@ export const DeleteApiTokenModal: FC = (props) => { .catch((err) => setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: err?.message ?? "Something went wrong. Please try again.", + title: t("workspace_settings.settings.api_tokens.delete.error.title"), + message: err?.message ?? t("workspace_settings.settings.api_tokens.delete.error.message"), }) ) .finally(() => setDeleteLoading(false)); @@ -70,12 +72,8 @@ export const DeleteApiTokenModal: FC = (props) => { handleSubmit={handleDeletion} isSubmitting={deleteLoading} isOpen={isOpen} - title="Delete API token" - content={ - <> - Any application using this token will no longer have the access to Plane data. This action cannot be undone. - - } + title={t("workspace_settings.settings.api_tokens.delete.title")} + content={<>{t("workspace_settings.settings.api_tokens.delete.description")} } /> ); }; diff --git a/web/core/components/api-token/modal/form.tsx b/web/core/components/api-token/modal/form.tsx index 18ade7f2af7..17e14ae563c 100644 --- a/web/core/components/api-token/modal/form.tsx +++ b/web/core/components/api-token/modal/form.tsx @@ -5,6 +5,7 @@ import { add } from "date-fns"; import { Controller, useForm } from "react-hook-form"; import { Calendar } from "lucide-react"; // types +import { useTranslation } from "@plane/i18n"; import { IApiToken } from "@plane/types"; // ui import { Button, CustomSelect, Input, TextArea, ToggleSwitch, TOAST_TYPE, setToast } from "@plane/ui"; @@ -76,6 +77,8 @@ export const CreateApiTokenForm: React.FC = (props) => { reset, watch, } = useForm({ defaultValues }); + // hooks + const { t } = useTranslation(); const handleFormSubmit = async (data: IApiToken) => { // if never expires is toggled off, and the user has not selected a custom date or a predefined date, show an error @@ -115,19 +118,21 @@ export const CreateApiTokenForm: React.FC = (props) => { return (
-

Create token

+

+ {t("workspace_settings.settings.api_tokens.create_token")} +

val.trim() !== "" || "Title is required", + validate: (val) => val.trim() !== "" || t("title_is_required"), }} render={({ field: { value, onChange } }) => ( = (props) => { value={value} onChange={onChange} hasError={Boolean(errors.label)} - placeholder="Title" + placeholder={t("title")} className="w-full text-base" /> )} @@ -150,7 +155,7 @@ export const CreateApiTokenForm: React.FC = (props) => { value={value} onChange={onChange} hasError={Boolean(errors.description)} - placeholder="Description" + placeholder={t("description")} className="w-full text-base resize-none min-h-24" /> )} @@ -229,14 +234,16 @@ export const CreateApiTokenForm: React.FC = (props) => {
{}} size="sm" />
- Never expires + {t("workspace_settings.settings.api_tokens.never_expires")}
diff --git a/web/core/components/api-token/modal/generated-token-details.tsx b/web/core/components/api-token/modal/generated-token-details.tsx index 8f0a62c3ac8..da833cefb3e 100644 --- a/web/core/components/api-token/modal/generated-token-details.tsx +++ b/web/core/components/api-token/modal/generated-token-details.tsx @@ -1,6 +1,7 @@ "use client"; import { Copy } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; import { IApiToken } from "@plane/types"; // ui import { Button, Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; @@ -19,12 +20,13 @@ type Props = { export const GeneratedTokenDetails: React.FC = (props) => { const { handleClose, tokenDetails } = props; const { isMobile } = usePlatformOS(); + const { t } = useTranslation(); const copyApiToken = (token: string) => { copyTextToClipboard(token).then(() => setToast({ type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Token copied to clipboard.", + title: `${t("success")}!`, + message: t("workspace_setting.token_copied"), }) ); }; @@ -32,11 +34,8 @@ export const GeneratedTokenDetails: React.FC = (props) => { return (
-

Key created

-

- Copy and save this secret key in Plane Pages. You can{"'"}t see this key after you hit Close. A CSV file - containing the key has been downloaded. -

+

{t("workspace_settings.key_created")}

+

{t("workspace_settings.copy_key")}

diff --git a/web/core/components/archives/archive-tabs-list.tsx b/web/core/components/archives/archive-tabs-list.tsx index 2e750219f2f..5bba6cc204c 100644 --- a/web/core/components/archives/archive-tabs-list.tsx +++ b/web/core/components/archives/archive-tabs-list.tsx @@ -14,7 +14,7 @@ const ARCHIVES_TAB_LIST: { }[] = [ { key: "issues", - label: "Issues", + label: "Work items", shouldRender: () => true, }, { diff --git a/web/core/components/automation/auto-archive-automation.tsx b/web/core/components/automation/auto-archive-automation.tsx index 84544a1f861..018f69fd46f 100644 --- a/web/core/components/automation/auto-archive-automation.tsx +++ b/web/core/components/automation/auto-archive-automation.tsx @@ -5,16 +5,16 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { ArchiveRestore } from "lucide-react"; // types +import { PROJECT_AUTOMATION_MONTHS,EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IProject } from "@plane/types"; // ui import { CustomSelect, Loader, ToggleSwitch } from "@plane/ui"; // component import { SelectMonthModal } from "@/components/automation"; // constants -import { PROJECT_AUTOMATION_MONTHS } from "@/constants/project"; // hooks import { useProject, useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; type Props = { handleChange: (formData: Partial) => Promise; @@ -30,6 +30,7 @@ export const AutoArchiveAutomation: React.FC = observer((props) => { const [monthModal, setmonthModal] = useState(false); // store hooks const { allowPermissions } = useUserPermissions(); + const { t } = useTranslation(); const { currentProjectDetails } = useProject(); @@ -56,9 +57,9 @@ export const AutoArchiveAutomation: React.FC = observer((props) => {
-

Auto-archive closed issues

+

{t("project_settings.automations.auto-archive.title")}

- Plane will auto archive issues that have been completed or canceled. + {t("project_settings.automations.auto-archive.description")}

@@ -78,7 +79,9 @@ export const AutoArchiveAutomation: React.FC = observer((props) => { currentProjectDetails.archive_in !== 0 && (
-
Auto-archive issues that are closed for
+
+ {t("project_settings.automations.auto-archive.duration")} +
= observer((props) => { > <> {PROJECT_AUTOMATION_MONTHS.map((month) => ( - - {month.label} + + {t(month.i18n_label, { month: month.value })} ))} @@ -103,7 +106,7 @@ export const AutoArchiveAutomation: React.FC = observer((props) => { className="flex w-full select-none items-center rounded px-1 py-1.5 text-sm text-custom-text-200 hover:bg-custom-background-80" onClick={() => setmonthModal(true)} > - Customize time range + {t("customize_time_range")} diff --git a/web/core/components/automation/auto-close-automation.tsx b/web/core/components/automation/auto-close-automation.tsx index 4a5899bde6e..da674644d00 100644 --- a/web/core/components/automation/auto-close-automation.tsx +++ b/web/core/components/automation/auto-close-automation.tsx @@ -6,16 +6,16 @@ import { useParams } from "next/navigation"; // icons import { ArchiveX } from "lucide-react"; // types +import { PROJECT_AUTOMATION_MONTHS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IProject } from "@plane/types"; // ui import { CustomSelect, CustomSearchSelect, ToggleSwitch, StateGroupIcon, DoubleCircleIcon, Loader } from "@plane/ui"; // component import { SelectMonthModal } from "@/components/automation"; // constants -import { PROJECT_AUTOMATION_MONTHS } from "@/constants/project"; // hooks import { useProject, useProjectState, useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; type Props = { handleChange: (formData: Partial) => Promise; @@ -31,6 +31,7 @@ export const AutoCloseAutomation: React.FC = observer((props) => { const { currentProjectDetails } = useProject(); const { projectStates } = useProjectState(); const { allowPermissions } = useUserPermissions(); + const { t } = useTranslation(); // const stateGroups = projectStateStore.groupedProjectStates ?? undefined; @@ -82,9 +83,9 @@ export const AutoCloseAutomation: React.FC = observer((props) => {
-

Auto-close issues

+

{t("project_settings.automations.auto-close.title")}

- Plane will automatically close issues that haven{"'"}t been completed or canceled. + {t("project_settings.automations.auto-close.description")}

@@ -105,7 +106,9 @@ export const AutoCloseAutomation: React.FC = observer((props) => {
-
Auto-close issues that are inactive for
+
+ {t("project_settings.automations.auto-close.duration")} +
= observer((props) => { > <> {PROJECT_AUTOMATION_MONTHS.map((month) => ( - - {month.label} + + {t(month.i18n_label, { month: month.value })} ))} @@ -137,7 +140,9 @@ export const AutoCloseAutomation: React.FC = observer((props) => {
-
Auto-close status
+
+ {t("project_settings.automations.auto-close.auto_close_status")} +
= observer((props) => { )} {selectedOption?.name ? selectedOption.name - : (currentDefaultState?.name ?? State)} + : (currentDefaultState?.name ?? {t("state")})}
} onChange={(val: string) => { diff --git a/web/core/components/command-palette/actions/issue-actions/actions-list.tsx b/web/core/components/command-palette/actions/issue-actions/actions-list.tsx index c15a935b5e7..b2fce17f2c0 100644 --- a/web/core/components/command-palette/actions/issue-actions/actions-list.tsx +++ b/web/core/components/command-palette/actions/issue-actions/actions-list.tsx @@ -79,7 +79,7 @@ export const CommandPaletteIssueActions: React.FC = observer((props) => { }; return ( - + { setPlaceholder("Change state..."); @@ -143,7 +143,7 @@ export const CommandPaletteIssueActions: React.FC = observer((props) => {
- Delete issue + Delete work item
= observer((props) => { >
- Copy issue URL + Copy work item URL
diff --git a/web/core/components/command-palette/actions/issue-actions/change-priority.tsx b/web/core/components/command-palette/actions/issue-actions/change-priority.tsx index 2f004e8eae0..d5fd09a55a5 100644 --- a/web/core/components/command-palette/actions/issue-actions/change-priority.tsx +++ b/web/core/components/command-palette/actions/issue-actions/change-priority.tsx @@ -5,12 +5,11 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { Check } from "lucide-react"; // plane constants -import { EIssuesStoreType } from "@plane/constants"; +import { EIssuesStoreType, ISSUE_PRIORITIES } from "@plane/constants"; // plane types import { TIssue, TIssuePriorities } from "@plane/types"; // mobx store import { PriorityIcon } from "@plane/ui"; -import { ISSUE_PRIORITIES } from "@/constants/issue"; import { useIssues } from "@/hooks/store"; // ui // types diff --git a/web/core/components/command-palette/actions/theme-actions.tsx b/web/core/components/command-palette/actions/theme-actions.tsx index 2ae81d3f316..9df95c00a96 100644 --- a/web/core/components/command-palette/actions/theme-actions.tsx +++ b/web/core/components/command-palette/actions/theme-actions.tsx @@ -5,9 +5,10 @@ import { Command } from "cmdk"; import { observer } from "mobx-react"; import { useTheme } from "next-themes"; import { Settings } from "lucide-react"; +// plane imports +import { THEME_OPTIONS } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TOAST_TYPE, setToast } from "@plane/ui"; -// constants -import { THEME_OPTIONS } from "@/constants/themes"; // hooks import { useUserProfile } from "@/hooks/store"; @@ -20,6 +21,7 @@ export const CommandPaletteThemeActions: FC = observer((props) => { const { setTheme } = useTheme(); // hooks const { updateUserTheme } = useUserProfile(); + const { t } = useTranslation(); // states const [mounted, setMounted] = useState(false); @@ -53,7 +55,7 @@ export const CommandPaletteThemeActions: FC = observer((props) => { >
- {theme.label} + {t(theme.i18n_label)}
))} diff --git a/web/core/components/command-palette/actions/workspace-settings-actions.tsx b/web/core/components/command-palette/actions/workspace-settings-actions.tsx index dee51c1b62a..670788a330d 100644 --- a/web/core/components/command-palette/actions/workspace-settings-actions.tsx +++ b/web/core/components/command-palette/actions/workspace-settings-actions.tsx @@ -4,13 +4,14 @@ import { Command } from "cmdk"; // hooks import Link from "next/link"; import { useParams } from "next/navigation"; - +import { WORKSPACE_SETTINGS_LINKS, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; +// components +import { SettingIcon } from "@/components/icons"; // hooks import { useUserPermissions } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; // plane wev constants -import { EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; -import { WORKSPACE_SETTINGS_LINKS } from "@/plane-web/constants/workspace"; // plane web helpers import { shouldRenderSettingLink } from "@/plane-web/helpers/workspace.helper"; @@ -25,6 +26,7 @@ export const CommandPaletteWorkspaceSettingsActions: React.FC = (props) = // router params const { workspaceSlug } = useParams(); // mobx store + const { t } = useTranslation(); const { allowPermissions } = useUserPermissions(); // derived values @@ -46,8 +48,8 @@ export const CommandPaletteWorkspaceSettingsActions: React.FC = (props) = >
- - {setting.label} + + {t(setting.i18n_label)}
diff --git a/web/core/components/command-palette/command-modal.tsx b/web/core/components/command-palette/command-modal.tsx index b80b0dfe145..db7202319ca 100644 --- a/web/core/components/command-palette/command-modal.tsx +++ b/web/core/components/command-palette/command-modal.tsx @@ -7,9 +7,10 @@ import { useParams } from "next/navigation"; import useSWR from "swr"; import { FolderPlus, Search, Settings } from "lucide-react"; import { Dialog, Transition } from "@headlessui/react"; -// types +// plane imports +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { IWorkspaceSearchResults } from "@plane/types"; -// ui import { LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui"; // components import { @@ -23,9 +24,7 @@ import { CommandPaletteThemeActions, CommandPaletteWorkspaceSettingsActions, } from "@/components/command-palette"; -import { EmptyState } from "@/components/empty-state"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; +import { SimpleEmptyState } from "@/components/empty-state"; // fetch-keys import { ISSUE_DETAILS } from "@/constants/fetch-keys"; // helpers @@ -36,21 +35,20 @@ import { useAppRouter } from "@/hooks/use-app-router"; import useDebounce from "@/hooks/use-debounce"; import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web components +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { IssueIdentifier } from "@/plane-web/components/issues"; // plane web services import { WorkspaceService } from "@/plane-web/services"; // services import { IssueService } from "@/services/issue"; -import { EUserPermissions, EUserPermissionsLevel } from "ee/constants/user-permissions"; const workspaceService = new WorkspaceService(); const issueService = new IssueService(); export const CommandModal: React.FC = observer(() => { - // hooks - const { workspaceProjectIds } = useProject(); - const { isMobile } = usePlatformOS(); - const { canPerformAnyCreateAction } = useUser(); + // router + const router = useAppRouter(); + const { workspaceSlug, projectId, issueId } = useParams(); // states const [placeholder, setPlaceholder] = useState("Type a command or search..."); const [resultsCount, setResultsCount] = useState(0); @@ -70,26 +68,25 @@ export const CommandModal: React.FC = observer(() => { }); const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false); const [pages, setPages] = useState([]); + // plane hooks + const { t } = useTranslation(); + // hooks + const { workspaceProjectIds } = useProject(); + const { isMobile } = usePlatformOS(); + const { canPerformAnyCreateAction } = useUser(); const { isCommandPaletteOpen, toggleCommandPaletteModal, toggleCreateIssueModal, toggleCreateProjectModal } = useCommandPalette(); const { allowPermissions } = useUserPermissions(); const { setTrackElement } = useEventTracker(); - - // router - const router = useAppRouter(); - // router params - const { workspaceSlug, projectId, issueId } = useParams(); - + // derived values const page = pages[pages.length - 1]; - const debouncedSearchTerm = useDebounce(searchTerm, 500); - const { baseTabIndex } = getTabIndex(undefined, isMobile); - const canPerformWorkspaceActions = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.WORKSPACE ); + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/search" }); // TODO: update this to mobx store const { data: issueDetails } = useSWR( @@ -268,7 +265,7 @@ export const CommandModal: React.FC = observer(() => { {!isLoading && resultsCount === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && (
- +
)} @@ -304,7 +301,7 @@ export const CommandModal: React.FC = observer(() => { workspaceProjectIds && workspaceProjectIds.length > 0 && canPerformAnyCreateAction && ( - + { closePalette(); @@ -315,7 +312,7 @@ export const CommandModal: React.FC = observer(() => { >
- Create new issue + Create new work item
C
diff --git a/web/core/components/command-palette/command-palette.tsx b/web/core/components/command-palette/command-palette.tsx index 4e59234cde9..fb2b51821cc 100644 --- a/web/core/components/command-palette/command-palette.tsx +++ b/web/core/components/command-palette/command-palette.tsx @@ -4,6 +4,7 @@ import React, { useCallback, useEffect, FC, useMemo } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // ui +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { TOAST_TYPE, setToast } from "@plane/ui"; // components import { CommandModal, ShortcutsModal } from "@/components/command-palette"; @@ -19,7 +20,6 @@ import { WorkspaceLevelModals, } from "@/plane-web/components/command-palette/modals"; // plane web constants -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; // plane web helpers import { getGlobalShortcutsList, diff --git a/web/core/components/command-palette/helpers.tsx b/web/core/components/command-palette/helpers.tsx index 99c8c310e43..b6310840bee 100644 --- a/web/core/components/command-palette/helpers.tsx +++ b/web/core/components/command-palette/helpers.tsx @@ -49,7 +49,7 @@ export const commandGroups: { ), path: (issue: IWorkspaceIssueSearchResult) => `/${issue?.workspace__slug}/projects/${issue?.project_id}/issues/${issue?.id}`, - title: "Issues", + title: "Work items", }, issue_view: { icon: , diff --git a/web/core/components/common/access-field.tsx b/web/core/components/common/access-field.tsx index b619dde44fb..f2c50934d21 100644 --- a/web/core/components/common/access-field.tsx +++ b/web/core/components/common/access-field.tsx @@ -1,5 +1,6 @@ import { LucideIcon } from "lucide-react"; // plane ui +import { useTranslation } from "@plane/i18n"; import { Tooltip } from "@plane/ui"; // plane utils import { cn } from "@plane/utils"; @@ -9,38 +10,44 @@ type Props = { value: number; accessSpecifiers: { key: number; - label: string; + i18n_label?: string; + label?: string; icon: LucideIcon; }[]; isMobile?: boolean; }; +// TODO: Remove label once i18n is done export const AccessField = (props: Props) => { const { onChange, value, accessSpecifiers, isMobile = false } = props; + const { t } = useTranslation(); return (
- {accessSpecifiers.map((access, index) => ( - - - - ))} + tabIndex={2 + index} + > + + + + ); + })}
); }; diff --git a/web/core/components/common/activity/helper.tsx b/web/core/components/common/activity/helper.tsx index 54ccd56e2ba..b3d16e7d047 100644 --- a/web/core/components/common/activity/helper.tsx +++ b/web/core/components/common/activity/helper.tsx @@ -271,7 +271,7 @@ export const messages = (activity: TProjectActivity): { message: string | ReactN }; case "is_issue_type_enabled": return { - message: <>{getBooleanActionText(newValue)} issue types, + message: <>{getBooleanActionText(newValue)} work item types, }; default: return { diff --git a/web/core/components/common/applied-filters/date.tsx b/web/core/components/common/applied-filters/date.tsx index 0f5680c48d4..9dcde53a439 100644 --- a/web/core/components/common/applied-filters/date.tsx +++ b/web/core/components/common/applied-filters/date.tsx @@ -1,8 +1,8 @@ import { observer } from "mobx-react"; // icons import { X } from "lucide-react"; -// constants -import { DATE_BEFORE_FILTER_OPTIONS } from "@/constants/filters"; +// plane constants +import { DATE_BEFORE_FILTER_OPTIONS } from "@plane/constants"; // helpers import { renderFormattedDate } from "@/helpers/date-time.helper"; import { capitalizeFirstLetter } from "@/helpers/string.helper"; diff --git a/web/core/components/common/filters/created-at.tsx b/web/core/components/common/filters/created-at.tsx index 9baf391bec5..b1c23f70a8b 100644 --- a/web/core/components/common/filters/created-at.tsx +++ b/web/core/components/common/filters/created-at.tsx @@ -1,10 +1,9 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; -// components +// plane constants +import { DATE_BEFORE_FILTER_OPTIONS } from "@plane/constants"; import { DateFilterModal } from "@/components/core"; import { FilterHeader, FilterOption } from "@/components/issues"; -// constants -import { DATE_BEFORE_FILTER_OPTIONS } from "@/constants/filters"; // helpers import { isInDateFormat } from "@/helpers/date-time.helper"; diff --git a/web/core/components/common/latest-feature-block.tsx b/web/core/components/common/latest-feature-block.tsx index a108e05bf27..35aacddcfea 100644 --- a/web/core/components/common/latest-feature-block.tsx +++ b/web/core/components/common/latest-feature-block.tsx @@ -28,7 +28,7 @@ export const LatestFeatureBlock = () => {
Plane Issues { return ( {activity?.issue_detail ? ( @@ -54,7 +54,7 @@ export const IssueLink = ({ activity }: { activity: IIssueActivity }) => { ) : ( - {" an Issue"}{" "} + {" a work item"}{" "} )} @@ -100,20 +100,20 @@ const LabelPill = observer(({ labelId, workspaceSlug }: { labelId: string; works const inboxActivityMessage = { declined: { - showIssue: "declined issue", - noIssue: "declined this issue from intake.", + showIssue: "declined work item", + noIssue: "declined this work item from intake.", }, snoozed: { - showIssue: "snoozed issue", - noIssue: "snoozed this issue.", + showIssue: "snoozed work item", + noIssue: "snoozed this work item.", }, accepted: { - showIssue: "accepted issue", - noIssue: "accepted this issue from intake.", + showIssue: "accepted work item", + noIssue: "accepted this work item from intake.", }, markedDuplicate: { - showIssue: "declined issue", - noIssue: "declined this issue from intake by marking a duplicate issue.", + showIssue: "declined work item", + noIssue: "declined this work item from intake by marking a duplicate work item.", }, }; @@ -128,7 +128,7 @@ const getInboxUserActivityMessage = (activity: IIssueActivity, showIssue: boolea case "2": return showIssue ? inboxActivityMessage.markedDuplicate.showIssue : inboxActivityMessage.markedDuplicate.noIssue; default: - return "updated intake issue status."; + return "updated intake work item status."; } }; @@ -393,7 +393,7 @@ const activityDetails: { return ( <> - added {showIssue ? : "this issue"}{" "} + added {showIssue ? : "this work item"}{" "} to the cycle{" "} - added {showIssue ? : "this issue"} to the module{" "} + added {showIssue ? : "this work item"} to the module{" "} - marked that {showIssue ? : "this issue"} relates to{" "} + marked that {showIssue ? : "this work item"} relates to{" "} {activity.new_value}. ); @@ -570,14 +570,14 @@ const activityDetails: { if (activity.old_value === "") return ( <> - marked {showIssue ? : "this issue"} is blocking issue{" "} + marked {showIssue ? : "this work item"} is blocking work item{" "} {activity.new_value}. ); else return ( <> - removed the blocking issue{" "} + removed the blocking work item{" "} {activity.old_value}. ); @@ -589,14 +589,14 @@ const activityDetails: { if (activity.old_value === "") return ( <> - marked {showIssue ? : "this issue"} is being blocked by{" "} + marked {showIssue ? : "this work item"} is being blocked by{" "} {activity.new_value}. ); else return ( <> - removed {showIssue ? : "this issue"} being blocked by issue{" "} + removed {showIssue ? : "this work item"} being blocked by work item{" "} {activity.old_value}. ); @@ -608,14 +608,14 @@ const activityDetails: { if (activity.old_value === "") return ( <> - marked {showIssue ? : "this issue"} as duplicate of{" "} + marked {showIssue ? : "this work item"} as duplicate of{" "} {activity.new_value}. ); else return ( <> - removed {showIssue ? : "this issue"} as a duplicate of{" "} + removed {showIssue ? : "this work item"} as a duplicate of{" "} {activity.old_value}. ); @@ -709,7 +709,7 @@ const activityDetails: { )} - {activity.verb === "2" && ` from intake by marking a duplicate issue.`} + {activity.verb === "2" && ` from intake by marking a duplicate work item.`} ), icon:
diff --git a/web/core/components/core/image-picker-popover.tsx b/web/core/components/core/image-picker-popover.tsx index bef7b4df0da..97f416027bc 100644 --- a/web/core/components/core/image-picker-popover.tsx +++ b/web/core/components/core/image-picker-popover.tsx @@ -8,14 +8,13 @@ import { useDropzone } from "react-dropzone"; import { Control, Controller } from "react-hook-form"; import useSWR from "swr"; import { Tab, Popover } from "@headlessui/react"; -// plane helpers +// plane imports +import { MAX_FILE_SIZE } from "@plane/constants"; import { useOutsideClickDetector } from "@plane/hooks"; // plane types import { EFileAssetType } from "@plane/types/src/enums"; // ui import { Button, Input, Loader } from "@plane/ui"; -// constants -import { MAX_STATIC_FILE_SIZE } from "@/constants/common"; // helpers import { getFileURL } from "@/helpers/file.helper"; // hooks @@ -92,7 +91,7 @@ export const ImagePickerPopover: React.FC = observer((props) => { accept: { "image/*": [".png", ".jpg", ".jpeg", ".webp"], }, - maxSize: MAX_STATIC_FILE_SIZE, + maxSize: MAX_FILE_SIZE, }); const handleSubmit = async () => { diff --git a/web/core/components/core/modals/bulk-delete-issues-modal.tsx b/web/core/components/core/modals/bulk-delete-issues-modal.tsx index aec70262ab1..642a59bbc72 100644 --- a/web/core/components/core/modals/bulk-delete-issues-modal.tsx +++ b/web/core/components/core/modals/bulk-delete-issues-modal.tsx @@ -6,19 +6,18 @@ import { useParams } from "next/navigation"; import { SubmitHandler, useForm } from "react-hook-form"; import { Search } from "lucide-react"; import { Combobox, Dialog, Transition } from "@headlessui/react"; +// plane imports import { EIssuesStoreType } from "@plane/constants"; -// types +import { useTranslation } from "@plane/i18n"; import { ISearchIssueResponse, IUser } from "@plane/types"; -// ui import { Button, Loader, TOAST_TYPE, setToast } from "@plane/ui"; // components -import { EmptyState } from "@/components/empty-state"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; +import { SimpleEmptyState } from "@/components/empty-state"; // hooks import { useIssues } from "@/hooks/store"; import useDebounce from "@/hooks/use-debounce"; // services +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { ProjectService } from "@/services/project"; // local components import { BulkDeleteIssuesModalItem } from "./bulk-delete-issues-modal-item"; @@ -39,16 +38,19 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { const { isOpen, onClose } = props; // router params const { workspaceSlug, projectId } = useParams(); - // hooks - const { - issues: { removeBulkIssues }, - } = useIssues(EIssuesStoreType.PROJECT); // states const [query, setQuery] = useState(""); const [issues, setIssues] = useState([]); const [isSearching, setIsSearching] = useState(false); - + // hooks + const { + issues: { removeBulkIssues }, + } = useIssues(EIssuesStoreType.PROJECT); + const { t } = useTranslation(); + // derived values const debouncedSearchTerm: string = useDebounce(query, 500); + const searchResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/search" }); + const issuesResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/issues" }); useEffect(() => { if (!isOpen || !workspaceSlug || !projectId) return; @@ -88,7 +90,7 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { setToast({ type: TOAST_TYPE.ERROR, title: "Error!", - message: "Please select at least one issue.", + message: "Please select at least one work item.", }); return; } @@ -100,7 +102,7 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { setToast({ type: TOAST_TYPE.SUCCESS, title: "Success!", - message: "Issues deleted successfully!", + message: "Work items deleted successfully!", }); handleClose(); }) @@ -117,7 +119,7 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { issues.length > 0 ? (
  • {query === "" && ( -

    Select issues to delete

    +

    Select work items to delete

    )}
      {issues.map((issue) => ( @@ -131,12 +133,11 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { ) : (
      - + {query === "" ? ( + + ) : ( + + )}
      ); @@ -203,7 +204,7 @@ export const BulkDeleteIssuesModal: React.FC = observer((props) => { Cancel
  • )} diff --git a/web/core/components/core/modals/existing-issues-list-modal.tsx b/web/core/components/core/modals/existing-issues-list-modal.tsx index 659dd7bfb93..63ce2b470c7 100644 --- a/web/core/components/core/modals/existing-issues-list-modal.tsx +++ b/web/core/components/core/modals/existing-issues-list-modal.tsx @@ -3,6 +3,8 @@ import React, { useEffect, useState } from "react"; import { Rocket, Search, X } from "lucide-react"; import { Combobox, Dialog, Transition } from "@headlessui/react"; +// i18n +import { useTranslation } from "@plane/i18n"; // types import { ISearchIssueResponse, TProjectIssuesSearchParams } from "@plane/types"; // ui @@ -33,6 +35,8 @@ type Props = { const projectService = new ProjectService(); export const ExistingIssuesListModal: React.FC = (props) => { + const { t } = useTranslation(); + const { workspaceSlug, projectId, @@ -66,8 +70,8 @@ export const ExistingIssuesListModal: React.FC = (props) => { if (selectedIssues.length === 0) { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Please select at least one issue.", + title: t("toast.error"), + message: t("issue.select.error"), }); return; @@ -140,7 +144,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { /> setSearchTerm(e.target.value)} tabIndex={baseTabIndex} @@ -174,7 +178,7 @@ export const ExistingIssuesListModal: React.FC = (props) => {
    ) : (
    - No issues selected + {t("issue.select.empty")}
    )} {workspaceLevelToggle && ( @@ -193,7 +197,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { onClick={() => setIsWorkspaceLevel((prevData) => !prevData)} className="flex-shrink-0" > - Workspace Level + {t("common.workspace_level")}
    @@ -204,6 +208,7 @@ export const ExistingIssuesListModal: React.FC = (props) => { static className="vertical-scrollbar scrollbar-md max-h-80 scroll-py-2 overflow-y-auto" > + {/* TODO: Translate here */} {searchTerm !== "" && (
    Search results for{" "} @@ -288,11 +293,11 @@ export const ExistingIssuesListModal: React.FC = (props) => {
    {selectedIssues.length > 0 && ( )}
    diff --git a/web/core/components/core/modals/issue-search-modal-empty-state.tsx b/web/core/components/core/modals/issue-search-modal-empty-state.tsx index 578d39a60fc..20646efb182 100644 --- a/web/core/components/core/modals/issue-search-modal-empty-state.tsx +++ b/web/core/components/core/modals/issue-search-modal-empty-state.tsx @@ -1,10 +1,10 @@ import React from "react"; -// components +// plane imports +import { useTranslation } from "@plane/i18n"; import { ISearchIssueResponse } from "@plane/types"; -import { EmptyState } from "@/components/empty-state"; -// types -import { EmptyStateType } from "@/constants/empty-state"; -// constants +// components +import { SimpleEmptyState } from "@/components/empty-state"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; interface EmptyStateProps { issues: ISearchIssueResponse[]; @@ -19,18 +19,28 @@ export const IssueSearchModalEmptyState: React.FC = ({ debouncedSearchTerm, isSearching, }) => { - const renderEmptyState = (type: EmptyStateType) => ( -
    - -
    - ); + // plane hooks + const { t } = useTranslation(); + // derived values + const searchResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/search" }); + const issuesResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/search/issues" }); - const emptyState = - issues.length === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && !isSearching - ? renderEmptyState(EmptyStateType.ISSUE_RELATION_SEARCH_EMPTY_STATE) - : issues.length === 0 - ? renderEmptyState(EmptyStateType.ISSUE_RELATION_EMPTY_STATE) - : null; + const EmptyStateContainer = ({ children }: { children: React.ReactNode }) => ( +
    {children}
    + ); - return emptyState; + if (issues.length === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && !isSearching) { + return ( + + + + ); + } else if (issues.length === 0) { + return ( + + + + ); + } + return null; }; diff --git a/web/core/components/core/modals/user-image-upload-modal.tsx b/web/core/components/core/modals/user-image-upload-modal.tsx index ad7a4daacf6..286f3641510 100644 --- a/web/core/components/core/modals/user-image-upload-modal.tsx +++ b/web/core/components/core/modals/user-image-upload-modal.tsx @@ -5,12 +5,10 @@ import { observer } from "mobx-react"; import { useDropzone } from "react-dropzone"; import { UserCircle2 } from "lucide-react"; import { Transition, Dialog } from "@headlessui/react"; -// plane types +// plane imports +import { MAX_FILE_SIZE } from "@plane/constants"; import { EFileAssetType } from "@plane/types/src/enums"; -// hooks import { Button, TOAST_TYPE, setToast } from "@plane/ui"; -// constants -import { MAX_STATIC_FILE_SIZE } from "@/constants/common"; // helpers import { getAssetIdFromUrl, getFileURL } from "@/helpers/file.helper"; import { checkURLValidity } from "@/helpers/string.helper"; @@ -40,7 +38,7 @@ export const UserImageUploadModal: React.FC = observer((props) => { accept: { "image/*": [".png", ".jpg", ".jpeg", ".webp"], }, - maxSize: MAX_STATIC_FILE_SIZE, + maxSize: MAX_FILE_SIZE, multiple: false, }); diff --git a/web/core/components/core/modals/workspace-image-upload-modal.tsx b/web/core/components/core/modals/workspace-image-upload-modal.tsx index cc7248a626d..df0248c843f 100644 --- a/web/core/components/core/modals/workspace-image-upload-modal.tsx +++ b/web/core/components/core/modals/workspace-image-upload-modal.tsx @@ -5,12 +5,10 @@ import { useParams } from "next/navigation"; import { useDropzone } from "react-dropzone"; import { UserCircle2 } from "lucide-react"; import { Transition, Dialog } from "@headlessui/react"; -// plane types +// plane imports +import { MAX_FILE_SIZE } from "@plane/constants"; import { EFileAssetType } from "@plane/types/src/enums"; -// hooks import { Button } from "@plane/ui"; -// constants -import { MAX_STATIC_FILE_SIZE } from "@/constants/common"; // helpers import { getAssetIdFromUrl, getFileURL } from "@/helpers/file.helper"; import { checkURLValidity } from "@/helpers/string.helper"; @@ -48,7 +46,7 @@ export const WorkspaceImageUploadModal: React.FC = observer((props) => { accept: { "image/*": [".png", ".jpg", ".jpeg", ".webp"], }, - maxSize: MAX_STATIC_FILE_SIZE, + maxSize: MAX_FILE_SIZE, multiple: false, }); diff --git a/web/core/components/core/sidebar/progress-chart.tsx b/web/core/components/core/sidebar/progress-chart.tsx index 25dd3fee5b3..bb4eeb49351 100644 --- a/web/core/components/core/sidebar/progress-chart.tsx +++ b/web/core/components/core/sidebar/progress-chart.tsx @@ -48,7 +48,7 @@ const ProgressChart: React.FC = ({ endDate, totalIssues, className = "", - plotTitle = "issues", + plotTitle = "work items", }) => { const chartData = Object.keys(distribution ?? []).map((key) => ({ currentDate: renderFormattedDateWithoutYear(key), diff --git a/web/core/components/core/theme/theme-switch.tsx b/web/core/components/core/theme/theme-switch.tsx index 7a188a48aa0..1a188fa03ef 100644 --- a/web/core/components/core/theme/theme-switch.tsx +++ b/web/core/components/core/theme/theme-switch.tsx @@ -1,10 +1,11 @@ "use client"; import { FC } from "react"; +// plane imports +import { I_THEME_OPTION, THEME_OPTIONS } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; // constants import { CustomSelect } from "@plane/ui"; -import { THEME_OPTIONS, I_THEME_OPTION } from "@/constants/themes"; // ui type Props = { diff --git a/web/core/components/cycles/active-cycle/cycle-stats.tsx b/web/core/components/cycles/active-cycle/cycle-stats.tsx index 8c0ec14a310..f932415c381 100644 --- a/web/core/components/cycles/active-cycle/cycle-stats.tsx +++ b/web/core/components/cycles/active-cycle/cycle-stats.tsx @@ -6,17 +6,16 @@ import { observer } from "mobx-react"; import { CalendarCheck } from "lucide-react"; // headless ui import { Tab } from "@headlessui/react"; -// types +// plane imports import { EIssuesStoreType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { ICycle, IIssueFilterOptions } from "@plane/types"; // ui import { Tooltip, Loader, PriorityIcon, Avatar } from "@plane/ui"; // components import { SingleProgressStats } from "@/components/core"; import { StateDropdown } from "@/components/dropdowns"; -import { EmptyState } from "@/components/empty-state"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; +import { SimpleEmptyState } from "@/components/empty-state"; // helpers import { cn } from "@/helpers/common.helper"; import { renderFormattedDate, renderFormattedDateWithoutYear } from "@/helpers/date-time.helper"; @@ -26,6 +25,7 @@ import { useIssueDetail, useIssues } from "@/hooks/store"; import { useIntersectionObserver } from "@/hooks/use-intersection-observer"; import useLocalStorage from "@/hooks/use-local-storage"; // plane web components +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { IssueIdentifier } from "@/plane-web/components/issues"; // store import { ActiveCycleIssueDetails } from "@/store/issue/cycle"; @@ -41,11 +41,18 @@ export type ActiveCycleStatsProps = { export const ActiveCycleStats: FC = observer((props) => { const { workspaceSlug, projectId, cycle, cycleId, handleFiltersUpdate, cycleIssueDetails } = props; - + // local storage const { storedValue: tab, setValue: setTab } = useLocalStorage("activeCycleTab", "Assignees"); - + // refs const issuesContainerRef = useRef(null); + // states const [issuesLoaderElement, setIssueLoaderElement] = useState(null); + // plane hooks + const { t } = useTranslation(); + // derived values + const priorityResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/priority" }); + const assigneesResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/assignee" }); + const labelsResolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/label" }); const currentValue = (tab: string | null) => { switch (tab) { @@ -119,7 +126,7 @@ export const ActiveCycleStats: FC = observer((props) => { ) } > - Priority Issues + {t("project_cycles.active_cycle.priority_issue")} @@ -132,7 +139,7 @@ export const ActiveCycleStats: FC = observer((props) => { ) } > - Assignees + {t("project_cycles.active_cycle.assignees")} @@ -145,7 +152,7 @@ export const ActiveCycleStats: FC = observer((props) => { ) } > - Labels + {t("project_cycles.active_cycle.labels")} @@ -231,10 +238,9 @@ export const ActiveCycleStats: FC = observer((props) => { ) : (
    -
    ) @@ -283,7 +289,7 @@ export const ActiveCycleStats: FC = observer((props) => {
    User
    - No assignee + {t("no_assignee")}
    } completed={assignee.completed_issues} @@ -293,10 +299,9 @@ export const ActiveCycleStats: FC = observer((props) => { }) ) : (
    -
    ) @@ -336,7 +341,7 @@ export const ActiveCycleStats: FC = observer((props) => { )) ) : (
    - +
    ) ) : ( diff --git a/web/core/components/cycles/active-cycle/productivity.tsx b/web/core/components/cycles/active-cycle/productivity.tsx index 74957af03c7..613355c7eba 100644 --- a/web/core/components/cycles/active-cycle/productivity.tsx +++ b/web/core/components/cycles/active-cycle/productivity.tsx @@ -1,16 +1,17 @@ import { FC, Fragment } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; +// plane imports +import { useTranslation } from "@plane/i18n"; import { ICycle, TCycleEstimateType } from "@plane/types"; import { Loader } from "@plane/ui"; // components import ProgressChart from "@/components/core/sidebar/progress-chart"; -import { EmptyState } from "@/components/empty-state"; +import { SimpleEmptyState } from "@/components/empty-state"; // constants -import { EmptyStateType } from "@/constants/empty-state"; -import { useCycle, useProjectEstimates } from "@/hooks/store"; +import { useCycle } from "@/hooks/store"; // plane web constants -import { EEstimateSystem } from "@/plane-web/constants/estimates"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { EstimateTypeDropdown } from "../dropdowns/estimate-type-dropdown"; export type ActiveCycleProductivityProps = { @@ -21,11 +22,13 @@ export type ActiveCycleProductivityProps = { export const ActiveCycleProductivity: FC = observer((props) => { const { workspaceSlug, projectId, cycle } = props; + // plane hooks + const { t } = useTranslation(); // hooks const { getEstimateTypeByCycleId, setEstimateType } = useCycle(); - // derived values const estimateType: TCycleEstimateType = (cycle && getEstimateTypeByCycleId(cycle.id)) || "issues"; + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/chart" }); const onChange = async (value: TCycleEstimateType) => { if (!workspaceSlug || !projectId || !cycle || !cycle.id) return; @@ -40,7 +43,9 @@ export const ActiveCycleProductivity: FC = observe
    -

    Issue burndown

    +

    + {t("project_cycles.active_cycle.issue_burndown")} +

    @@ -53,17 +58,17 @@ export const ActiveCycleProductivity: FC = observe
    - Ideal + {t("project_cycles.active_cycle.ideal")}
    - Current + {t("project_cycles.active_cycle.current")}
    {estimateType === "points" ? ( {`Pending points - ${cycle.backlog_estimate_points + cycle.unstarted_estimate_points + cycle.started_estimate_points}`} ) : ( - {`Pending issues - ${cycle.backlog_issues + cycle.unstarted_issues + cycle.started_issues}`} + {`Pending work items - ${cycle.backlog_issues + cycle.unstarted_issues + cycle.started_issues}`} )}
    @@ -84,7 +89,7 @@ export const ActiveCycleProductivity: FC = observe startDate={cycle.start_date ?? ""} endDate={cycle.end_date ?? ""} totalIssues={cycle.total_issues || 0} - plotTitle={"issues"} + plotTitle={"work items"} /> )} @@ -95,7 +100,7 @@ export const ActiveCycleProductivity: FC = observe ) : ( <>
    - +
    )} diff --git a/web/core/components/cycles/active-cycle/progress.tsx b/web/core/components/cycles/active-cycle/progress.tsx index 0dffff5c757..c2f0174f624 100644 --- a/web/core/components/cycles/active-cycle/progress.tsx +++ b/web/core/components/cycles/active-cycle/progress.tsx @@ -4,14 +4,14 @@ import { FC } from "react"; import { observer } from "mobx-react"; // plane package imports import { PROGRESS_STATE_GROUPS_DETAILS } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { ICycle, IIssueFilterOptions } from "@plane/types"; import { LinearProgressIndicator, Loader } from "@plane/ui"; // components -import { EmptyState } from "@/components/empty-state"; -// constants -import { EmptyStateType } from "@/constants/empty-state"; +import { SimpleEmptyState } from "@/components/empty-state"; // hooks import { useProjectState } from "@/hooks/store"; +import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; export type ActiveCycleProgressProps = { cycle: ICycle | null; @@ -22,9 +22,10 @@ export type ActiveCycleProgressProps = { export const ActiveCycleProgress: FC = observer((props) => { const { handleFiltersUpdate, cycle } = props; + // plane hooks + const { t } = useTranslation(); // store hooks const { groupedProjectStates } = useProjectState(); - // derived values const progressIndicatorData = PROGRESS_STATE_GROUPS_DETAILS.map((group, index) => ({ id: index, @@ -40,16 +41,17 @@ export const ActiveCycleProgress: FC = observer((props backlog: cycle?.backlog_issues, } : {}; + const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/active-cycle/progress" }); return cycle && cycle.hasOwnProperty("started_issues") ? (
    -

    Progress

    +

    {t("project_cycles.active_cycle.progress")}

    {cycle.total_issues > 0 && ( {`${cycle.completed_issues + cycle.cancelled_issues}/${cycle.total_issues - cycle.cancelled_issues} ${ - cycle.completed_issues + cycle.cancelled_issues > 1 ? "Issues" : "Issue" + cycle.completed_issues + cycle.cancelled_issues > 1 ? "Work items" : "Work item" } closed`} )} @@ -82,7 +84,7 @@ export const ActiveCycleProgress: FC = observer((props {group}
    {`${groupedIssues[group]} ${ - groupedIssues[group] > 1 ? "Issues" : "Issue" + groupedIssues[group] > 1 ? "Work items" : "Work item" }`}
    @@ -93,7 +95,7 @@ export const ActiveCycleProgress: FC = observer((props {`${cycle.cancelled_issues} cancelled ${ - cycle.cancelled_issues > 1 ? "issues are" : "issue is" + cycle.cancelled_issues > 1 ? "work items are" : "work item is" } excluded from this report.`}{" "} @@ -101,7 +103,7 @@ export const ActiveCycleProgress: FC = observer((props
    ) : (
    - +
    )}
    diff --git a/web/core/components/cycles/analytics-sidebar/issue-progress.tsx b/web/core/components/cycles/analytics-sidebar/issue-progress.tsx index 0efdaded511..47f9df5e5d3 100644 --- a/web/core/components/cycles/analytics-sidebar/issue-progress.tsx +++ b/web/core/components/cycles/analytics-sidebar/issue-progress.tsx @@ -8,6 +8,7 @@ import { useSearchParams } from "next/navigation"; import { ChevronUp, ChevronDown } from "lucide-react"; import { Disclosure, Transition } from "@headlessui/react"; import { EIssueFilterType, EIssuesStoreType } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { ICycle, IIssueFilterOptions, TCyclePlotType, TProgressSnapshot } from "@plane/types"; // components import { CycleProgressStats } from "@/components/cycles"; @@ -30,7 +31,7 @@ type Options = { }; export const cycleEstimateOptions: Options[] = [ - { value: "issues", label: "Issues" }, + { value: "issues", label: "Work items" }, { value: "points", label: "Points" }, ]; export const cycleChartOptions: Options[] = [ @@ -63,6 +64,7 @@ export const CycleAnalyticsProgress: FC = observer((pro const { issuesFilter: { issueFilters, updateFilters }, } = useIssues(EIssuesStoreType.CYCLE); + const { t } = useTranslation(); // derived values const cycleDetails = validateCycleSnapshot(getCycleById(cycleId)); @@ -138,7 +140,9 @@ export const CycleAnalyticsProgress: FC = observer((pro {isCycleDateValid ? (
    -
    Progress
    +
    + {t("project_cycles.active_cycle.progress")} +
    {open ? ( @@ -150,7 +154,9 @@ export const CycleAnalyticsProgress: FC = observer((pro
    ) : (
    -
    Progress
    +
    + {t("project_cycles.active_cycle.progress")} +
    )} diff --git a/web/core/components/cycles/analytics-sidebar/progress-stats.tsx b/web/core/components/cycles/analytics-sidebar/progress-stats.tsx index 63203ffdec4..4f13b861281 100644 --- a/web/core/components/cycles/analytics-sidebar/progress-stats.tsx +++ b/web/core/components/cycles/analytics-sidebar/progress-stats.tsx @@ -4,6 +4,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; import Image from "next/image"; import { Tab } from "@headlessui/react"; +import { useTranslation } from "@plane/i18n"; import { IIssueFilterOptions, IIssueFilters, @@ -73,6 +74,7 @@ type TStateStatComponent = { export const AssigneeStatComponent = observer((props: TAssigneeStatComponent) => { const { distribution, isEditable, filters, handleFiltersUpdate } = props; + const { t } = useTranslation(); return (
    {distribution && distribution.length > 0 ? ( @@ -104,7 +106,7 @@ export const AssigneeStatComponent = observer((props: TAssigneeStatComponent) =>
    User
    - No assignee + {t("no_assignee")}
    } completed={assignee?.completed ?? 0} @@ -117,7 +119,7 @@ export const AssigneeStatComponent = observer((props: TAssigneeStatComponent) =>
    empty members
    -
    No assignees yet
    +
    {t("no_assignee")}
    )}
    @@ -126,6 +128,7 @@ export const AssigneeStatComponent = observer((props: TAssigneeStatComponent) => export const LabelStatComponent = observer((props: TLabelStatComponent) => { const { distribution, isEditable, filters, handleFiltersUpdate } = props; + const { t } = useTranslation(); return (
    {distribution && distribution.length > 0 ? ( @@ -142,7 +145,7 @@ export const LabelStatComponent = observer((props: TLabelStatComponent) => { backgroundColor: label.color ?? "transparent", }} /> - {label.title ?? "No labels"} + {label.title ?? t("no_labels_yet")}
    } completed={label.completed} @@ -165,7 +168,7 @@ export const LabelStatComponent = observer((props: TLabelStatComponent) => { backgroundColor: label.color ?? "transparent", }} /> - {label.title ?? "No labels"} + {label.title ?? t("no_labels_yet")}
    } completed={label.completed} @@ -179,7 +182,7 @@ export const LabelStatComponent = observer((props: TLabelStatComponent) => {
    empty label
    -
    No labels yet
    +
    {t("no_labels_yet")}
    )}
    @@ -222,15 +225,15 @@ export const StateStatComponent = observer((props: TStateStatComponent) => { const progressStats = [ { key: "stat-states", - title: "States", + i18n_title: "common.states", }, { key: "stat-assignees", - title: "Assignees", + i18n_title: "common.assignees", }, { key: "stat-labels", - title: "Labels", + i18n_title: "common.labels", }, ]; @@ -267,6 +270,7 @@ export const CycleProgressStats: FC = observer((props) => { `cycle-analytics-tab-${cycleId}`, "stat-assignees" ); + const { t } = useTranslation(); // derived values const currentTabIndex = (tab: string): number => progressStats.findIndex((stat) => stat.key === tab); @@ -337,7 +341,7 @@ export const CycleProgressStats: FC = observer((props) => { key={stat.key} onClick={() => setCycleTab(stat.key)} > - {stat.title} + {t(stat.i18n_title)} ))}
    diff --git a/web/core/components/cycles/analytics-sidebar/sidebar-details.tsx b/web/core/components/cycles/analytics-sidebar/sidebar-details.tsx index c60b5dae95c..eaa460f7abe 100644 --- a/web/core/components/cycles/analytics-sidebar/sidebar-details.tsx +++ b/web/core/components/cycles/analytics-sidebar/sidebar-details.tsx @@ -4,6 +4,7 @@ import isEmpty from "lodash/isEmpty"; import { observer } from "mobx-react"; import { LayersIcon, SquareUser, Users } from "lucide-react"; // plane types +import { useTranslation } from "@plane/i18n"; import { ICycle } from "@plane/types"; // plane ui import { Avatar, AvatarGroup, TextArea } from "@plane/ui"; @@ -24,6 +25,7 @@ export const CycleSidebarDetails: FC = observer((props) => { // hooks const { getUserDetails } = useMember(); const { areEstimateEnabledByProjectId, currentActiveEstimateId, estimateById } = useProjectEstimates(); + const { t } = useTranslation(); const areEstimateEnabled = projectId && areEstimateEnabledByProjectId(projectId.toString()); const cycleStatus = cycleDetails?.status?.toLocaleLowerCase(); @@ -32,10 +34,10 @@ export const CycleSidebarDetails: FC = observer((props) => { const issueCount = isCompleted && !isEmpty(cycleDetails?.progress_snapshot) ? cycleDetails?.progress_snapshot?.total_issues === 0 - ? "0 Issue" + ? `0 ${t("common.work_item")}` : `${cycleDetails?.progress_snapshot?.completed_issues}/${cycleDetails?.progress_snapshot?.total_issues}` : cycleDetails?.total_issues === 0 - ? "0 Issue" + ? `0 ${t("common.work_item")}` : `${cycleDetails?.completed_issues}/${cycleDetails?.total_issues}`; const estimateType = areEstimateEnabled && currentActiveEstimateId && estimateById(currentActiveEstimateId); const cycleOwnerDetails = cycleDetails ? getUserDetails(cycleDetails.owned_by_id) : undefined; @@ -51,10 +53,10 @@ export const CycleSidebarDetails: FC = observer((props) => { const issueEstimatePointCount = isCompleted && !isEmpty(cycleDetails?.progress_snapshot) ? cycleDetails?.progress_snapshot.total_issues === 0 - ? "0 Issue" + ? `0 ${t("common.work_item")}` : `${cycleDetails?.progress_snapshot.completed_estimate_points}/${cycleDetails?.progress_snapshot.total_estimate_points}` : cycleDetails?.total_issues === 0 - ? "0 Issue" + ? `0 ${t("common.work_item")}` : `${cycleDetails?.completed_estimate_points}/${cycleDetails?.total_estimate_points}`; return (
    @@ -70,7 +72,7 @@ export const CycleSidebarDetails: FC = observer((props) => {
    - Lead + {t("lead")}
    @@ -83,7 +85,7 @@ export const CycleSidebarDetails: FC = observer((props) => {
    - Members + {t("members")}
    @@ -104,7 +106,7 @@ export const CycleSidebarDetails: FC = observer((props) => { ) : ( - No assignees + {t("no_assignee")} )}
    @@ -113,7 +115,7 @@ export const CycleSidebarDetails: FC = observer((props) => {
    - Issues + {t("work_items")}
    {issueCount} @@ -127,7 +129,7 @@ export const CycleSidebarDetails: FC = observer((props) => {
    - Points + {t("points")}
    {issueEstimatePointCount} diff --git a/web/core/components/cycles/analytics-sidebar/sidebar-header.tsx b/web/core/components/cycles/analytics-sidebar/sidebar-header.tsx index af645804eb7..c8d4cc487b4 100644 --- a/web/core/components/cycles/analytics-sidebar/sidebar-header.tsx +++ b/web/core/components/cycles/analytics-sidebar/sidebar-header.tsx @@ -5,14 +5,13 @@ import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; import { ArchiveIcon, ArchiveRestoreIcon, ChevronRight, EllipsisIcon, LinkIcon, Trash2 } from "lucide-react"; // types +import { CYCLE_STATUS, CYCLE_UPDATED, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { ICycle } from "@plane/types"; // ui import { CustomMenu, setToast, TOAST_TYPE } from "@plane/ui"; // components import { DateRangeDropdown } from "@/components/dropdowns"; -// constants -import { CYCLE_STATUS } from "@/constants/cycle"; -import { CYCLE_UPDATED } from "@/constants/event-tracker"; // helpers import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper"; import { copyUrlToClipboard } from "@/helpers/string.helper"; @@ -20,7 +19,6 @@ import { copyUrlToClipboard } from "@/helpers/string.helper"; import { useCycle, useEventTracker, useUserPermissions } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; // plane web constants -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; // services import { CycleService } from "@/services/cycle.service"; // local components @@ -53,6 +51,7 @@ export const CycleSidebarHeader: FC = observer((props) => { const { allowPermissions } = useUserPermissions(); const { updateCycleDetails, restoreCycle } = useCycle(); const { setTrackElement, captureCycleEvent } = useEventTracker(); + const { t } = useTranslation(); // form info const { control, reset } = useForm({ @@ -71,16 +70,16 @@ export const CycleSidebarHeader: FC = observer((props) => { .then(() => { setToast({ type: TOAST_TYPE.SUCCESS, - title: "Restore success", - message: "Your cycle can be found in project cycles.", + title: t("project_cycles.action.restore.success.title"), + message: t("project_cycles.action.restore.success.description"), }); router.push(`/${workspaceSlug.toString()}/projects/${projectId.toString()}/archives/cycles`); }) .catch(() => setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Cycle could not be restored. Please try again.", + title: t("project_cycles.action.restore.failed.title"), + message: t("project_cycles.action.restore.failed.description"), }) ); }; @@ -90,14 +89,14 @@ export const CycleSidebarHeader: FC = observer((props) => { .then(() => { setToast({ type: TOAST_TYPE.SUCCESS, - title: "Link Copied!", - message: "Cycle link copied to clipboard.", + title: t("common.link_copied"), + message: t("common.link_copied_to_clipboard"), }); }) .catch(() => { setToast({ type: TOAST_TYPE.ERROR, - title: "Some error occurred", + title: t("common.errors.default.message"), }); }); }; @@ -167,15 +166,14 @@ export const CycleSidebarHeader: FC = observer((props) => { submitChanges(payload, "date_range"); setToast({ type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Cycle updated successfully.", + title: t("project_cycles.action.update.success.title"), + message: t("project_cycles.action.update.success.description"), }); } else { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: - "You already have a cycle on the given dates, if you want to create a draft cycle, you can do that by removing both the dates.", + title: t("project_cycles.action.update.failed.title"), + message: t("project_cycles.action.update.error.already_exists"), }); reset({ ...cycleDetails }); } @@ -232,15 +230,15 @@ export const CycleSidebarHeader: FC = observer((props) => { {isCompleted ? (
    - Archive cycle + {t("common.archive")}
    ) : (
    -

    Archive cycle

    +

    {t("common.archive")}

    - Only completed cycles
    can be archived. + {t("project_cycles.only_completed_cycles_can_be_archived")}

    @@ -251,7 +249,7 @@ export const CycleSidebarHeader: FC = observer((props) => { - Restore cycle + {t("project_cycles.action.restore.title")} )} @@ -264,7 +262,7 @@ export const CycleSidebarHeader: FC = observer((props) => { > - Delete cycle + {t("delete")} )} @@ -283,7 +281,7 @@ export const CycleSidebarHeader: FC = observer((props) => { backgroundColor: `${currentCycle.color}20`, }} > - {currentCycle.title} + {t(currentCycle.i18n_title)} )}
    diff --git a/web/core/components/cycles/applied-filters/date.tsx b/web/core/components/cycles/applied-filters/date.tsx index 488eef12ce9..c2ee47be524 100644 --- a/web/core/components/cycles/applied-filters/date.tsx +++ b/web/core/components/cycles/applied-filters/date.tsx @@ -1,7 +1,7 @@ import { observer } from "mobx-react"; import { X } from "lucide-react"; // helpers -import { DATE_AFTER_FILTER_OPTIONS } from "@/constants/filters"; +import { DATE_AFTER_FILTER_OPTIONS } from "@plane/constants"; import { renderFormattedDate } from "@/helpers/date-time.helper"; import { capitalizeFirstLetter } from "@/helpers/string.helper"; // constants diff --git a/web/core/components/cycles/applied-filters/root.tsx b/web/core/components/cycles/applied-filters/root.tsx index 9fa74c39365..b53f0ab4d07 100644 --- a/web/core/components/cycles/applied-filters/root.tsx +++ b/web/core/components/cycles/applied-filters/root.tsx @@ -1,12 +1,13 @@ import { observer } from "mobx-react"; import { X } from "lucide-react"; +import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TCycleFilters } from "@plane/types"; // hooks import { Tag } from "@plane/ui"; import { AppliedDateFilters, AppliedStatusFilters } from "@/components/cycles"; import { replaceUnderscoreIfSnakeCase } from "@/helpers/string.helper"; import { useUserPermissions } from "@/hooks/store"; -import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; // components // helpers @@ -26,6 +27,7 @@ export const CycleAppliedFiltersList: React.FC = observer((props) => { const { appliedFilters, handleClearAllFilters, handleRemoveFilter, alwaysAllowEditing } = props; // store hooks const { allowPermissions } = useUserPermissions(); + const { t } = useTranslation(); if (!appliedFilters) return null; @@ -77,7 +79,7 @@ export const CycleAppliedFiltersList: React.FC = observer((props) => { {isEditingAllowed && ( diff --git a/web/core/components/cycles/applied-filters/status.tsx b/web/core/components/cycles/applied-filters/status.tsx index d46b565033e..7bb21925114 100644 --- a/web/core/components/cycles/applied-filters/status.tsx +++ b/web/core/components/cycles/applied-filters/status.tsx @@ -1,6 +1,7 @@ import { observer } from "mobx-react"; import { X } from "lucide-react"; -import { CYCLE_STATUS } from "@/constants/cycle"; +import { CYCLE_STATUS } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { cn } from "@/helpers/common.helper"; type Props = { @@ -11,6 +12,7 @@ type Props = { export const AppliedStatusFilters: React.FC = observer((props) => { const { handleRemove, values, editable } = props; + const { t } = useTranslation(); return ( <> @@ -25,7 +27,7 @@ export const AppliedStatusFilters: React.FC = observer((props) => { statusDetails?.textColor )} > - {statusDetails?.title} + {statusDetails && t(statusDetails?.i18n_title)} {editable && (
    diff --git a/web/core/components/cycles/delete-modal.tsx b/web/core/components/cycles/delete-modal.tsx index f89a1cea77f..210b1a13a3d 100644 --- a/web/core/components/cycles/delete-modal.tsx +++ b/web/core/components/cycles/delete-modal.tsx @@ -4,12 +4,12 @@ import { useState } from "react"; import { observer } from "mobx-react"; import { useParams, useSearchParams } from "next/navigation"; // types +import { PROJECT_ERROR_MESSAGES, CYCLE_DELETED } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { ICycle } from "@plane/types"; // ui import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui"; // constants -import { CYCLE_DELETED } from "@/constants/event-tracker"; -import { PROJECT_ERROR_MESSAGES } from "@/constants/project"; // hooks import { useEventTracker, useCycle } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; @@ -29,6 +29,7 @@ export const CycleDeleteModal: React.FC = observer((props) => { // store hooks const { captureCycleEvent } = useEventTracker(); const { deleteCycle } = useCycle(); + const { t } = useTranslation(); // router const router = useAppRouter(); const { cycleId } = useParams(); @@ -59,9 +60,9 @@ export const CycleDeleteModal: React.FC = observer((props) => { ? PROJECT_ERROR_MESSAGES.permissionError : PROJECT_ERROR_MESSAGES.cycleDeleteError; setToast({ - title: currentError.title, + title: t(currentError.i18n_title), type: TOAST_TYPE.ERROR, - message: currentError.message, + message: currentError.i18n_message && t(currentError.i18n_message), }); captureCycleEvent({ eventName: CYCLE_DELETED, diff --git a/web/core/components/cycles/dropdowns/filters/end-date.tsx b/web/core/components/cycles/dropdowns/filters/end-date.tsx index e5b4a7a8639..f9b7d2f37b0 100644 --- a/web/core/components/cycles/dropdowns/filters/end-date.tsx +++ b/web/core/components/cycles/dropdowns/filters/end-date.tsx @@ -1,11 +1,11 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; - +// constants +import { DATE_AFTER_FILTER_OPTIONS } from "@plane/constants"; // components import { DateFilterModal } from "@/components/core"; import { FilterHeader, FilterOption } from "@/components/issues"; -// constants -import { DATE_AFTER_FILTER_OPTIONS } from "@/constants/filters"; + // helpers import { isInDateFormat } from "@/helpers/date-time.helper"; diff --git a/web/core/components/cycles/dropdowns/filters/start-date.tsx b/web/core/components/cycles/dropdowns/filters/start-date.tsx index 9bfd8f2d8e2..eb2032edb68 100644 --- a/web/core/components/cycles/dropdowns/filters/start-date.tsx +++ b/web/core/components/cycles/dropdowns/filters/start-date.tsx @@ -1,11 +1,11 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; - +// constants +import { DATE_AFTER_FILTER_OPTIONS } from "@plane/constants"; // components import { DateFilterModal } from "@/components/core"; import { FilterHeader, FilterOption } from "@/components/issues"; -// constants -import { DATE_AFTER_FILTER_OPTIONS } from "@/constants/filters"; + // helpers import { isInDateFormat } from "@/helpers/date-time.helper"; @@ -17,7 +17,6 @@ type Props = { export const FilterStartDate: React.FC = observer((props) => { const { appliedFilters, handleUpdate, searchQuery } = props; - const [previewEnabled, setPreviewEnabled] = useState(true); const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false); diff --git a/web/core/components/cycles/dropdowns/filters/status.tsx b/web/core/components/cycles/dropdowns/filters/status.tsx index b2ca62b51db..0e24a2a2182 100644 --- a/web/core/components/cycles/dropdowns/filters/status.tsx +++ b/web/core/components/cycles/dropdowns/filters/status.tsx @@ -1,10 +1,11 @@ import React, { useState } from "react"; import { observer } from "mobx-react"; +import { CYCLE_STATUS } from "@plane/constants"; +import { useTranslation } from "@plane/i18n"; import { TCycleGroups } from "@plane/types"; // components import { FilterHeader, FilterOption } from "@/components/issues"; // types -import { CYCLE_STATUS } from "@/constants/cycle"; // constants type Props = { @@ -17,7 +18,8 @@ export const FilterStatus: React.FC = observer((props) => { const { appliedFilters, handleUpdate, searchQuery } = props; // states const [previewEnabled, setPreviewEnabled] = useState(true); - + //hooks + const { t } = useTranslation(); const appliedFiltersCount = appliedFilters?.length ?? 0; const filteredOptions = CYCLE_STATUS.filter((p) => p.value.includes(searchQuery.toLowerCase())); @@ -36,7 +38,7 @@ export const FilterStatus: React.FC = observer((props) => { key={status.value} isChecked={appliedFilters?.includes(status.value) ? true : false} onClick={() => handleUpdate(status.value)} - title={status.title} + title={t(status.i18n_title)} /> )) ) : ( diff --git a/web/core/components/cycles/form.tsx b/web/core/components/cycles/form.tsx index 7651c5d4433..c5c9c918697 100644 --- a/web/core/components/cycles/form.tsx +++ b/web/core/components/cycles/form.tsx @@ -2,14 +2,16 @@ import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; +// plane imports +import { ETabIndices } from "@plane/constants"; // types +import { useTranslation } from "@plane/i18n"; import { ICycle } from "@plane/types"; // ui import { Button, Input, TextArea } from "@plane/ui"; // components import { DateRangeDropdown, ProjectDropdown } from "@/components/dropdowns"; // constants -import { ETabIndices } from "@/constants/tab-indices"; // helpers import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"; import { shouldRenderProject } from "@/helpers/project.helper"; @@ -34,6 +36,7 @@ const defaultValues: Partial = { export const CycleForm: React.FC = (props) => { const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data, isMobile = false } = props; + const { t } = useTranslation(); // form data const { formState: { errors, isSubmitting, dirtyFields }, @@ -84,7 +87,9 @@ export const CycleForm: React.FC = (props) => { )} /> )} -

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

    +

    + {status ? t("project_cycles.update_cycle") : t("project_cycles.create_cycle")} +

    @@ -92,17 +97,17 @@ export const CycleForm: React.FC = (props) => { name="name" control={control} rules={{ - required: "Title is required", + required: t("title_is_required"), maxLength: { value: 255, - message: "Title should be less than 255 characters", + message: t("title_should_be_less_than_255_characters"), }, }} render={({ field: { value, onChange } }) => ( = (props) => { render={({ field: { value, onChange } }) => (