From 6a13a64996e1f8ed6416776b533a0371d69a0194 Mon Sep 17 00:00:00 2001 From: Vamsi Krishna <46787868+mathalav55@users.noreply.github.com> Date: Thu, 2 Jan 2025 18:27:34 +0530 Subject: [PATCH 01/13] [WEB-1964]chore: cycles actions restructuring (#6298) * chore: cycles quick actions restructuring * chore: added additional actions to cycle list actions * chore: cycle quick action structure * chore: added additional actions to cycle list actions * chore: added end cycle hook * fix: updated end cycle export --------- Co-authored-by: gurusinath --- web/ce/components/cycles/additional-actions.tsx | 7 +++++++ web/ce/components/cycles/end-cycle/index.ts | 2 ++ web/ce/components/cycles/end-cycle/modal.tsx | 12 ++++++++++++ .../components/cycles/end-cycle/use-end-cycle.tsx | 7 +++++++ web/ce/components/cycles/index.ts | 2 ++ .../cycles/list/cycle-list-item-action.tsx | 6 ++++-- .../components/cycles/list/cycles-list-item.tsx | 2 +- web/core/components/cycles/quick-actions.tsx | 15 +++++++++++++++ web/ee/components/cycles/end-cycle/index.ts | 1 + web/ee/components/cycles/index.ts | 2 ++ 10 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 web/ce/components/cycles/additional-actions.tsx create mode 100644 web/ce/components/cycles/end-cycle/index.ts create mode 100644 web/ce/components/cycles/end-cycle/modal.tsx create mode 100644 web/ce/components/cycles/end-cycle/use-end-cycle.tsx create mode 100644 web/ee/components/cycles/end-cycle/index.ts diff --git a/web/ce/components/cycles/additional-actions.tsx b/web/ce/components/cycles/additional-actions.tsx new file mode 100644 index 00000000000..1fcb7146fca --- /dev/null +++ b/web/ce/components/cycles/additional-actions.tsx @@ -0,0 +1,7 @@ +import { FC } from "react"; +import { observer } from "mobx-react"; +type Props = { + cycleId: string; + projectId: string; +}; +export const CycleAdditionalActions: FC = observer(() => <>); diff --git a/web/ce/components/cycles/end-cycle/index.ts b/web/ce/components/cycles/end-cycle/index.ts new file mode 100644 index 00000000000..2e60c456193 --- /dev/null +++ b/web/ce/components/cycles/end-cycle/index.ts @@ -0,0 +1,2 @@ +export * from "./modal"; +export * from "./use-end-cycle"; diff --git a/web/ce/components/cycles/end-cycle/modal.tsx b/web/ce/components/cycles/end-cycle/modal.tsx new file mode 100644 index 00000000000..de66c1a9cbd --- /dev/null +++ b/web/ce/components/cycles/end-cycle/modal.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +interface Props { + isOpen: boolean; + handleClose: () => void; + cycleId: string; + projectId: string; + workspaceSlug: string; + transferrableIssuesCount: number; +} + +export const EndCycleModal: React.FC = () => <>; diff --git a/web/ce/components/cycles/end-cycle/use-end-cycle.tsx b/web/ce/components/cycles/end-cycle/use-end-cycle.tsx new file mode 100644 index 00000000000..c1bf6261855 --- /dev/null +++ b/web/ce/components/cycles/end-cycle/use-end-cycle.tsx @@ -0,0 +1,7 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const useEndCycle = (isCurrentCycle: boolean) => ({ + isEndCycleModalOpen: false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setEndCycleModalOpen: (value: boolean) => {}, + endCycleContextMenu: undefined, +}); diff --git a/web/ce/components/cycles/index.ts b/web/ce/components/cycles/index.ts index 89934687567..1da1150255f 100644 --- a/web/ce/components/cycles/index.ts +++ b/web/ce/components/cycles/index.ts @@ -1,2 +1,4 @@ export * from "./active-cycle"; export * from "./analytics-sidebar"; +export * from "./additional-actions"; +export * from "./end-cycle"; diff --git a/web/core/components/cycles/list/cycle-list-item-action.tsx b/web/core/components/cycles/list/cycle-list-item-action.tsx index fca9ede6750..2fa3d4fd346 100644 --- a/web/core/components/cycles/list/cycle-list-item-action.tsx +++ b/web/core/components/cycles/list/cycle-list-item-action.tsx @@ -33,7 +33,8 @@ import { generateQueryParams } from "@/helpers/router.helper"; import { useCycle, useEventTracker, useMember, useUserPermissions } from "@/hooks/store"; import { useAppRouter } from "@/hooks/use-app-router"; import { usePlatformOS } from "@/hooks/use-platform-os"; -// plane web +// plane web components +import { CycleAdditionalActions } from "@/plane-web/components/cycles"; // plane web constants import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; // services @@ -156,7 +157,7 @@ export const CycleListItemAction: FC = observer((props) => { try { const res = await cycleService.cycleDateCheck(workspaceSlug as string, projectId as string, payload); return res.status; - } catch (err) { + } catch { return false; } }; @@ -244,6 +245,7 @@ export const CycleListItemAction: FC = observer((props) => { )} + {showTransferIssues && (
= observer((props) => { const cycleDetails = getCycleById(cycleId); const isArchived = !!cycleDetails?.archived_at; const isCompleted = cycleDetails?.status?.toLowerCase() === "completed"; + const isCurrentCycle = cycleDetails?.status?.toLowerCase() === "current"; + const transferableIssuesCount = cycleDetails ? cycleDetails.total_issues - cycleDetails.completed_issues : 0; // auth const isEditingAllowed = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], @@ -48,6 +51,8 @@ export const CycleQuickActions: React.FC = observer((props) => { projectId ); + const { isEndCycleModalOpen, setEndCycleModalOpen, endCycleContextMenu } = useEndCycle(isCurrentCycle); + const cycleLink = `${workspaceSlug}/projects/${projectId}/cycles/${cycleId}`; const handleCopyText = () => copyUrlToClipboard(cycleLink).then(() => { @@ -138,6 +143,8 @@ export const CycleQuickActions: React.FC = observer((props) => { }, ]; + if (endCycleContextMenu) MENU_ITEMS.splice(3, 0, endCycleContextMenu); + return ( <> {cycleDetails && ( @@ -163,6 +170,14 @@ export const CycleQuickActions: React.FC = observer((props) => { workspaceSlug={workspaceSlug} projectId={projectId} /> + setEndCycleModalOpen(false)} + cycleId={cycleId} + projectId={projectId} + workspaceSlug={workspaceSlug} + transferrableIssuesCount={transferableIssuesCount} + />
)} diff --git a/web/ee/components/cycles/end-cycle/index.ts b/web/ee/components/cycles/end-cycle/index.ts new file mode 100644 index 00000000000..3d772e91cf9 --- /dev/null +++ b/web/ee/components/cycles/end-cycle/index.ts @@ -0,0 +1 @@ +export * from "ce/components/cycles/end-cycle"; diff --git a/web/ee/components/cycles/index.ts b/web/ee/components/cycles/index.ts index 89934687567..30d8e85dd0c 100644 --- a/web/ee/components/cycles/index.ts +++ b/web/ee/components/cycles/index.ts @@ -1,2 +1,4 @@ export * from "./active-cycle"; export * from "./analytics-sidebar"; +export * from "./end-cycle"; +export * from "ce/components/cycles/additional-actions"; From ade0aa1643eed6fd0fee1ae38e904fc9a8b93902 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:08:02 +0530 Subject: [PATCH 02/13] fix: active cycle graph tooltip and endpoint validation (#6306) --- .../cycles/active-cycle/use-cycles-details.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/web/core/components/cycles/active-cycle/use-cycles-details.ts b/web/core/components/cycles/active-cycle/use-cycles-details.ts index 4412319ba37..2bfe9951e2e 100644 --- a/web/core/components/cycles/active-cycle/use-cycles-details.ts +++ b/web/core/components/cycles/active-cycle/use-cycles-details.ts @@ -30,21 +30,23 @@ const useCyclesDetails = (props: IActiveCycleDetails) => { // fetch cycle details useSWR( - workspaceSlug && projectId && cycle ? `PROJECT_ACTIVE_CYCLE_${projectId}_PROGRESS` : null, - workspaceSlug && projectId && cycle ? () => fetchActiveCycleProgress(workspaceSlug, projectId, cycle.id) : null, + workspaceSlug && projectId && cycle?.id ? `PROJECT_ACTIVE_CYCLE_${projectId}_PROGRESS` : null, + workspaceSlug && projectId && cycle?.id ? () => fetchActiveCycleProgress(workspaceSlug, projectId, cycle.id) : null, { revalidateIfStale: false, revalidateOnFocus: false } ); useSWR( - workspaceSlug && projectId && cycle && !cycle?.distribution ? `PROJECT_ACTIVE_CYCLE_${projectId}_DURATION` : null, - workspaceSlug && projectId && cycle && !cycle?.distribution + workspaceSlug && projectId && cycle?.id && !cycle?.distribution + ? `PROJECT_ACTIVE_CYCLE_${projectId}_DURATION` + : null, + workspaceSlug && projectId && cycle?.id && !cycle?.distribution ? () => fetchActiveCycleAnalytics(workspaceSlug, projectId, cycle.id, "issues") : null ); useSWR( - workspaceSlug && projectId && cycle && !cycle?.estimate_distribution + workspaceSlug && projectId && cycle?.id && !cycle?.estimate_distribution ? `PROJECT_ACTIVE_CYCLE_${projectId}_ESTIMATE_DURATION` : null, - workspaceSlug && projectId && cycle && !cycle?.estimate_distribution + workspaceSlug && projectId && cycle?.id && !cycle?.estimate_distribution ? () => fetchActiveCycleAnalytics(workspaceSlug, projectId, cycle.id, "points") : null ); From 873e4330bc88098465756459c5ecd33dc9f75b18 Mon Sep 17 00:00:00 2001 From: Vamsi Krishna <46787868+mathalav55@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:16:26 +0530 Subject: [PATCH 03/13] [WEB-2870]feat: language support (#6215) * fix: adding language support package * fix: language support implementation using mobx * fix: adding more languages for support * fix: profile settings translations * feat: added language support for sidebar and user settings * feat: added language support for deactivation modal * fix: added project sync after transfer issues (#6200) * code refactor and improvement (#6203) * chore: package code refactoring * chore: component restructuring and refactor * chore: comment create improvement * refactor: enhance workspace and project wrapper modularity (#6207) * [WEB-2678]feat: added functionality to add labels directly from dropdown (#6211) * enhancement:added functionality to add features directly from dropdown * fix: fixed import order * fix: fixed lint errors * chore: added common component for project activity (#6212) * chore: added common component for project activity * fix: added enum * fix: added enum for initiatives * - Do not clear temp files that are locked. (#6214) - Handle edge cases in sync workspace * fix: labels empty state for drop down (#6216) * refactor: remove cn helper function from the editor package (#6217) * * feat: added language support to issue create modal in sidebar * fix: project activity type * * fix: added missing translations * fix: modified translation for plurals * fix: fixed spanish translation * dev: language type error in space user profile types * fix: type fixes * chore: added alpha tag --------- Co-authored-by: sriram veeraghanta Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: Prateek Shourya Co-authored-by: Akshita Goyal <36129505+gakshita@users.noreply.github.com> Co-authored-by: Satish Gandham Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Co-authored-by: gurusinath --- packages/constants/src/workspace.ts | 2 +- packages/i18n/.eslintignore | 3 + packages/i18n/.eslintrc.js | 9 + packages/i18n/.prettierignore | 4 + packages/i18n/.prettierrc | 5 + packages/i18n/package.json | 20 + packages/i18n/src/components/index.tsx | 29 ++ packages/i18n/src/components/store.ts | 42 ++ packages/i18n/src/config/index.ts | 39 ++ packages/i18n/src/hooks/index.ts | 1 + packages/i18n/src/hooks/use-translation.ts | 17 + packages/i18n/src/index.ts | 3 + .../i18n/src/locales/en/translations.json | 318 ++++++++++++ .../i18n/src/locales/es/translations.json | 318 ++++++++++++ .../i18n/src/locales/fr/translations.json | 318 ++++++++++++ .../i18n/src/locales/ja/translations.json | 318 ++++++++++++ packages/i18n/tsconfig.json | 10 + packages/types/src/users.d.ts | 2 +- space/core/store/profile.store.ts | 1 + .../(projects)/active-cycles/header.tsx | 17 +- .../(projects)/analytics/header.tsx | 6 +- .../(projects)/profile/[userId]/layout.tsx | 4 +- .../(projects)/profile/[userId]/navbar.tsx | 5 +- web/app/create-workspace/page.tsx | 42 +- web/app/invitations/page.tsx | 29 +- web/app/profile/activity/page.tsx | 6 +- web/app/profile/appearance/page.tsx | 9 +- web/app/profile/notifications/page.tsx | 8 +- web/app/profile/page.tsx | 425 +--------------- web/app/profile/security/page.tsx | 35 +- web/app/profile/sidebar.tsx | 27 +- web/app/provider.tsx | 23 +- .../workspace-active-cycles-upgrade.tsx | 18 +- .../global/product-updates-header.tsx | 32 +- web/ce/components/global/version-number.tsx | 6 +- .../components/projects/create/attributes.tsx | 6 +- web/ce/components/projects/create/root.tsx | 12 +- web/ce/components/workspace/edition-badge.tsx | 4 +- web/ce/constants/dashboard.ts | 23 +- .../constants/project/settings/features.tsx | 10 + web/ce/types/projects/project-activity.ts | 20 +- .../account/deactivate-account-modal.tsx | 11 +- .../account/password-strength-meter.tsx | 14 +- .../core/theme/custom-theme-selector.tsx | 71 +-- .../components/core/theme/theme-switch.tsx | 9 +- .../components/dropdowns/member/index.tsx | 10 +- .../dropdowns/member/member-options.tsx | 10 +- web/core/components/dropdowns/priority.tsx | 30 +- web/core/components/dropdowns/project.tsx | 9 +- web/core/components/dropdowns/state.tsx | 12 +- .../global/product-updates/footer.tsx | 52 +- .../global/product-updates/modal.tsx | 11 +- .../components/issues/issue-modal/base.tsx | 18 +- .../components/default-properties.tsx | 20 +- .../issue-modal/components/title-input.tsx | 10 +- .../components/issues/issue-modal/form.tsx | 23 +- web/core/components/issues/select/label.tsx | 13 +- web/core/components/profile/form.tsx | 463 ++++++++++++++++++ web/core/components/profile/index.ts | 5 +- .../notification/email-notification-form.tsx | 37 +- .../project/create/common-attributes.tsx | 24 +- web/core/components/project/create/header.tsx | 6 +- .../project/create/project-create-buttons.tsx | 6 +- web/core/components/project/form.tsx | 6 +- .../project/project-feature-update.tsx | 10 +- .../project/settings/features-list.tsx | 10 +- .../workspace/create-workspace-form.tsx | 54 +- .../components/workspace/sidebar/dropdown.tsx | 59 ++- .../sidebar/favorites/favorites-menu.tsx | 30 +- .../sidebar/favorites/new-fav-folder.tsx | 38 +- .../workspace/sidebar/help-section.tsx | 14 +- .../workspace/sidebar/project-navigation.tsx | 13 +- .../workspace/sidebar/projects-list-item.tsx | 38 +- .../workspace/sidebar/projects-list.tsx | 23 +- .../workspace/sidebar/quick-actions.tsx | 4 +- .../workspace/sidebar/user-menu.tsx | 16 +- .../workspace/sidebar/workspace-menu.tsx | 25 +- web/core/constants/cycle.ts | 6 + web/core/constants/profile.ts | 4 + web/core/constants/themes.ts | 7 + web/core/lib/wrappers/store-wrapper.tsx | 11 +- web/core/store/user/profile.store.ts | 1 + web/next.config.js | 1 + web/package.json | 1 + 84 files changed, 2588 insertions(+), 873 deletions(-) create mode 100644 packages/i18n/.eslintignore create mode 100644 packages/i18n/.eslintrc.js create mode 100644 packages/i18n/.prettierignore create mode 100644 packages/i18n/.prettierrc create mode 100644 packages/i18n/package.json create mode 100644 packages/i18n/src/components/index.tsx create mode 100644 packages/i18n/src/components/store.ts create mode 100644 packages/i18n/src/config/index.ts create mode 100644 packages/i18n/src/hooks/index.ts create mode 100644 packages/i18n/src/hooks/use-translation.ts create mode 100644 packages/i18n/src/index.ts create mode 100644 packages/i18n/src/locales/en/translations.json create mode 100644 packages/i18n/src/locales/es/translations.json create mode 100644 packages/i18n/src/locales/fr/translations.json create mode 100644 packages/i18n/src/locales/ja/translations.json create mode 100644 packages/i18n/tsconfig.json create mode 100644 web/core/components/profile/form.tsx diff --git a/packages/constants/src/workspace.ts b/packages/constants/src/workspace.ts index c17b5432ee8..d37decf3f2f 100644 --- a/packages/constants/src/workspace.ts +++ b/packages/constants/src/workspace.ts @@ -1,5 +1,5 @@ export const ORGANIZATION_SIZE = [ - "Just myself", + "Just myself", // TODO: translate "2-10", "11-50", "51-200", diff --git a/packages/i18n/.eslintignore b/packages/i18n/.eslintignore new file mode 100644 index 00000000000..6019047c3e5 --- /dev/null +++ b/packages/i18n/.eslintignore @@ -0,0 +1,3 @@ +build/* +dist/* +out/* \ No newline at end of file diff --git a/packages/i18n/.eslintrc.js b/packages/i18n/.eslintrc.js new file mode 100644 index 00000000000..558b8f76ed4 --- /dev/null +++ b/packages/i18n/.eslintrc.js @@ -0,0 +1,9 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + root: true, + extends: ["@plane/eslint-config/library.js"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: true, + }, +}; diff --git a/packages/i18n/.prettierignore b/packages/i18n/.prettierignore new file mode 100644 index 00000000000..d5be669c5e0 --- /dev/null +++ b/packages/i18n/.prettierignore @@ -0,0 +1,4 @@ +.turbo +out/ +dist/ +build/ \ No newline at end of file diff --git a/packages/i18n/.prettierrc b/packages/i18n/.prettierrc new file mode 100644 index 00000000000..87d988f1b26 --- /dev/null +++ b/packages/i18n/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "trailingComma": "es5" +} diff --git a/packages/i18n/package.json b/packages/i18n/package.json new file mode 100644 index 00000000000..0a4d0562797 --- /dev/null +++ b/packages/i18n/package.json @@ -0,0 +1,20 @@ +{ + "name": "@plane/i18n", + "version": "0.24.1", + "description": "I18n shared across multiple apps internally", + "private": true, + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "lint": "eslint src --ext .ts,.tsx", + "lint:errors": "eslint src --ext .ts,.tsx --quiet" + }, + "dependencies": { + "@plane/utils": "*" + }, + "devDependencies": { + "@plane/eslint-config": "*", + "@types/node": "^22.5.4", + "typescript": "^5.3.3" + } +} diff --git a/packages/i18n/src/components/index.tsx b/packages/i18n/src/components/index.tsx new file mode 100644 index 00000000000..705b0ee4a15 --- /dev/null +++ b/packages/i18n/src/components/index.tsx @@ -0,0 +1,29 @@ +import { observer } from "mobx-react"; +import React, { createContext, useEffect } from "react"; +import { Language, languages } from "../config"; +import { TranslationStore } from "./store"; + +// Create the store instance +const translationStore = new TranslationStore(); + +// Create Context +export const TranslationContext = createContext(translationStore); + +export const TranslationProvider = observer(({ children }: { children: React.ReactNode }) => { + // Handle storage events for cross-tab synchronization + useEffect(() => { + const handleStorageChange = (event: StorageEvent) => { + if (event.key === "userLanguage" && event.newValue) { + const newLang = event.newValue as Language; + if (languages.includes(newLang)) { + translationStore.setLanguage(newLang); + } + } + }; + + window.addEventListener("storage", handleStorageChange); + return () => window.removeEventListener("storage", handleStorageChange); + }, []); + + return {children}; +}); diff --git a/packages/i18n/src/components/store.ts b/packages/i18n/src/components/store.ts new file mode 100644 index 00000000000..6524c0df271 --- /dev/null +++ b/packages/i18n/src/components/store.ts @@ -0,0 +1,42 @@ +import { makeObservable, observable } from "mobx"; +import { Language, fallbackLng, languages, translations } from "../config"; + +export class TranslationStore { + currentLocale: Language = fallbackLng; + + constructor() { + makeObservable(this, { + currentLocale: observable.ref, + }); + this.initializeLanguage(); + } + + get availableLanguages() { + return languages; + } + + t(key: string) { + return translations[this.currentLocale]?.[key] || translations[fallbackLng][key] || key; + } + + setLanguage(lng: Language) { + try { + localStorage.setItem("userLanguage", lng); + this.currentLocale = lng; + } catch (error) { + console.error(error); + } + } + + initializeLanguage() { + if (typeof window === "undefined") return; + const savedLocale = localStorage.getItem("userLanguage") as Language; + if (savedLocale && languages.includes(savedLocale)) { + this.setLanguage(savedLocale); + } else { + const browserLang = navigator.language.split("-")[0] as Language; + const newLocale = languages.includes(browserLang as Language) ? (browserLang as Language) : fallbackLng; + this.setLanguage(newLocale); + } + } +} diff --git a/packages/i18n/src/config/index.ts b/packages/i18n/src/config/index.ts new file mode 100644 index 00000000000..3f55d8cf6f0 --- /dev/null +++ b/packages/i18n/src/config/index.ts @@ -0,0 +1,39 @@ +import en from "../locales/en/translations.json"; +import fr from "../locales/fr/translations.json"; +import es from "../locales/es/translations.json"; +import ja from "../locales/ja/translations.json"; + +export type Language = (typeof languages)[number]; +export type Translations = { + [key: string]: { + [key: string]: string; + }; +}; + +export const fallbackLng = "en"; +export const languages = ["en", "fr", "es", "ja"] as const; +export const translations: Translations = { + en, + fr, + es, + ja, +}; + +export const SUPPORTED_LANGUAGES = [ + { + label: "English", + value: "en", + }, + { + label: "French", + value: "fr", + }, + { + label: "Spanish", + value: "es", + }, + { + label: "Japanese", + value: "ja", + }, +]; diff --git a/packages/i18n/src/hooks/index.ts b/packages/i18n/src/hooks/index.ts new file mode 100644 index 00000000000..fb4e297e216 --- /dev/null +++ b/packages/i18n/src/hooks/index.ts @@ -0,0 +1 @@ +export * from "./use-translation"; diff --git a/packages/i18n/src/hooks/use-translation.ts b/packages/i18n/src/hooks/use-translation.ts new file mode 100644 index 00000000000..f947d1d5eb5 --- /dev/null +++ b/packages/i18n/src/hooks/use-translation.ts @@ -0,0 +1,17 @@ +import { useContext } from "react"; +import { TranslationContext } from "../components"; +import { Language } from "../config"; + +export function useTranslation() { + const store = useContext(TranslationContext); + if (!store) { + throw new Error("useTranslation must be used within a TranslationProvider"); + } + + return { + t: (key: string) => store.t(key), + currentLocale: store.currentLocale, + changeLanguage: (lng: Language) => store.setLanguage(lng), + languages: store.availableLanguages, + }; +} diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts new file mode 100644 index 00000000000..639ef4b59a4 --- /dev/null +++ b/packages/i18n/src/index.ts @@ -0,0 +1,3 @@ +export * from "./config"; +export * from "./components"; +export * from "./hooks"; diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json new file mode 100644 index 00000000000..d67f34a2df9 --- /dev/null +++ b/packages/i18n/src/locales/en/translations.json @@ -0,0 +1,318 @@ +{ + "submit": "Submit", + "cancel": "Cancel", + "loading": "Loading", + "error": "Error", + "success": "Success", + "warning": "Warning", + "info": "Info", + "close": "Close", + "yes": "Yes", + "no": "No", + "ok": "OK", + "name": "Name", + "description": "Description", + "search": "Search", + "add_member": "Add member", + "remove_member": "Remove member", + "add_members": "Add members", + "remove_members": "Remove members", + "add": "Add", + "remove": "Remove", + "add_new": "Add new", + "remove_selected": "Remove selected", + "first_name": "First name", + "last_name": "Last name", + "email": "Email", + "display_name": "Display name", + "role": "Role", + "timezone": "Timezone", + "avatar": "Avatar", + "cover_image": "Cover image", + "password": "Password", + "change_cover": "Change cover", + "language": "Language", + "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", + "workspaces": "Workspaces", + "create_workspace": "Create workspace", + "invitations": "Invitations", + "summary": "Summary", + "assigned": "Assigned", + "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", + "system_preference": "System preference", + "light": "Light", + "dark": "Dark", + "light_contrast": "Light high contrast", + "dark_contrast": "Dark high contrast", + "custom": "Custom theme", + "select_your_theme": "Select your theme", + "customize_your_theme": "Customize your theme", + "background_color": "Background color", + "text_color": "Text color", + "primary_color": "Primary(Theme) color", + "sidebar_background_color": "Sidebar background color", + "sidebar_text_color": "Sidebar text color", + "set_theme": "Set theme", + "enter_a_valid_hex_code_of_6_characters": "Enter a valid hex code of 6 characters", + "background_color_is_required": "Background color is required", + "text_color_is_required": "Text color is required", + "primary_color_is_required": "Primary color is required", + "sidebar_background_color_is_required": "Sidebar background color is required", + "sidebar_text_color_is_required": "Sidebar text color is required", + "updating_theme": "Updating theme", + "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.", + "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.", + "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", + "comments": "Comments", + "comments_description": "Notify me when someone leaves a comment on the issue", + "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.", + "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.", + "10000_feet_view_description": "Zoom out to see running cycles across all your projects at once instead of going from Cycle to Cycle in each project.", + "get_snapshot_of_each_active_cycle": "Get a snapshot of each active cycle.", + "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.", + "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", + "your_work": "Your work", + "drafts": "Drafts", + "projects": "Projects", + "views": "Views", + "workspace": "Workspace", + "archives": "Archives", + "settings": "Settings", + "failed_to_move_favorite": "Failed to move favorite", + "your_favorites": "Your favorites", + "no_favorites_yet": "No favorites yet", + "create_folder": "Create folder", + "new_folder": "New folder", + "favorite_updated_successfully": "Favorite updated successfully", + "favorite_created_successfully": "Favorite created successfully", + "folder_already_exists": "Folder already exists", + "folder_name_cannot_be_empty": "Folder name cannot be empty", + "something_went_wrong": "Something went wrong", + "failed_to_reorder_favorite": "Failed to reorder favorite", + "favorite_removed_successfully": "Favorite removed successfully", + "failed_to_create_favorite": "Failed to create favorite", + "failed_to_rename_favorite": "Failed to rename favorite", + "project_link_copied_to_clipboard": "Project link copied to clipboard", + "link_copied": "Link copied", + "your_projects": "Your projects", + "add_project": "Add project", + "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_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", + "project_name": "Project name", + "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...", + "only_alphanumeric_non_latin_characters_allowed": "Only Alphanumeric & Non-latin characters are allowed.", + "project_id_is_required": "Project ID is required", + "select_network": "Select network", + "lead": "Lead", + "private": "Private", + "public": "Public", + "accessible_only_by_invite": "Accessible only by invite", + "anyone_in_the_workspace_except_guests_can_join": "Anyone in the workspace except Guests can join", + "creating": "Creating", + "creating_project": "Creating project", + "adding_project_to_favorites": "Adding project to favorites", + "project_added_to_favorites": "Project added to favorites", + "couldnt_add_the_project_to_favorites": "Couldn't add the project to favorites. Please try again.", + "removing_project_from_favorites": "Removing project from favorites", + "project_removed_from_favorites": "Project removed from favorites", + "couldnt_remove_the_project_from_favorites": "Couldn't remove the project from favorites. Please try again.", + "add_to_favorites": "Add to favorites", + "remove_from_favorites": "Remove from favorites", + "publish_settings": "Publish settings", + "publish": "Publish", + "copy_link": "Copy link", + "leave_project": "Leave project", + "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", + "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_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.", + "work_management_description": "Manage your work and projects with ease.", + "documentation": "Documentation", + "message_support": "Message support", + "contact_sales": "Contact sales", + "hyper_mode": "Hyper Mode", + "keyboard_shortcuts": "Keyboard shortcuts", + "whats_new": "What's new?", + "version": "Version", + "we_are_having_trouble_fetching_the_updates": "We are having trouble fetching the updates.", + "our_changelogs": "our changelogs", + "for_the_latest_updates": "for the latest updates.", + "please_visit": "Please visit", + "docs": "Docs", + "full_changelog": "Full changelog", + "support": "Support", + "discord": "Discord", + "powered_by_plane_pages": "Powered by Plane Pages", + "please_select_at_least_one_invitation": "Please select at least one invitation.", + "please_select_at_least_one_invitation_description": "Please select at least one invitation to join the workspace.", + "we_see_that_someone_has_invited_you_to_join_a_workspace": "We see that someone has invited you to join a workspace", + "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", + "join_a_workspace_description": "Join a workspace", + "accept_and_join": "Accept & Join", + "go_home": "Go Home", + "no_pending_invites": "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": "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.", + "deactivating": "Deactivating", + "confirm": "Confirm", + "draft_created": "Draft created", + "issue_created_successfully": "Issue 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", + "create_a_draft": "Create a draft", + "save_to_drafts": "Save to Drafts", + "save": "Save", + "updating": "Updating", + "create_new_issue": "Create new issue", + "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", + "create_more": "Create more", + "add_to_project": "Add to project", + "discard": "Discard", + "duplicate_issue_found": "Duplicate issue found", + "duplicate_issues_found": "Duplicate issues found", + "no_matching_results": "No matching results", + "title_is_required": "Title is required", + "title": "Title", + "state": "State", + "priority": "Priority", + "none": "None", + "urgent": "Urgent", + "high": "High", + "medium": "Medium", + "low": "Low", + "members": "Members", + "assignee": "Assignee", + "assignees": "Assignees", + "you": "You", + "labels": "Labels", + "create_new_label": "Create new label", + "start_date": "Start date", + "due_date": "Due date", + "cycle": "Cycle", + "estimate": "Estimate", + "change_parent_issue": "Change parent issue", + "remove_parent_issue": "Remove parent issue", + "add_parent": "Add parent", + "loading_members": "Loading members..." +} diff --git a/packages/i18n/src/locales/es/translations.json b/packages/i18n/src/locales/es/translations.json new file mode 100644 index 00000000000..6cbd8c3a8cb --- /dev/null +++ b/packages/i18n/src/locales/es/translations.json @@ -0,0 +1,318 @@ +{ + "submit": "Enviar", + "cancel": "Cancelar", + "loading": "Cargando", + "error": "Error", + "success": "Éxito", + "warning": "Advertencia", + "info": "Información", + "close": "Cerrar", + "yes": "Sí", + "no": "No", + "ok": "OK", + "name": "Nombre", + "description": "Descripción", + "search": "Buscar", + "add_member": "Agregar miembro", + "remove_member": "Eliminar miembro", + "add_members": "Agregar miembros", + "remove_members": "Eliminar miembros", + "add": "Agregar", + "remove": "Eliminar", + "add_new": "Agregar nuevo", + "remove_selected": "Eliminar seleccionados", + "first_name": "Nombre", + "last_name": "Apellido", + "email": "Correo electrónico", + "display_name": "Nombre para mostrar", + "role": "Rol", + "timezone": "Zona horaria", + "avatar": "Avatar", + "cover_image": "Imagen de portada", + "password": "Contraseña", + "change_cover": "Cambiar portada", + "language": "Idioma", + "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", + "your_account": "Tu cuenta", + "profile": "Perfil", + "security": "Seguridad", + "activity": "Actividad", + "appearance": "Apariencia", + "notifications": "Notificaciones", + "workspaces": "Espacios de trabajo", + "create_workspace": "Crear espacio de trabajo", + "invitations": "Invitaciones", + "summary": "Resumen", + "assigned": "Asignado", + "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.", + "theme": "Tema", + "system_preference": "Preferencia del sistema", + "light": "Claro", + "dark": "Oscuro", + "light_contrast": "Alto contraste claro", + "dark_contrast": "Alto contraste oscuro", + "custom": "Tema personalizado", + "select_your_theme": "Selecciona tu tema", + "customize_your_theme": "Personaliza tu tema", + "background_color": "Color de fondo", + "text_color": "Color del texto", + "primary_color": "Color primario (Tema)", + "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", + "updating_theme": "Actualizando tema", + "theme_updated_successfully": "Tema actualizado con éxito", + "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", + "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.", + "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", + "comments": "Comentarios", + "comments_description": "Notifícame cuando alguien deje un comentario en el problema", + "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", + "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.", + "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.", + "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.", + "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", + "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", + "your_work": "Tu trabajo", + "drafts": "Borradores", + "projects": "Proyectos", + "views": "Vistas", + "workspace": "Espacio de trabajo", + "archives": "Archivos", + "settings": "Configuración", + "failed_to_move_favorite": "Error al mover favorito", + "your_favorites": "Tus favoritos", + "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", + "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", + "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", + "link_copied": "Enlace copiado", + "your_projects": "Tus proyectos", + "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_cover_image_alt": "Imagen de portada del proyecto", + "name_is_required": "El nombre es obligatorio", + "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...", + "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", + "select_network": "Seleccionar red", + "lead": "Líder", + "private": "Privado", + "public": "Público", + "accessible_only_by_invite": "Accesible solo por invitación", + "anyone_in_the_workspace_except_guests_can_join": "Cualquiera en el espacio de trabajo excepto invitados puede unirse", + "creating": "Creando", + "creating_project": "Creando proyecto", + "adding_project_to_favorites": "Agregando proyecto a favoritos", + "project_added_to_favorites": "Proyecto agregado a favoritos", + "couldnt_add_the_project_to_favorites": "No se pudo agregar el proyecto a favoritos. Por favor, inténtalo de nuevo.", + "removing_project_from_favorites": "Eliminando proyecto de favoritos", + "project_removed_from_favorites": "Proyecto eliminado de favoritos", + "couldnt_remove_the_project_from_favorites": "No se pudo eliminar el proyecto de favoritos. Por favor, inténtalo de nuevo.", + "add_to_favorites": "Agregar a favoritos", + "remove_from_favorites": "Eliminar de favoritos", + "publish_settings": "Configuración de publicación", + "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", + "congrats": "¡Felicitaciones!", + "project": "Proyecto", + "open_project": "Abrir proyecto", + "issues": "Problemas", + "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_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.", + "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.", + "work_management_description": "Gestiona tu trabajo y proyectos con facilidad.", + "documentation": "Documentación", + "message_support": "Mensaje al soporte", + "contact_sales": "Contactar ventas", + "hyper_mode": "Modo Hyper", + "keyboard_shortcuts": "Atajos de teclado", + "whats_new": "¿Qué hay de nuevo?", + "version": "Versión", + "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", + "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.", + "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", + "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", + "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", + "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.", + "deactivating": "Desactivando", + "confirm": "Confirmar", + "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", + "create_a_draft": "Crear un borrador", + "save_to_drafts": "Guardar en borradores", + "save": "Guardar", + "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_more": "Crear más", + "add_to_project": "Agregar al proyecto", + "discard": "Descartar", + "duplicate_issue_found": "Problema duplicado encontrado", + "duplicate_issues_found": "Problemas duplicados encontrados", + "no_matching_results": "No hay resultados coincidentes", + "title_is_required": "El título es obligatorio", + "title": "Título", + "state": "Estado", + "priority": "Prioridad", + "none": "Ninguno", + "urgent": "Urgente", + "high": "Alta", + "medium": "Media", + "low": "Baja", + "members": "Miembros", + "assignee": "Asignado", + "assignees": "Asignados", + "you": "Tú", + "labels": "Etiquetas", + "create_new_label": "Crear nueva etiqueta", + "start_date": "Fecha de inicio", + "due_date": "Fecha de vencimiento", + "cycle": "Ciclo", + "estimate": "Estimación", + "change_parent_issue": "Cambiar problema padre", + "remove_parent_issue": "Eliminar problema padre", + "add_parent": "Agregar padre", + "loading_members": "Cargando miembros..." +} diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json new file mode 100644 index 00000000000..f9b59be3d3c --- /dev/null +++ b/packages/i18n/src/locales/fr/translations.json @@ -0,0 +1,318 @@ +{ + "submit": "Soumettre", + "cancel": "Annuler", + "loading": "Chargement", + "error": "Erreur", + "success": "Succès", + "warning": "Avertissement", + "info": "Info", + "close": "Fermer", + "yes": "Oui", + "no": "Non", + "ok": "OK", + "name": "Nom", + "description": "Description", + "search": "Rechercher", + "add_member": "Ajouter un membre", + "remove_member": "Supprimer un membre", + "add_members": "Ajouter des membres", + "remove_members": "Supprimer des membres", + "add": "Ajouter", + "remove": "Supprimer", + "add_new": "Ajouter nouveau", + "remove_selected": "Supprimer la sélection", + "first_name": "Prénom", + "last_name": "Nom de famille", + "email": "Email", + "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", + "language": "Langue", + "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é", + "activity": "Activité", + "appearance": "Apparence", + "notifications": "Notifications", + "workspaces": "Workspaces", + "create_workspace": "Créer un workspace", + "invitations": "Invitations", + "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 pour 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 être supérieure à 8 caractères.", + "password_is_weak": "Le mot de passe est faible.", + "password_is_strong": "Le mot de passe est fort.", + "load_more": "Charger plus", + "select_or_customize_your_interface_color_scheme": "Sélectionnez ou personnalisez votre schéma de couleurs de l'interface.", + "theme": "Thème", + "system_preference": "Préférence du système", + "light": "Clair", + "dark": "Foncé", + "light_contrast": "Clair de haut contraste", + "dark_contrast": "Foncé de haut contraste", + "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", + "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", + "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.", + "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é", + "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 workspace", + "only_your_instance_admin_can_create_workspaces": "Seuls les administrateurs de votre instance peuvent créer des workspaces", + "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", + "general_settings": "Paramètres généraux", + "sign_out": "Déconnexion", + "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", + "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", + "enter_god_mode": "Entrer en mode dieu", + "workspace_logo": "Logo de l'espace de travail", + "new_issue": "Nouveau problème", + "home": "Accueil", + "your_work": "Votre travail", + "drafts": "Brouillons", + "projects": "Projets", + "views": "Vues", + "workspace": "Espace de travail", + "archives": "Archives", + "settings": "Paramètres", + "failed_to_move_favorite": "Impossible de déplacer le favori", + "your_favorites": "Vos favoris", + "no_favorites_yet": "Aucun favori pour le moment", + "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", + "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", + "project_link_copied_to_clipboard": "Lien du projet copié dans le presse-papiers", + "link_copied": "Lien copié", + "your_projects": "Vos projets", + "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_cover_image_alt": "Image de couverture du projet", + "name_is_required": "Le nom est requis", + "title_should_be_less_than_255_characters": "Le titre doit être inférieur à 255 caractères", + "project_name": "Nom du projet", + "project_id_must_be_at_least_1_character": "Le projet ID doit être au moins de 1 caractère", + "project_id_must_be_at_most_5_characters": "Le projet ID doit être au plus de 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...", + "only_alphanumeric_non_latin_characters_allowed": "Seuls les caractères alphanumériques et non latins sont autorisés.", + "project_id_is_required": "Le projet ID est requis", + "select_network": "Sélectionner le réseau", + "lead": "Lead", + "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", + "creating": "Création", + "creating_project": "Création du projet", + "adding_project_to_favorites": "Ajout du projet aux favoris", + "project_added_to_favorites": "Projet ajouté aux favoris", + "couldnt_add_the_project_to_favorites": "Impossible d'ajouter le projet aux favoris. Veuillez réessayer.", + "removing_project_from_favorites": "Suppression du projet des favoris", + "project_removed_from_favorites": "Projet supprimé des favoris", + "couldnt_remove_the_project_from_favorites": "Impossible de supprimer le projet des favoris. Veuillez réessayer.", + "add_to_favorites": "Ajouter aux favoris", + "remove_from_favorites": "Supprimer des favoris", + "publish_settings": "Paramètres de publication", + "publish": "Publier", + "copy_link": "Copier le lien", + "leave_project": "Quitter le projet", + "join_the_project_to_rearrange": "Rejoindre 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", + "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é.", + "documentation": "Documentation", + "message_support": "Contacter le support", + "contact_sales": "Contacter les ventes", + "hyper_mode": "Mode hyper", + "keyboard_shortcuts": "Raccourcis clavier", + "whats_new": "Nouveautés?", + "version": "Version", + "we_are_having_trouble_fetching_the_updates": "Nous avons des difficultés à récupérer les mises à jour.", + "our_changelogs": "nos changelogs", + "for_the_latest_updates": "pour les dernières mises à jour.", + "please_visit": "Veuillez visiter", + "docs": "Documentation", + "full_changelog": "Journal complet", + "support": "Support", + "discord": "Discord", + "powered_by_plane_pages": "Propulsé par Plane Pages", + "please_select_at_least_one_invitation": "Veuillez sélectionner au moins une invitation.", + "please_select_at_least_one_invitation_description": "Veuillez sélectionner au moins une invitation pour rejoindre l'espace de travail.", + "we_see_that_someone_has_invited_you_to_join_a_workspace": "Nous voyons que quelqu'un vous a invité à rejoindre un espace de travail", + "join_a_workspace": "Rejoindre un espace de travail", + "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", + "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", + "back_to_home": "Retour à l'accueil", + "workspace_name": "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.", + "deactivating": "Désactivation", + "confirm": "Confirmer", + "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", + "create_a_draft": "Créer un brouillon", + "save_to_drafts": "Enregistrer dans les brouillons", + "save": "Enregistrer", + "updating": "Mise à jour", + "create_new_issue": "Créer un nouveau problème", + "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", + "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", + "no_matching_results": "Aucun résultat correspondant", + "title_is_required": "Le titre est requis", + "title": "Titre", + "state": "État", + "priority": "Priorité", + "none": "Aucune", + "urgent": "Urgent", + "high": "Haute", + "medium": "Moyenne", + "low": "Basse", + "members": "Membres", + "assignee": "Assigné", + "assignees": "Assignés", + "you": "Vous", + "labels": "Étiquettes", + "create_new_label": "Créer une nouvelle étiquette", + "start_date": "Date de début", + "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", + "add_parent": "Ajouter un parent", + "loading_members": "Chargement des membres..." +} diff --git a/packages/i18n/src/locales/ja/translations.json b/packages/i18n/src/locales/ja/translations.json new file mode 100644 index 00000000000..a6a929b735e --- /dev/null +++ b/packages/i18n/src/locales/ja/translations.json @@ -0,0 +1,318 @@ +{ + "submit": "送信", + "cancel": "キャンセル", + "loading": "読み込み中", + "error": "エラー", + "success": "成功", + "warning": "警告", + "info": "情報", + "close": "閉じる", + "yes": "はい", + "no": "いいえ", + "ok": "OK", + "name": "名前", + "description": "説明", + "search": "検索", + "add_member": "メンバーを追加", + "remove_member": "メンバーを削除", + "add_members": "メンバーを追加", + "remove_members": "メンバーを削除", + "add": "追加", + "remove": "削除", + "add_new": "新規追加", + "remove_selected": "選択項目を削除", + "first_name": "名", + "last_name": "姓", + "email": "メールアドレス", + "display_name": "表示名", + "role": "役割", + "timezone": "タイムゾーン", + "avatar": "アバター", + "cover_image": "カバー画像", + "password": "パスワード", + "change_cover": "カバーを変更", + "language": "言語", + "saving": "保存中...", + "save_changes": "変更を保存", + "deactivate_account": "アカウントを無効化", + "deactivate_account_description": "アカウントを無効化すると、そのアカウント内のすべてのデータとリソースが完全に削除され、復元することはできません。", + "profile_settings": "プロフィール設定", + "your_account": "アカウント", + "profile": "プロフィール", + "security": "セキュリティ", + "activity": "アクティビティ", + "appearance": "アピアンス", + "notifications": "通知", + "workspaces": "ワークスペース", + "create_workspace": "ワークスペースを作成", + "invitations": "招待", + "summary": "概要", + "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": "パスワードが強いです。", + "load_more": "もっと読み込む", + "select_or_customize_your_interface_color_scheme": "インターフェースのカラースキームを選択またはカスタマイズしてください。", + "theme": "テーマ", + "system_preference": "システム設定", + "light": "ライト", + "dark": "ダーク", + "light_contrast": "ライト高コントラスト", + "dark_contrast": "ダーク高コントラスト", + "custom": "カスタムテーマ", + "select_your_theme": "テーマを選択", + "customize_your_theme": "テーマをカスタマイズ", + "background_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": "サイドバーテキスト色は必須です", + "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": "購読している問題についての通知を受け取るには、これを有効にしてください。", + "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": "誰かが問題にコメントを残したときに通知します", + "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": "古いパスワード", + "general_settings": "一般設定", + "sign_out": "サインアウト", + "signing_out": "サインアウト中", + "active_cycles": "アクティブサイクル", + "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": "分析", + "workspace_invites": "ワークスペースの招待", + "workspace_settings": "ワークスペース設定", + "enter_god_mode": "ゴッドモードに入る", + "workspace_logo": "ワークスペースロゴ", + "new_issue": "新しい問題", + "home": "ホーム", + "your_work": "あなたの作業", + "drafts": "下書き", + "projects": "プロジェクト", + "views": "ビュー", + "workspace": "ワークスペース", + "archives": "アーカイブ", + "settings": "設定", + "failed_to_move_favorite": "お気に入りの移動に失敗しました", + "your_favorites": "あなたのお気に入り", + "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": "お気に入りの並べ替えに失敗しました", + "favorite_removed_successfully": "お気に入りが正常に削除されました", + "failed_to_create_favorite": "お気に入りの作成に失敗しました", + "failed_to_rename_favorite": "お気に入りの名前変更に失敗しました", + "project_link_copied_to_clipboard": "プロジェクトリンクがクリップボードにコピーされました", + "link_copied": "リンクがコピーされました", + "your_projects": "あなたのプロジェクト", + "add_project": "プロジェクトを追加", + "create_project": "プロジェクトを作成", + "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": "プロジェクトID", + "project_id_tooltip_content": "プロジェクト内の問題を一意に識別するのに役立ちます。最大5文字。", + "description_placeholder": "説明...", + "only_alphanumeric_non_latin_characters_allowed": "英数字と非ラテン文字のみが許可されます。", + "project_id_is_required": "プロジェクトIDは必須です", + "select_network": "ネットワークを選択", + "lead": "リード", + "private": "プライベート", + "public": "パブリック", + "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": "お気に入りからプロジェクトを削除中", + "project_removed_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": "ドラッグして並べ替え", + "congrats": "おめでとうございます!", + "project": "プロジェクト", + "open_project": "プロジェクトを開く", + "issues": "問題", + "cycles": "サイクル", + "modules": "モジュール", + "pages": "ページ", + "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": "作業とプロジェクトを簡単に管理します。", + "documentation": "ドキュメント", + "message_support": "サポートにメッセージを送る", + "contact_sales": "営業に連絡", + "hyper_mode": "ハイパーモード", + "keyboard_shortcuts": "キーボードショートカット", + "whats_new": "新着情報", + "version": "バージョン", + "we_are_having_trouble_fetching_the_updates": "更新の取得に問題が発生しています。", + "our_changelogs": "私たちの変更履歴", + "for_the_latest_updates": "最新の更新情報については", + "please_visit": "訪問してください", + "docs": "ドキュメント", + "full_changelog": "完全な変更履歴", + "support": "サポート", + "discord": "ディスコード", + "powered_by_plane_pages": "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": "誰かがワークスペースに参加するようにあなたを招待したことがわかります", + "join_a_workspace": "ワークスペースに参加", + "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": "誰かがワークスペースに招待した場合、ここで確認できます", + "back_to_home": "ホームに戻る", + "workspace_name": "ワークスペース名", + "deactivate_your_account": "アカウントを無効化", + "deactivate_your_account_description": "無効化すると、問題を割り当てられなくなり、ワークスペースの請求もされなくなります。アカウントを再有効化するには、このメールアドレスに招待されたワークスペースが必要です。", + "deactivating": "無効化中", + "confirm": "確認", + "draft_created": "下書きが作成されました", + "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": "保存", + "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": "重複問題が見つかりました", + "no_matching_results": "一致する結果はありません", + "title_is_required": "タイトルは必須です", + "title": "タイトル", + "state": "ステータス", + "priority": "優先度", + "none": "なし", + "urgent": "緊急", + "high": "高", + "medium": "中", + "low": "低", + "members": "メンバー", + "assignee": "アサイン者", + "assignees": "アサイン者", + "you": "あなた", + "labels": "ラベル", + "create_new_label": "新しいラベルを作成", + "start_date": "開始日", + "due_date": "期限日", + "cycle": "サイクル", + "estimate": "見積もり", + "change_parent_issue": "親問題を変更", + "remove_parent_issue": "親問題を削除", + "add_parent": "親問題を追加", + "loading_members": "メンバーを読み込んでいます..." +} diff --git a/packages/i18n/tsconfig.json b/packages/i18n/tsconfig.json new file mode 100644 index 00000000000..6599e6e82aa --- /dev/null +++ b/packages/i18n/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@plane/typescript-config/react-library.json", + "compilerOptions": { + "jsx": "react", + "lib": ["esnext", "dom"], + "resolveJsonModule": true + }, + "include": ["./src"], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/packages/types/src/users.d.ts b/packages/types/src/users.d.ts index 452bc23c238..c562e7c246b 100644 --- a/packages/types/src/users.d.ts +++ b/packages/types/src/users.d.ts @@ -25,7 +25,6 @@ export interface IUser extends IUserLite { is_password_autoset: boolean; is_tour_completed: boolean; mobile_number: string | null; - role: string | null; last_workspace_id: string; user_timezone: string; username: string; @@ -62,6 +61,7 @@ export type TUserProfile = { billing_address_country: string | undefined; billing_address: string | undefined; has_billing_address: boolean; + language: string; created_at: Date | string; updated_at: Date | string; }; diff --git a/space/core/store/profile.store.ts b/space/core/store/profile.store.ts index d0332805890..5e001a875e8 100644 --- a/space/core/store/profile.store.ts +++ b/space/core/store/profile.store.ts @@ -54,6 +54,7 @@ export class ProfileStore implements IProfileStore { has_billing_address: false, created_at: "", updated_at: "", + language: "", }; // services diff --git a/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx b/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx index 4edf41bbdba..6416aee125c 100644 --- a/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx @@ -1,6 +1,6 @@ "use client"; - import { observer } from "mobx-react"; +import { useTranslation } from "@plane/i18n"; // ui import { Breadcrumbs, ContrastIcon, Header } from "@plane/ui"; // components @@ -8,15 +8,17 @@ import { BreadcrumbLink } from "@/components/common"; // plane web components import { UpgradeBadge } from "@/plane-web/components/workspace"; -export const WorkspaceActiveCycleHeader = observer(() => ( -
- - +export const WorkspaceActiveCycleHeader = observer(() => { + const { t } = useTranslation(); + return ( +
+ + } /> } @@ -25,4 +27,5 @@ export const WorkspaceActiveCycleHeader = observer(() => (
-)); + ); +}); diff --git a/web/app/[workspaceSlug]/(projects)/analytics/header.tsx b/web/app/[workspaceSlug]/(projects)/analytics/header.tsx index 4aa66e2a433..fe55f8cbdc9 100644 --- a/web/app/[workspaceSlug]/(projects)/analytics/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/analytics/header.tsx @@ -3,8 +3,8 @@ import { useEffect } from "react"; import { observer } from "mobx-react"; import { useSearchParams } from "next/navigation"; -// icons import { BarChart2, PanelRight } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; // ui import { Breadcrumbs, Header } from "@plane/ui"; // components @@ -13,8 +13,8 @@ import { BreadcrumbLink } from "@/components/common"; import { cn } from "@/helpers/common.helper"; // hooks import { useAppTheme } from "@/hooks/store"; - export const WorkspaceAnalyticsHeader = observer(() => { + const { t } = useTranslation(); const searchParams = useSearchParams(); const analytics_tab = searchParams.get("analytics_tab"); // store hooks @@ -41,7 +41,7 @@ export const WorkspaceAnalyticsHeader = observer(() => { } />} + link={} />} /> {analytics_tab === "custom" ? ( diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx index f31ff959dce..d6743e8f2ba 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/layout.tsx @@ -4,6 +4,7 @@ import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; import useSWR from "swr"; // components +import { useTranslation } from "@plane/i18n"; import { AppHeader, ContentWrapper } from "@/components/core"; import { ProfileSidebar } from "@/components/profile"; // constants @@ -32,6 +33,7 @@ const UseProfileLayout: React.FC = observer((props) => { const pathname = usePathname(); // store hooks const { allowPermissions } = useUserPermissions(); + const { t } = useTranslation(); // derived values const isAuthorized = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], @@ -79,7 +81,7 @@ const UseProfileLayout: React.FC = observer((props) => {
{children}
) : (
- You do not have the permission to access this page. + {t("you_do_not_have_the_permission_to_access_this_page")}
)} diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/navbar.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/navbar.tsx index 4acb93217f6..e002f8f6641 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/navbar.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/navbar.tsx @@ -2,6 +2,7 @@ import React from "react"; import Link from "next/link"; import { useParams, usePathname } from "next/navigation"; +import { useTranslation } from "@plane/i18n"; // components // constants @@ -14,7 +15,7 @@ type Props = { export const ProfileNavbar: React.FC = (props) => { const { isAuthorized } = props; - + const { t } = useTranslation(); const { workspaceSlug, userId } = useParams(); const pathname = usePathname(); @@ -32,7 +33,7 @@ export const ProfileNavbar: React.FC = (props) => { : "border-transparent" }`} > - {tab.label} + {t(tab.label)} ))} diff --git a/web/app/create-workspace/page.tsx b/web/app/create-workspace/page.tsx index 36bc8978ad2..77b71492f3a 100644 --- a/web/app/create-workspace/page.tsx +++ b/web/app/create-workspace/page.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react"; import Image from "next/image"; import Link from "next/link"; import { useTheme } from "next-themes"; +import { useTranslation } from "@plane/i18n"; import { IWorkspace } from "@plane/types"; // components import { Button, getButtonStyling } from "@plane/ui"; @@ -22,6 +23,7 @@ import WhiteHorizontalLogo from "@/public/plane-logos/white-horizontal-with-blue import WorkspaceCreationDisabled from "@/public/workspace/workspace-creation-disabled.png"; const CreateWorkspacePage = observer(() => { + const { t } = useTranslation(); // router const router = useAppRouter(); // store hooks @@ -38,6 +40,17 @@ const CreateWorkspacePage = observer(() => { // derived values const isWorkspaceCreationDisabled = getIsWorkspaceCreationDisabled(); + // 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 || ""); + + return `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; + }; + const onSubmit = async (workspace: IWorkspace) => { await updateUserProfile({ last_workspace_id: workspace.id }).then(() => router.push(`/${workspace.slug}`)); }; @@ -54,7 +67,7 @@ const CreateWorkspacePage = observer(() => { href="/" >
- Plane logo + {t("plane_logo")}
@@ -64,27 +77,30 @@ const CreateWorkspacePage = observer(() => {
{isWorkspaceCreationDisabled ? (
- Workspace creation disabled -
Only your instance admin can create workspaces
-

- If you know your instance admin's email address,
click the button below to get in touch with - them. + {t("workspace_creation_disabled")} +

+ {t("only_your_instance_admin_can_create_workspaces")} +
+

+ {t("only_your_instance_admin_can_create_workspaces_description")}

- - Request instance admin + + {t("request_instance_admin")}
) : (
-

Create your workspace

+

{t("create_your_workspace")}

{ // router const router = useAppRouter(); // store hooks + const { t } = useTranslation(); const { captureEvent, joinWorkspaceMetricGroup } = useEventTracker(); const { data: currentUser } = useUser(); const { updateUserProfile } = useUserProfile(); @@ -72,8 +73,8 @@ const UserInvitationsPage = observer(() => { if (invitationsRespond.length === 0) { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Please select at least one invitation.", + title: t("error"), + message: t("please_select_at_least_one_invitation"), }); return; } @@ -107,8 +108,8 @@ const UserInvitationsPage = observer(() => { .catch(() => { setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Something went wrong, Please try again.", + title: t("error"), + message: t("something_went_wrong_please_try_again"), }); setIsJoiningWorkspaces(false); }); @@ -122,8 +123,8 @@ const UserInvitationsPage = observer(() => { }); setToast({ type: TOAST_TYPE.ERROR, - title: "Error!", - message: "Something went wrong, Please try again.", + title: t("error"), + message: t("something_went_wrong_please_try_again"), }); setIsJoiningWorkspaces(false); }); @@ -152,8 +153,8 @@ const UserInvitationsPage = observer(() => { invitations.length > 0 ? (
-
We see that someone has invited you to
-

Join a workspace

+
{t("we_see_that_someone_has_invited_you_to_join_a_workspace")}
+

{t("join_a_workspace")}

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

Theme

-

Select or customize your interface color scheme.

+

{t("theme")}

+

{t("select_or_customize_your_interface_color_scheme")}

diff --git a/web/app/profile/notifications/page.tsx b/web/app/profile/notifications/page.tsx index b39563378b1..cbdcd147d73 100644 --- a/web/app/profile/notifications/page.tsx +++ b/web/app/profile/notifications/page.tsx @@ -2,6 +2,7 @@ import useSWR from "swr"; // components +import { useTranslation } from "@plane/i18n"; import { PageHead } from "@/components/core"; import { ProfileSettingContentHeader, ProfileSettingContentWrapper } from "@/components/profile"; import { EmailNotificationForm } from "@/components/profile/notification"; @@ -12,6 +13,7 @@ import { UserService } from "@/services/user.service"; const userService = new UserService(); export default function ProfileNotificationPage() { + const { t } = useTranslation(); // fetching user email notification settings const { data, isLoading } = useSWR("CURRENT_USER_EMAIL_NOTIFICATION_SETTINGS", () => userService.currentUserEmailNotificationSettings() @@ -23,11 +25,11 @@ export default function ProfileNotificationPage() { return ( <> - + diff --git a/web/app/profile/page.tsx b/web/app/profile/page.tsx index ea02ab56bc4..22a0d4ba142 100644 --- a/web/app/profile/page.tsx +++ b/web/app/profile/page.tsx @@ -1,129 +1,18 @@ "use client"; -import React, { useEffect, useState } from "react"; import { observer } from "mobx-react"; -import { Controller, useForm } from "react-hook-form"; -import { ChevronDown, CircleUserRound } from "lucide-react"; -import { Disclosure, Transition } from "@headlessui/react"; -import type { IUser, TUserProfile } from "@plane/types"; -import { - Button, - CustomSelect, - CustomSearchSelect, - Input, - TOAST_TYPE, - setPromiseToast, - setToast, - Tooltip, -} from "@plane/ui"; +import { useTranslation } from "@plane/i18n"; // components -import { DeactivateAccountModal } from "@/components/account"; import { LogoSpinner } from "@/components/common"; -import { ImagePickerPopover, UserImageUploadModal, PageHead } from "@/components/core"; -import { TimezoneSelect } from "@/components/global"; -import { ProfileSettingContentWrapper } from "@/components/profile"; -// constants -import { USER_ROLES } from "@/constants/workspace"; -// helpers -import { getFileURL } from "@/helpers/file.helper"; +import { PageHead } from "@/components/core"; +import { ProfileSettingContentWrapper, ProfileForm } from "@/components/profile"; // hooks -import { useUser, useUserProfile } from "@/hooks/store"; - -const defaultValues: Partial = { - avatar_url: "", - cover_image_url: "", - first_name: "", - last_name: "", - display_name: "", - email: "", - role: "Product / Project Manager", - user_timezone: "Asia/Kolkata", -}; +import { useUser } from "@/hooks/store"; const ProfileSettingsPage = observer(() => { - // states - const [isLoading, setIsLoading] = useState(false); - const [isImageUploadModalOpen, setIsImageUploadModalOpen] = useState(false); - const [deactivateAccountModal, setDeactivateAccountModal] = useState(false); - // form info - const { - handleSubmit, - reset, - watch, - control, - setValue, - formState: { errors }, - } = useForm({ defaultValues }); - // derived values - const userAvatar = watch("avatar_url"); - const userCover = watch("cover_image_url"); + const { t } = useTranslation(); // store hooks - const { data: currentUser, updateCurrentUser } = useUser(); - const { updateUserProfile, data: currentUserProfile } = useUserProfile(); - - useEffect(() => { - reset({ ...defaultValues, ...currentUser, ...currentUserProfile }); - }, [currentUser, currentUserProfile, reset]); - - const onSubmit = async (formData: IUser) => { - setIsLoading(true); - const userPayload: Partial = { - first_name: formData.first_name, - last_name: formData.last_name, - avatar_url: formData.avatar_url, - display_name: formData?.display_name, - user_timezone: formData.user_timezone, - }; - const userProfilePayload: Partial = { - role: formData.role ?? undefined, - }; - // if unsplash or a pre-defined image is uploaded, delete the old uploaded asset - if (formData.cover_image_url?.startsWith("http")) { - userPayload.cover_image = formData.cover_image_url; - userPayload.cover_image_asset = null; - } - - const updateUser = Promise.all([updateCurrentUser(userPayload), updateUserProfile(userProfilePayload)]).finally( - () => setIsLoading(false) - ); - setPromiseToast(updateUser, { - loading: "Updating...", - success: { - title: "Success!", - message: () => `Profile updated successfully.`, - }, - error: { - title: "Error!", - message: () => `There was some error in updating your profile. Please try again.`, - }, - }); - }; - - const handleDelete = async (url: string | null | undefined) => { - if (!url) return; - - await updateCurrentUser({ - avatar_url: "", - }) - .then(() => { - setToast({ - type: TOAST_TYPE.SUCCESS, - title: "Success!", - message: "Profile picture deleted successfully.", - }); - setValue("avatar_url", ""); - }) - .catch(() => { - setToast({ - type: TOAST_TYPE.ERROR, - title: "Error!", - message: "There was some error in deleting your profile picture. Please try again.", - }); - }) - .finally(() => { - setIsImageUploadModalOpen(false); - }); - }; + const { data: currentUser, userProfile } = useUser(); if (!currentUser) return ( @@ -134,307 +23,9 @@ const ProfileSettingsPage = observer(() => { return ( <> - + - ( - setIsImageUploadModalOpen(false)} - handleRemove={async () => await handleDelete(currentUser?.avatar_url)} - onSuccess={(url) => { - onChange(url); - handleSubmit(onSubmit)(); - setIsImageUploadModalOpen(false); - }} - value={value && value.trim() !== "" ? value : null} - /> - )} - /> - setDeactivateAccountModal(false)} /> -
-
-
- {currentUser?.first_name -
-
-
- -
-
-
-
- ( - onChange(imageUrl)} - control={control} - value={value ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"} - isProfileCover - /> - )} - /> -
-
-
-
-
- {`${watch("first_name")} ${watch("last_name")}`} -
- {watch("email")} -
-
-
-
-
-

- First name* -

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

Last name

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

- Display name* -

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

- Email* -

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

- Role* -

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

- Timezone* -

- ( - { - onChange(value); - }} - error={Boolean(errors.user_timezone)} - /> - )} - /> - {errors.user_timezone && {errors.user_timezone.message}} -
- -
-

Language

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

Current password

+

{t("current_password")}

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

New password

+

{t("new_password")}

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

Confirm password

+

{t("confirm_password")}

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

Profile settings

+

{t("profile_settings")}

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

{link.label}

} + {!sidebarCollapsed &&

{t(link.key)}

}
@@ -156,7 +157,7 @@ export const ProfileLayoutSidebar = observer(() => {
{!sidebarCollapsed && ( -
Workspaces
+
{t("workspaces")}
)} {workspacesList && workspacesList.length > 0 && (
{ {WORKSPACE_ACTION_LINKS.map((link) => ( { }`} > {} - {!sidebarCollapsed && link.label} + {!sidebarCollapsed && t(link.key)}
@@ -238,7 +239,7 @@ export const ProfileLayoutSidebar = observer(() => { disabled={isSigningOut} > - {!sidebarCollapsed && {isSigningOut ? "Signing out..." : "Sign out"}} + {!sidebarCollapsed && {isSigningOut ? `${t("signing_out")}...` : t("sign_out")}} diff --git a/web/ce/constants/dashboard.ts b/web/ce/constants/dashboard.ts index 0df2719a772..e2567d5804a 100644 --- a/web/ce/constants/dashboard.ts +++ b/web/ce/constants/dashboard.ts @@ -13,8 +13,9 @@ import { EUserPermissions } from "@/plane-web/constants/user-permissions"; import { TSidebarUserMenuItemKeys, TSidebarWorkspaceMenuItemKeys } from "@/plane-web/types/dashboard"; export type TSidebarMenuItems = { - key: T; + value: T; label: string; + key: string; href: string; access: EUserPermissions[]; highlight: (pathname: string, baseUrl: string, options?: TLinkOptions) => boolean; @@ -25,16 +26,18 @@ export type TSidebarUserMenuItems = TSidebarMenuItems; export const SIDEBAR_USER_MENU_ITEMS: TSidebarUserMenuItems[] = [ { - key: "home", + value: "home", label: "Home", + key: "home", href: ``, access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/`, Icon: Home, }, { - key: "your-work", + value: "your-work", label: "Your work", + key: "your_work", href: "/profile", access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER], highlight: (pathname: string, baseUrl: string, options?: TLinkOptions) => @@ -42,16 +45,18 @@ export const SIDEBAR_USER_MENU_ITEMS: TSidebarUserMenuItems[] = [ Icon: UserActivityIcon, }, { - key: "notifications", + value: "notifications", label: "Inbox", + key: "notifications", href: `/notifications`, access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/notifications/`), Icon: Inbox, }, { - key: "drafts", + value: "drafts", label: "Drafts", + key: "drafts", href: `/drafts`, access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER], highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/drafts/`), @@ -63,6 +68,7 @@ export type TSidebarWorkspaceMenuItems = TSidebarMenuItems> = { projects: { + value: "projects", key: "projects", label: "Projects", href: `/projects`, @@ -71,7 +77,8 @@ export const SIDEBAR_WORKSPACE_MENU: Partial = (props) => { const router = useAppRouter(); const { isOpen, onClose } = props; // hooks + const { t } = useTranslation(); const { deactivateAccount, signOut } = useUser(); // states @@ -90,11 +92,10 @@ export const DeactivateAccountModal: React.FC = (props) => {
- Deactivate your account? + {t("deactivate_your_account")}

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

@@ -102,10 +103,10 @@ export const DeactivateAccountModal: React.FC = (props) => {
diff --git a/web/core/components/account/password-strength-meter.tsx b/web/core/components/account/password-strength-meter.tsx index 342f77efb70..358320eb518 100644 --- a/web/core/components/account/password-strength-meter.tsx +++ b/web/core/components/account/password-strength-meter.tsx @@ -1,6 +1,7 @@ "use client"; import { FC, useMemo } from "react"; +import { useTranslation } from "@plane/i18n"; // import { CircleCheck } from "lucide-react"; // helpers import { cn } from "@/helpers/common.helper"; @@ -17,6 +18,7 @@ type TPasswordStrengthMeter = { export const PasswordStrengthMeter: FC = (props) => { const { password, isFocused = false } = props; + const { t } = useTranslation(); // derived values const strength = useMemo(() => getPasswordStrength(password), [password]); const strengthBars = useMemo(() => { @@ -24,40 +26,40 @@ export const PasswordStrengthMeter: FC = (props) => { case E_PASSWORD_STRENGTH.EMPTY: { return { bars: [`bg-custom-text-100`, `bg-custom-text-100`, `bg-custom-text-100`], - text: "Please enter your password.", + text: t("please_enter_your_password"), textColor: "text-custom-text-100", }; } case E_PASSWORD_STRENGTH.LENGTH_NOT_VALID: { return { bars: [`bg-red-500`, `bg-custom-text-100`, `bg-custom-text-100`], - text: "Password length should me more than 8 characters.", + text: t("password_length_should_me_more_than_8_characters"), textColor: "text-red-500", }; } case E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID: { return { bars: [`bg-red-500`, `bg-custom-text-100`, `bg-custom-text-100`], - text: "Password is weak.", + text: t("password_is_weak"), textColor: "text-red-500", }; } case E_PASSWORD_STRENGTH.STRENGTH_VALID: { return { bars: [`bg-green-500`, `bg-green-500`, `bg-green-500`], - text: "Password is strong.", + text: t("password_is_strong"), textColor: "text-green-500", }; } default: { return { bars: [`bg-custom-text-100`, `bg-custom-text-100`, `bg-custom-text-100`], - text: "Please enter your password.", + text: t("please_enter_your_password"), textColor: "text-custom-text-100", }; } } - }, [strength]); + }, [strength,t]); const isPasswordMeterVisible = isFocused ? true : strength === E_PASSWORD_STRENGTH.STRENGTH_VALID ? false : true; diff --git a/web/core/components/core/theme/custom-theme-selector.tsx b/web/core/components/core/theme/custom-theme-selector.tsx index da903439307..d66fbe82f83 100644 --- a/web/core/components/core/theme/custom-theme-selector.tsx +++ b/web/core/components/core/theme/custom-theme-selector.tsx @@ -1,29 +1,16 @@ "use client"; +import { useMemo } from "react"; import { observer } from "mobx-react"; import { Controller, useForm } from "react-hook-form"; // types +import { useTranslation } from "@plane/i18n"; import { IUserTheme } from "@plane/types"; // ui import { Button, InputColorPicker, setPromiseToast } from "@plane/ui"; // hooks import { useUserProfile } from "@/hooks/store"; -const inputRules = { - minLength: { - value: 7, - message: "Enter a valid hex code of 6 characters", - }, - maxLength: { - value: 7, - message: "Enter a valid hex code of 6 characters", - }, - pattern: { - value: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/, - message: "Enter a valid hex code of 6 characters", - }, -}; - type TCustomThemeSelector = { applyThemeChange: (theme: Partial) => void; }; @@ -32,7 +19,7 @@ export const CustomThemeSelector: React.FC = observer((pro const { applyThemeChange } = props; // hooks const { data: userProfile, updateUserTheme } = useUserProfile(); - + const { t } = useTranslation(); const { control, formState: { errors, isSubmitting }, @@ -51,6 +38,24 @@ export const CustomThemeSelector: React.FC = observer((pro }, }); + const inputRules = useMemo( + () => ({ + minLength: { + value: 7, + message: t("enter_a_valid_hex_code_of_6_characters"), + }, + maxLength: { + value: 7, + message: t("enter_a_valid_hex_code_of_6_characters"), + }, + pattern: { + value: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/, + message: t("enter_a_valid_hex_code_of_6_characters"), + }, + }), + [t] // Empty dependency array since these rules never change + ); + const handleUpdateTheme = async (formData: Partial) => { const payload: IUserTheme = { background: formData.background, @@ -66,14 +71,14 @@ export const CustomThemeSelector: React.FC = observer((pro const updateCurrentUserThemePromise = updateUserTheme(payload); setPromiseToast(updateCurrentUserThemePromise, { - loading: "Updating theme...", + loading: t("updating_theme"), success: { - title: "Success!", - message: () => "Theme updated successfully!", + title: t("success"), + message: () => t("theme_updated_successfully"), }, error: { - title: "Error!", - message: () => "Failed to Update the theme", + title: t("error"), + message: () => t("failed_to_update_the_theme"), }, }); @@ -91,16 +96,16 @@ export const CustomThemeSelector: React.FC = observer((pro return (
-

Customize your theme

+

{t("customize_your_theme")}

-

Background color

+

{t("background_color")}

( = observer((pro
-

Text color

+

{t("text_color")}

( = observer((pro
-

Primary(Theme) color

+

{t("primary_color")}

( = observer((pro
-

Sidebar background color

+

{t("sidebar_background_color")}

( = observer((pro
-

Sidebar text color

+

{t("sidebar_text_color")}

( = observer((pro
diff --git a/web/core/components/core/theme/theme-switch.tsx b/web/core/components/core/theme/theme-switch.tsx index b79e2104eb2..7a188a48aa0 100644 --- a/web/core/components/core/theme/theme-switch.tsx +++ b/web/core/components/core/theme/theme-switch.tsx @@ -1,6 +1,7 @@ "use client"; import { FC } from "react"; +import { useTranslation } from "@plane/i18n"; // constants import { CustomSelect } from "@plane/ui"; import { THEME_OPTIONS, I_THEME_OPTION } from "@/constants/themes"; @@ -13,7 +14,7 @@ type Props = { export const ThemeSwitch: FC = (props) => { const { value, onChange } = props; - + const { t } = useTranslation(); return ( = (props) => { }} />
- {value.label} + {t(value.key)}
) : ( - "Select your theme" + t("select_your_theme") ) } onChange={onChange} @@ -72,7 +73,7 @@ export const ThemeSwitch: FC = (props) => { }} />
- {themeOption.label} + {t(themeOption.key)}
))} diff --git a/web/core/components/dropdowns/member/index.tsx b/web/core/components/dropdowns/member/index.tsx index f905bc27b2a..914bc8e9547 100644 --- a/web/core/components/dropdowns/member/index.tsx +++ b/web/core/components/dropdowns/member/index.tsx @@ -1,6 +1,7 @@ import { useRef, useState } from "react"; import { observer } from "mobx-react"; import { ChevronDown, LucideIcon } from "lucide-react"; +import { useTranslation } from "@plane/i18n"; // ui import { ComboDropDown } from "@plane/ui"; // helpers @@ -26,6 +27,7 @@ type Props = { } & MemberDropdownProps; export const MemberDropdown: React.FC = observer((props) => { + const { t } = useTranslation(); const { button, buttonClassName, @@ -40,7 +42,7 @@ export const MemberDropdown: React.FC = observer((props) => { multiple, onChange, onClose, - placeholder = "Members", + placeholder = t("members"), tooltipContent, placement, projectId, @@ -86,7 +88,7 @@ export const MemberDropdown: React.FC = observer((props) => { if (value.length === 1) { return getUserDetails(value[0])?.display_name || placeholder; } else { - return showUserDetails ? `${value.length} members` : ""; + return showUserDetails ? `${value.length} ${t("members").toLocaleLowerCase()}` : ""; } } else { return placeholder; @@ -131,7 +133,9 @@ export const MemberDropdown: React.FC = observer((props) => { className={cn("text-xs", buttonClassName)} isActive={isOpen} tooltipHeading={placeholder} - tooltipContent={tooltipContent ?? `${value?.length ?? 0} assignee${value?.length !== 1 ? "s" : ""}`} + tooltipContent={ + tooltipContent ?? `${value?.length ?? 0} ${value?.length !== 1 ? t("assignees") : t("assignee")}` + } showTooltip={showTooltip} variant={buttonVariant} renderToolTipByDefault={renderByDefault} diff --git a/web/core/components/dropdowns/member/member-options.tsx b/web/core/components/dropdowns/member/member-options.tsx index a2283fa4bc2..81cf1945609 100644 --- a/web/core/components/dropdowns/member/member-options.tsx +++ b/web/core/components/dropdowns/member/member-options.tsx @@ -8,6 +8,7 @@ import { createPortal } from "react-dom"; import { usePopper } from "react-popper"; import { Check, Search } from "lucide-react"; import { Combobox } from "@headlessui/react"; +import { useTranslation } from "@plane/i18n"; // plane ui import { Avatar } from "@plane/ui"; // helpers @@ -34,6 +35,7 @@ export const MemberOptions: React.FC = observer((props: Props) => { // refs const inputRef = useRef(null); // store hooks + const { t } = useTranslation(); const { workspaceSlug } = useParams(); const { getUserDetails, @@ -85,7 +87,7 @@ export const MemberOptions: React.FC = observer((props: Props) => { content: (
- {currentUser?.id === userId ? "You" : userDetails?.display_name} + {currentUser?.id === userId ? t("you") : userDetails?.display_name}
), }; @@ -115,7 +117,7 @@ export const MemberOptions: React.FC = observer((props: Props) => { className="w-full bg-transparent py-1 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none" value={query} onChange={(e) => setQuery(e.target.value)} - placeholder="Search" + placeholder={t("search")} displayValue={(assigned: any) => assigned?.name} onKeyDown={searchInputKeyDown} /> @@ -142,10 +144,10 @@ export const MemberOptions: React.FC = observer((props: Props) => { )) ) : ( -

No matching results

+

{t("no_matching_results")}

) ) : ( -

Loading...

+

{t("loading")}

)}
diff --git a/web/core/components/dropdowns/priority.tsx b/web/core/components/dropdowns/priority.tsx index ee11679156c..acf6dc26b12 100644 --- a/web/core/components/dropdowns/priority.tsx +++ b/web/core/components/dropdowns/priority.tsx @@ -5,6 +5,7 @@ import { useTheme } from "next-themes"; import { usePopper } from "react-popper"; import { Check, ChevronDown, Search, SignalHigh } from "lucide-react"; import { Combobox } from "@headlessui/react"; +import { useTranslation } from "@plane/i18n"; // types import { TIssuePriorities } from "@plane/types"; // ui @@ -71,11 +72,12 @@ const BorderButton = (props: ButtonProps) => { }; const { isMobile } = usePlatformOS(); + const { t } = useTranslation(); return ( { ) : ( ))} - {!hideText && {priorityDetails?.title ?? placeholder}} + {!hideText && {t(priorityDetails?.key ?? "priority") ?? placeholder}} {dropdownArrow && (
diff --git a/web/core/components/dropdowns/project.tsx b/web/core/components/dropdowns/project.tsx index 052527ab6dd..88c13bdb91e 100644 --- a/web/core/components/dropdowns/project.tsx +++ b/web/core/components/dropdowns/project.tsx @@ -4,6 +4,7 @@ import { usePopper } from "react-popper"; import { Briefcase, Check, ChevronDown, Search } from "lucide-react"; import { Combobox } from "@headlessui/react"; // ui +import { useTranslation } from "@plane/i18n"; import { ComboDropDown } from "@plane/ui"; // components import { Logo } from "@/components/common"; @@ -86,7 +87,7 @@ export const ProjectDropdown: React.FC = observer((props) => { }); // store hooks const { joinedProjectIds, getProjectById } = useProject(); - + const { t } = useTranslation(); const options = joinedProjectIds?.map((projectId) => { const projectDetails = getProjectById(projectId); if (renderCondition && projectDetails && !renderCondition(projectDetails)) return; @@ -238,7 +239,7 @@ export const ProjectDropdown: React.FC = observer((props) => { className="w-full bg-transparent py-1 text-xs text-custom-text-200 placeholder:text-custom-text-400 focus:outline-none" value={query} onChange={(e) => setQuery(e.target.value)} - placeholder="Search" + placeholder={t("search")} displayValue={(assigned: any) => assigned?.name} onKeyDown={searchInputKeyDown} /> @@ -268,10 +269,10 @@ export const ProjectDropdown: React.FC = observer((props) => { ); }) ) : ( -

No matching results

+

{t("no_matching_results")}

) ) : ( -

Loading...

+

{t("loading")}

)}
diff --git a/web/core/components/dropdowns/state.tsx b/web/core/components/dropdowns/state.tsx index 86d8d80521c..59178c14968 100644 --- a/web/core/components/dropdowns/state.tsx +++ b/web/core/components/dropdowns/state.tsx @@ -6,6 +6,7 @@ import { useParams } from "next/navigation"; import { usePopper } from "react-popper"; import { ChevronDown, Search } from "lucide-react"; import { Combobox } from "@headlessui/react"; +import { useTranslation } from "@plane/i18n"; // ui import { ComboDropDown, Spinner, StateGroupIcon } from "@plane/ui"; // helpers @@ -82,6 +83,7 @@ export const StateDropdown: React.FC = observer((props) => { ], }); // store hooks + const { t } = useTranslation(); const { workspaceSlug } = useParams(); const { fetchProjectStates, getProjectStates, getStateById } = useProjectState(); const statesList = stateIds @@ -160,8 +162,8 @@ export const StateDropdown: React.FC = observer((props) => { = observer((props) => { /> )} {BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && ( - {selectedState?.name ?? "State"} + {selectedState?.name ?? t("state")} )} {dropdownArrow && (
diff --git a/web/core/components/global/product-updates/footer.tsx b/web/core/components/global/product-updates/footer.tsx index 6dd2638332b..5d402e85eef 100644 --- a/web/core/components/global/product-updates/footer.tsx +++ b/web/core/components/global/product-updates/footer.tsx @@ -1,4 +1,5 @@ import Image from "next/image"; +import { useTranslation } from "@plane/i18n"; // ui import { getButtonStyling } from "@plane/ui"; // helpers @@ -6,38 +7,40 @@ import { cn } from "@/helpers/common.helper"; // assets import PlaneLogo from "@/public/plane-logos/blue-without-text.png"; -export const ProductUpdatesFooter = () => ( -
-
- { + const { t } = useTranslation(); + return ( +
+ -); + Plane + {t("powered_by_plane_pages")} + +
+ ); +}; diff --git a/web/core/components/global/product-updates/modal.tsx b/web/core/components/global/product-updates/modal.tsx index 4288d68ee35..c2700e18143 100644 --- a/web/core/components/global/product-updates/modal.tsx +++ b/web/core/components/global/product-updates/modal.tsx @@ -1,5 +1,6 @@ import { FC } from "react"; import { observer } from "mobx-react-lite"; +import { useTranslation } from "@plane/i18n"; // ui import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui"; // components @@ -16,7 +17,7 @@ export type ProductUpdatesModalProps = { export const ProductUpdatesModal: FC = observer((props) => { const { isOpen, handleClose } = props; - + const { t } = useTranslation(); const { config } = useInstance(); return ( @@ -27,17 +28,17 @@ export const ProductUpdatesModal: FC = observer((props