From 4946758d423dfa93bb750c8396d97ced5bac31ef Mon Sep 17 00:00:00 2001 From: Lewis Carhart Date: Tue, 18 Mar 2025 19:06:30 +0000 Subject: [PATCH 1/2] refactor: update organization setup logic and remove subdomain field - Modified the `create-organization-action` to include framework handling and organization setup. - Introduced a new task `create-organization` for better organization creation management. - Removed the `subdomain` field from the `Organization` model and added a `setup` boolean field. - Updated environment variables in `env.mjs` to include `REVALIDATION_SECRET`. - Enhanced the onboarding process to check organization setup status. - Refactored various components and actions to improve type safety and organization management. --- apps/app/package.json | 3 +- .../check-subdomain-availability.ts | 40 - .../create-organization-action.ts | 174 +- apps/app/src/actions/schema.ts | 10 +- .../app/[locale]/(app)/(dashboard)/layout.tsx | 23 +- .../(app)/(dashboard)/settings/page.tsx | 37 +- .../app/src/app/[locale]/(app)/setup/page.tsx | 44 +- apps/app/src/app/api/revalidate/path/route.ts | 30 + apps/app/src/auth/org.ts | 4 +- .../forms/create-organization-form.tsx | 506 ++-- .../components/settings/team/team-members.tsx | 66 +- apps/app/src/env.mjs | 120 +- .../organization/create-default-policies.ts | 166 -- .../tasks/organization/create-organization.ts | 387 +++ .../tasks/organization/new-organization.ts | 22 - .../organization/setup-org-frameworks.ts | 342 +++ apps/app/src/locales/en.ts | 2192 +++++++------- apps/app/src/locales/fr.ts | 2653 +++++++++-------- apps/app/src/types/actions.ts | 5 + .../migration.sql | 12 + packages/db/prisma/schema/schema.prisma | 2 +- 21 files changed, 3666 insertions(+), 3172 deletions(-) delete mode 100644 apps/app/src/actions/organization/check-subdomain-availability.ts create mode 100644 apps/app/src/app/api/revalidate/path/route.ts delete mode 100644 apps/app/src/jobs/tasks/organization/create-default-policies.ts create mode 100644 apps/app/src/jobs/tasks/organization/create-organization.ts delete mode 100644 apps/app/src/jobs/tasks/organization/new-organization.ts create mode 100644 apps/app/src/jobs/tasks/organization/setup-org-frameworks.ts create mode 100644 apps/app/src/types/actions.ts create mode 100644 packages/db/prisma/migrations/20250318161548_drop_subdomain_add_setup/migration.sql diff --git a/apps/app/package.json b/apps/app/package.json index 51fcfa5c5a..ff597e779b 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -3,7 +3,8 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "NODE_OPTIONS='--inspect' next dev --turbopack --turbo -p 3001", + "dev": "npx concurrently --kill-others --names \"next,trigger\" --prefix-colors \"yellow,blue\" \"next dev --turbopack --turbo -p 3001\" \"npm run trigger:dev\"", + "trigger:dev": "npx trigger.dev@latest dev", "build": "next build", "start": "next start", "lint": "biome lint ./src", diff --git a/apps/app/src/actions/organization/check-subdomain-availability.ts b/apps/app/src/actions/organization/check-subdomain-availability.ts deleted file mode 100644 index 8672e51859..0000000000 --- a/apps/app/src/actions/organization/check-subdomain-availability.ts +++ /dev/null @@ -1,40 +0,0 @@ -// check-subdomain-availability.ts - -"use server"; - -import { db } from "@bubba/db"; -import { authActionClient } from "../safe-action"; -import { subdomainAvailabilitySchema } from "../schema"; -import type { ActionResponse } from "../types"; - -export const checkSubdomainAvailability = authActionClient - .schema(subdomainAvailabilitySchema) - .metadata({ - name: "check-subdomain-availability", - }) - .action(async ({ parsedInput }): Promise => { - const { subdomain } = parsedInput; - - try { - const subdomainExists = await db.organization.findFirst({ - where: { - subdomain: { - equals: subdomain, - mode: "insensitive", - }, - }, - select: { id: true }, - }); - - return { - success: true, - data: !subdomainExists, - }; - } catch (error) { - console.error("Prisma error:", error); - return { - success: false, - error: "Failed to check subdomain availability", - }; - } - }); diff --git a/apps/app/src/actions/organization/create-organization-action.ts b/apps/app/src/actions/organization/create-organization-action.ts index 2a37ceaf3d..21432c933f 100644 --- a/apps/app/src/actions/organization/create-organization-action.ts +++ b/apps/app/src/actions/organization/create-organization-action.ts @@ -3,133 +3,71 @@ "use server"; import { createOrganizationAndConnectUser } from "@/auth/org"; -import type { createDefaultPoliciesTask } from "@/jobs/tasks/organization/create-default-policies"; -import { - addDomainToVercel, - removeDomainFromVercelProject, -} from "@/lib/domains"; import { db } from "@bubba/db"; -import { tasks } from "@trigger.dev/sdk/v3"; -import { revalidateTag } from "next/cache"; import { authActionClient } from "../safe-action"; import { organizationSchema } from "../schema"; +import { tasks } from "@trigger.dev/sdk/v3"; +import type { createOrganizationTask } from "@/jobs/tasks/organization/create-organization"; export const createOrganizationAction = authActionClient - .schema(organizationSchema) - .metadata({ - name: "create-organization", - track: { - event: "create-organization", - channel: "server", - }, - }) - .action(async ({ parsedInput, ctx }) => { - const { name, website, subdomain } = parsedInput; - const { id: userId, organizationId } = ctx.user; - - if (!name || !website) { - console.log("Invalid input detected:", { name, website }); - throw new Error("Invalid user input"); - } - - const hasVercelConfig = Boolean( - process.env.NEXT_PUBLIC_VERCEL_URL && - process.env.VERCEL_ACCESS_TOKEN && - process.env.VERCEL_TEAM_ID && - process.env.VERCEL_PROJECT_ID, - ); - - if (hasVercelConfig && subdomain) { - try { - await addDomainToVercel( - `${subdomain}.${process.env.NEXT_PUBLIC_VERCEL_URL}`, - ); - } catch (error) { - console.error("Failed to add domain to Vercel:", error); - throw new Error("Failed to set up subdomain"); - } - } - - if (!organizationId) { - await createOrganizationAndConnectUser({ - userId, - normalizedEmail: ctx.user.email!, - subdomain: hasVercelConfig ? subdomain || "" : "", - }); - } - - const organization = await db.organization.findFirst({ - where: { - users: { - some: { - id: userId, - }, - }, - }, - }); + .schema(organizationSchema) + .metadata({ + name: "create-organization", + track: { + event: "create-organization", + channel: "server", + }, + }) + .action(async ({ parsedInput, ctx }) => { + const { name, website, frameworks } = parsedInput; + const { id: userId, organizationId } = ctx.user; - if (!organization) { - throw new Error("Organization not found"); - } + if (!name || !website) { + console.log("Invalid input detected:", { name, website }); + throw new Error("Invalid user input"); + } - try { - await db.$transaction(async () => { - await db.organization.upsert({ - where: { - id: organization.id, - }, - update: { - name, - website, - subdomain: hasVercelConfig ? subdomain || "" : "", - }, - create: { - name, - website, - subdomain: hasVercelConfig ? subdomain || "" : "", - }, - }); + if (!organizationId) { + await createOrganizationAndConnectUser({ + userId, + normalizedEmail: ctx.user.email!, + }); + } - await db.user.update({ - where: { - id: userId, - }, - data: { - onboarded: true, - }, - }); - }); + const organization = await db.organization.findFirst({ + where: { + users: { + some: { + id: userId, + }, + }, + }, + }); - // await tasks.trigger( - // "create-default-policies", - // { - // ownerId: userId, - // organizationId: organization.id, - // organizationName: name, - // } - // ); + if (!organization) { + throw new Error("Organization not found"); + } - revalidateTag(`user_${userId}`); - revalidateTag(`organization_${organizationId}`); + try { + const handle = await tasks.trigger( + "create-organization", + { + userId, + fullName: name, + website, + frameworkIds: frameworks, + organizationId: organization.id, + }, + ); - return { - success: true, - }; - } catch (error) { - if (hasVercelConfig && subdomain) { - try { - await removeDomainFromVercelProject( - `${subdomain}.${process.env.NEXT_PUBLIC_VERCEL_URL}`, - ); - } catch (cleanupError) { - console.error( - "Failed to clean up subdomain after error:", - cleanupError, - ); - } - } + return { + success: true, + runId: handle.id, + publicAccessToken: handle.publicAccessToken, + }; + } catch (error) { + console.error("Error during organization update:", error); - console.error("Error during organization update:", error); - throw new Error("Failed to update organization"); - } - }); + throw new Error("Failed to update organization"); + } + }); diff --git a/apps/app/src/actions/schema.ts b/apps/app/src/actions/schema.ts index 62b7f5fb2d..788d53cc7c 100644 --- a/apps/app/src/actions/schema.ts +++ b/apps/app/src/actions/schema.ts @@ -11,12 +11,16 @@ import { import { z } from "zod"; export const organizationSchema = z.object({ - fullName: z.string().min(1, "Full name is required"), name: z.string().min(1, "Name is required"), - website: z.string().url("Must be a valid URL"), - subdomain: z.string().min(1, "Subdomain is required").optional(), + fullName: z.string().min(1, "Full name is required"), + website: z.string().url("Must be a valid URL").optional().or(z.literal("")), + frameworks: z + .array(z.string()) + .min(1, "Please select at least one framework to get started with"), }); +export type OrganizationSchema = z.infer; + export const organizationNameSchema = z.object({ name: z .string() diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/layout.tsx index 32a1e55265..c5be7799bc 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/layout.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/layout.tsx @@ -1,8 +1,10 @@ import { auth } from "@/auth"; import { Header } from "@/components/header"; import { Sidebar } from "@/components/sidebar"; +import { db } from "@bubba/db"; import dynamic from "next/dynamic"; import { redirect } from "next/navigation"; +import { cache } from "react"; const HotKeys = dynamic( () => import("@/components/hot-keys").then((mod) => mod.HotKeys), @@ -18,10 +20,16 @@ export default async function Layout({ }) { const session = await auth(); - if (!session?.user) { + if (!session?.user || !session.user.organizationId) { redirect("/auth"); } + const isSetup = await isOrganizationSetup(session.user.organizationId); + + if (!isSetup) { + redirect("/setup"); + } + return (
@@ -35,3 +43,16 @@ export default async function Layout({
); } + +const isOrganizationSetup = cache(async (organizationId: string) => { + const organization = await db.organization.findUnique({ + where: { + id: organizationId, + }, + select: { + setup: true, + }, + }); + + return organization?.setup; +}); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/settings/page.tsx index 63836d9aa2..a618b73fe0 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/settings/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/settings/page.tsx @@ -7,6 +7,7 @@ import { db } from "@bubba/db"; import type { Metadata } from "next"; import { setStaticParamsLocale } from "next-international/server"; import { redirect } from "next/navigation"; +import { cache } from "react"; export default async function OrganizationSettings({ params, @@ -18,28 +19,17 @@ export default async function OrganizationSettings({ const session = await auth(); - const [organization] = await Promise.all([ - db.organization.findUnique({ - where: { - id: session?.user.organizationId, - }, - select: { - name: true, - website: true, - id: true, - }, - }), - ]); - - if (!organization) { + if (!session?.user.organizationId) { return redirect("/"); } + const organization = await organizationDetails(session.user.organizationId); + return (
- - - + + +
); } @@ -57,3 +47,16 @@ export async function generateMetadata({ title: t("sidebar.settings"), }; } + +const organizationDetails = cache(async (organizationId: string) => { + const organization = await db.organization.findUnique({ + where: { id: organizationId }, + select: { + name: true, + website: true, + id: true, + }, + }); + + return organization; +}); diff --git a/apps/app/src/app/[locale]/(app)/setup/page.tsx b/apps/app/src/app/[locale]/(app)/setup/page.tsx index f458766d8f..9397d81300 100644 --- a/apps/app/src/app/[locale]/(app)/setup/page.tsx +++ b/apps/app/src/app/[locale]/(app)/setup/page.tsx @@ -1,9 +1,9 @@ import { auth } from "@/auth"; import { Onboarding } from "@/components/forms/create-organization-form"; -import { Icons } from "@bubba/ui/icons"; +import { db } from "@bubba/db"; import type { Metadata } from "next"; -import Link from "next/link"; import { redirect } from "next/navigation"; +import { cache } from "react"; export const metadata: Metadata = { title: "Organization Setup | Comp AI", @@ -16,19 +16,35 @@ export default async function Page() { return redirect("/"); } - if (session.user.onboarded && session.user.organizationId) { + if (!session.user.organizationId) { return redirect("/"); } - return ( -
-
- - - -
- - -
- ); + const isSetup = await isOrganizationSetup(session.user.organizationId); + const frameworks = await getFrameworks(); + + if (isSetup) { + return redirect("/"); + } + + return ; } + +const getFrameworks = cache(async () => { + return await db.framework.findMany({ + orderBy: { + name: "asc", + }, + }); +}); + +const isOrganizationSetup = cache(async (organizationId: string) => { + const organization = await db.organization.findUnique({ + where: { id: organizationId }, + select: { + setup: true, + }, + }); + + return organization?.setup; +}); diff --git a/apps/app/src/app/api/revalidate/path/route.ts b/apps/app/src/app/api/revalidate/path/route.ts new file mode 100644 index 0000000000..2b15f9e30c --- /dev/null +++ b/apps/app/src/app/api/revalidate/path/route.ts @@ -0,0 +1,30 @@ +import { type NextRequest, NextResponse } from "next/server"; +import { revalidatePath } from "next/cache"; +import { env } from "@/env.mjs"; + +export async function POST(request: NextRequest) { + try { + const { path, type, secret } = await request.json(); + + if (secret !== env.REVALIDATION_SECRET) { + return NextResponse.json({ message: "Invalid secret" }, { status: 401 }); + } + + if (!path) { + return NextResponse.json( + { message: "Path is required" }, + { status: 400 }, + ); + } + + revalidatePath(path, type); + + return NextResponse.json({ revalidated: true }); + } catch (err) { + console.error("Error revalidating path:", err); + return NextResponse.json( + { message: "Error revalidating path" }, + { status: 500 }, + ); + } +} diff --git a/apps/app/src/auth/org.ts b/apps/app/src/auth/org.ts index 0e08985d4e..8471dc2ffa 100644 --- a/apps/app/src/auth/org.ts +++ b/apps/app/src/auth/org.ts @@ -25,7 +25,6 @@ async function createStripeCustomer(input: { export async function createOrganizationAndConnectUser(input: { userId: string; normalizedEmail: string; - subdomain?: string; }): Promise { const initialName = "New Organization"; @@ -35,11 +34,10 @@ export async function createOrganizationAndConnectUser(input: { name: initialName, tier: "free", website: "", - subdomain: input.subdomain || "", members: { create: { userId: input.userId, - role: "admin", + role: "owner", }, }, }, diff --git a/apps/app/src/components/forms/create-organization-form.tsx b/apps/app/src/components/forms/create-organization-form.tsx index 49f286de67..2ccc26e4fd 100644 --- a/apps/app/src/components/forms/create-organization-form.tsx +++ b/apps/app/src/components/forms/create-organization-form.tsx @@ -1,15 +1,9 @@ "use client"; -import { checkSubdomainAvailability } from "@/actions/organization/check-subdomain-availability"; import { createOrganizationAction } from "@/actions/organization/create-organization-action"; -import { - organizationSchema, - subdomainAvailabilitySchema, -} from "@/actions/schema"; -import { env } from "@/env.mjs"; +import { organizationSchema } from "@/actions/schema"; import { useI18n } from "@/locales/client"; import { Button } from "@bubba/ui/button"; -import { Checkbox } from "@bubba/ui/checkbox"; import { Form, FormControl, @@ -19,155 +13,78 @@ import { FormMessage, } from "@bubba/ui/form"; import { Input } from "@bubba/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@bubba/ui/select"; import { zodResolver } from "@hookform/resolvers/zod"; -import { - ArrowLeftIcon, - ArrowRightIcon, - CheckIcon, - Loader2, - XIcon, -} from "lucide-react"; +import { ArrowRight, Loader2 } from "lucide-react"; import { useSession } from "next-auth/react"; import { useAction } from "next-safe-action/hooks"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; -import { useDebouncedCallback } from "use-debounce"; import type { z } from "zod"; +import type { Framework } from "@bubba/db/types"; +import { Icons } from "@bubba/ui/icons"; +import Link from "next/link"; +import { Checkbox } from "@bubba/ui/checkbox"; +import { cn } from "@bubba/ui/cn"; +import { useRealtimeRun } from "@trigger.dev/react-hooks"; -interface BaseFieldConfig { - name: string; - label: string; - placeholder: string; -} - -interface TextFieldConfig extends BaseFieldConfig { - type?: "text"; -} - -interface CheckboxFieldConfig extends BaseFieldConfig { - type: "checkbox"; - options?: string[]; -} - -interface SelectFieldConfig extends BaseFieldConfig { - type: "select"; - options: string[]; -} - -type FieldConfig = TextFieldConfig | CheckboxFieldConfig | SelectFieldConfig; - -export function Onboarding() { +function RealtimeStatus({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) { const t = useI18n(); - const { data: session } = useSession(); - const [isCheckingName, setIsCheckingName] = useState(false); - const [isNameAvailable, setIsNameAvailable] = useState(null); - - const hasVercelConfig = Boolean(env.NEXT_PUBLIC_VERCEL_URL); - - const steps: Array<{ - title: string; - description: string; - fields: FieldConfig[]; - }> = [ - { - title: t("onboarding.title"), - description: t("onboarding.description"), - fields: [ - { - name: "fullName", - label: t("onboarding.fields.fullName.label"), - placeholder: t("onboarding.fields.fullName.placeholder"), - }, - { - name: "name", - label: t("onboarding.fields.name.label"), - placeholder: t("onboarding.fields.name.placeholder"), - }, - { - name: "website", - label: t("onboarding.fields.website.label"), - placeholder: t("onboarding.fields.website.placeholder"), - }, - ...(hasVercelConfig - ? [ - { - name: "subdomain", - label: t("onboarding.fields.subdomain.label"), - placeholder: t("onboarding.fields.subdomain.placeholder"), - }, - ] - : []), - ], - }, - ]; - - const [currentStep, setCurrentStep] = useState(0); - - const checkAvailability = useAction(checkSubdomainAvailability, { - onSuccess: (result) => { - if (result.data?.data === false) { - setIsCheckingName(false); - setIsNameAvailable(false); - } else { - setIsCheckingName(false); - setIsNameAvailable(true); + const { run, error } = useRealtimeRun(runId, { + accessToken: publicAccessToken, + onComplete: (run, error) => { + if (error) { + toast.error(t("common.actions.error"), { duration: 5000 }); + return; } }, - onError: () => { - setIsCheckingName(false); - setIsNameAvailable(false); - }, }); - const debouncedCheck = useDebouncedCallback(async (subdomain: string) => { - setIsNameAvailable(null); - - const result = subdomainAvailabilitySchema.safeParse({ subdomain }); - - if (!result.success) { - setIsNameAvailable(false); - return; - } - - setIsCheckingName(true); - checkAvailability.execute({ subdomain }); - }, 500); - - const handleNext = async () => { - if (currentStep < steps.length - 1) { - const currentFields = steps[currentStep].fields.map( - (field) => field.name, - ) as Array>; - - const result = await form.trigger(currentFields); + return ( +
+ {run?.status !== "FAILED" && run?.status !== "COMPLETED" && ( +
+ +

+ {t("onboarding.trigger.title")} +

+

+ {t("onboarding.trigger.creating")} +

+
+ )} - if (result) { - setCurrentStep(currentStep + 1); - } - } - }; + {run?.status === "COMPLETED" && ( +
+

+ {t("onboarding.trigger.completed")} +

+
+ +
+
+ )} +
+ ); +} - const handlePrevious = () => { - if (currentStep > 0) { - setCurrentStep(currentStep - 1); - } - }; +function OnboardingClient({ frameworks }: { frameworks: Framework[] }) { + const t = useI18n(); + const { data: session } = useSession(); + const [runId, setRunId] = useState(null); + const [publicAccessToken, setPublicAccessToken] = useState(null); const createOrganization = useAction(createOrganizationAction, { - onSuccess: () => { - toast.success(t("onboarding.success")); + onSuccess: async (data) => { + setRunId(data.data?.runId ?? null); + setPublicAccessToken(data.data?.publicAccessToken ?? null); }, onError: () => { - toast.error(t("common.actions.error")); + toast.error(t("common.actions.error"), { duration: 5000 }); }, }); @@ -177,208 +94,175 @@ export function Onboarding() { fullName: session?.user?.name ?? "", name: "", website: "", - ...(hasVercelConfig ? { subdomain: "" } : {}), + frameworks: [], }, mode: "onChange", }); - const onSubmit = (data: z.infer) => { - createOrganization.execute(data); + const onSubmit = async (data: z.infer) => { + await createOrganization.execute(data); }; + if (runId && publicAccessToken) { + return ( +
+
+
+ + + +
+ +
+
+ ); + } + return (
+
+ + + +
+

{t("onboarding.setup")}

- {steps[currentStep].description} + {t("onboarding.description")}

- {steps[currentStep].fields.map((fieldConfig) => ( - - } - key={fieldConfig.name} - render={({ field }) => ( - - - {fieldConfig.label} - - - {fieldConfig.name === "subdomain" ? ( -
- { - field.onChange(e); - debouncedCheck(e.target.value); - }} - /> - {env.NEXT_PUBLIC_VERCEL_URL && ( -
- .{env.NEXT_PUBLIC_VERCEL_URL} -
- )} -
- ) : (fieldConfig as CheckboxFieldConfig).type === - "checkbox" ? ( -
- {(fieldConfig as CheckboxFieldConfig).options ? ( - (fieldConfig as CheckboxFieldConfig).options?.map( - (option) => ( -
- { - const currentValue = Array.isArray( - field.value, - ) - ? field.value - : []; - const newValue = checked - ? [...currentValue, option] - : currentValue.filter( - (v: string) => v !== option, - ); - field.onChange(newValue); - }} - /> - {option} -
- ), - ) - ) : ( -
- - - {fieldConfig.label} - -
+ ( + + + {t("onboarding.fields.fullName.label")} + + + + + + + )} + /> + + ( + + + {t("onboarding.fields.name.label")} + + + + + + + )} + /> + + ( + + + {t("onboarding.fields.website.label")} + + + + + + + )} + /> + + ( + + + {t("frameworks.overview.grid.title")} + + +
+ {frameworks.map((framework) => ( +
- ) : (fieldConfig as SelectFieldConfig).type === - "select" ? ( - - ) : ( - - )} - - - {fieldConfig.name === "subdomain" && - field.value && - isCheckingName && ( -
- - - {t("onboarding.check_availability")} - -
- )} - {fieldConfig.name === "subdomain" && isNameAvailable && ( -
- - - {t("onboarding.available")} - -
- )} - {fieldConfig.name === "subdomain" && - !form.formState.errors.subdomain && - !isNameAvailable && - !isCheckingName && - field.value && ( -
- - - {t("onboarding.unavailable")} - +
- )} - - )} - /> - ))} + ))} +
+
+ +
+ )} + /> -
- {currentStep > 0 && ( - + -
+ {t("onboarding.submit")} +
); } + +export function Onboarding({ frameworks }: { frameworks: Framework[] }) { + return ; +} diff --git a/apps/app/src/components/settings/team/team-members.tsx b/apps/app/src/components/settings/team/team-members.tsx index fd6ed4c4d8..f06f306ed9 100644 --- a/apps/app/src/components/settings/team/team-members.tsx +++ b/apps/app/src/components/settings/team/team-members.tsx @@ -1,14 +1,12 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@bubba/ui/tabs"; -import { Suspense } from "react"; import { InviteMemberForm } from "./invite-member-form"; import { MembersList } from "./members-list"; import { PendingInvitations } from "./pending-invitations"; import { db } from "@bubba/db"; import { auth } from "@/auth"; import { unstable_cache } from "next/cache"; -import type { Metadata } from "next"; -import { setStaticParamsLocale } from "next-international/server"; import { getI18n } from "@/locales/server"; +import { cache } from "react"; export async function TeamMembers() { const session = await auth(); @@ -53,39 +51,35 @@ export async function TeamMembers() { ); } - -const getOrganizationMembers = unstable_cache( - async (organizationId: string) => { - return db.organizationMember.findMany({ - where: { - organizationId, - accepted: true, - }, - include: { - user: true, - }, - orderBy: { - joinedAt: "desc", - }, - }); - }, - ["organization-members"], - { tags: ["organization-members"], revalidate: 60 } +const getOrganizationMembers = cache(async (organizationId: string) => { + return db.organizationMember.findMany({ + where: { + organizationId, + OR: [ + { accepted: true }, + { invitedEmail: null } + ] + }, + include: { + user: true, + }, + orderBy: { + joinedAt: "desc", + }, + }); +}, ); -const getPendingInvitations = unstable_cache( - async (organizationId: string) => { - return db.organizationMember.findMany({ - where: { - organizationId, - accepted: false, - invitedEmail: { not: null }, - }, - orderBy: { - joinedAt: "desc", - }, - }); - }, - ["pending-invitations"], - { tags: ["pending-invitations"], revalidate: 60 } +const getPendingInvitations = cache(async (organizationId: string) => { + return db.organizationMember.findMany({ + where: { + organizationId, + accepted: false, + invitedEmail: { not: null }, + }, + orderBy: { + joinedAt: "desc", + }, + }); +}, ); \ No newline at end of file diff --git a/apps/app/src/env.mjs b/apps/app/src/env.mjs index afd08c240b..7a8f2848a5 100644 --- a/apps/app/src/env.mjs +++ b/apps/app/src/env.mjs @@ -2,66 +2,68 @@ import { createEnv } from "@t3-oss/env-nextjs"; import { z } from "zod"; export const env = createEnv({ - server: { - AUTH_GOOGLE_ID: z.string(), - AUTH_GOOGLE_SECRET: z.string(), - AUTH_SECRET: z.string(), - DATABASE_URL: z.string().min(1), - OPENAI_API_KEY: z.string(), - RESEND_API_KEY: z.string(), - UPSTASH_REDIS_REST_URL: z.string(), - UPSTASH_REDIS_REST_TOKEN: z.string(), - STRIPE_SECRET_KEY: z.string(), - STRIPE_WEBHOOK_SECRET: z.string(), - DISCORD_WEBHOOK_URL: z.string(), - TRIGGER_SECRET_KEY: z.string(), - TRIGGER_API_KEY: z.string().optional(), - TRIGGER_API_URL: z.string().optional(), - VERCEL_ACCESS_TOKEN: z.string().optional(), - VERCEL_TEAM_ID: z.string().optional(), - VERCEL_PROJECT_ID: z.string().optional(), - NODE_ENV: z.string().optional(), - AWS_ACCESS_KEY_ID: z.string(), - AWS_SECRET_ACCESS_KEY: z.string(), - AWS_REGION: z.string(), - AWS_BUCKET_NAME: z.string(), - }, + server: { + AUTH_GOOGLE_ID: z.string(), + AUTH_GOOGLE_SECRET: z.string(), + AUTH_SECRET: z.string(), + DATABASE_URL: z.string().min(1), + OPENAI_API_KEY: z.string(), + RESEND_API_KEY: z.string(), + UPSTASH_REDIS_REST_URL: z.string(), + UPSTASH_REDIS_REST_TOKEN: z.string(), + STRIPE_SECRET_KEY: z.string(), + STRIPE_WEBHOOK_SECRET: z.string(), + DISCORD_WEBHOOK_URL: z.string(), + TRIGGER_SECRET_KEY: z.string(), + TRIGGER_API_KEY: z.string().optional(), + TRIGGER_API_URL: z.string().optional(), + REVALIDATION_SECRET: z.string(), + VERCEL_ACCESS_TOKEN: z.string().optional(), + VERCEL_TEAM_ID: z.string().optional(), + VERCEL_PROJECT_ID: z.string().optional(), + NODE_ENV: z.string().optional(), + AWS_ACCESS_KEY_ID: z.string(), + AWS_SECRET_ACCESS_KEY: z.string(), + AWS_REGION: z.string(), + AWS_BUCKET_NAME: z.string(), + }, - client: { - NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(), - NEXT_PUBLIC_POSTHOG_HOST: z.string().optional(), - NEXT_PUBLIC_VERCEL_URL: z.string().optional(), - NEXT_PUBLIC_NOVU_IDENTIFIER: z.string().optional(), - }, + client: { + NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(), + NEXT_PUBLIC_POSTHOG_HOST: z.string().optional(), + NEXT_PUBLIC_VERCEL_URL: z.string().optional(), + NEXT_PUBLIC_NOVU_IDENTIFIER: z.string().optional(), + }, - runtimeEnv: { - AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID, - AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET, - AUTH_SECRET: process.env.AUTH_SECRET, - DATABASE_URL: process.env.DATABASE_URL, - OPENAI_API_KEY: process.env.OPENAI_API_KEY, - RESEND_API_KEY: process.env.RESEND_API_KEY, - UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL, - UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN, - STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, - STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET, - DISCORD_WEBHOOK_URL: process.env.DISCORD_WEBHOOK_URL, - TRIGGER_SECRET_KEY: process.env.TRIGGER_SECRET_KEY, - TRIGGER_API_KEY: process.env.TRIGGER_API_KEY, - TRIGGER_API_URL: process.env.TRIGGER_API_URL, - NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY, - NEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST, - VERCEL_ACCESS_TOKEN: process.env.VERCEL_ACCESS_TOKEN, - VERCEL_TEAM_ID: process.env.VERCEL_TEAM_ID, - VERCEL_PROJECT_ID: process.env.VERCEL_PROJECT_ID, - NEXT_PUBLIC_VERCEL_URL: process.env.NEXT_PUBLIC_VERCEL_URL, - NEXT_PUBLIC_NOVU_IDENTIFIER: process.env.NEXT_PUBLIC_NOVU_IDENTIFIER, - NODE_ENV: process.env.NODE_ENV, - AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID, - AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY, - AWS_REGION: process.env.AWS_REGION, - AWS_BUCKET_NAME: process.env.AWS_BUCKET_NAME, - }, + runtimeEnv: { + AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID, + AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET, + AUTH_SECRET: process.env.AUTH_SECRET, + DATABASE_URL: process.env.DATABASE_URL, + OPENAI_API_KEY: process.env.OPENAI_API_KEY, + RESEND_API_KEY: process.env.RESEND_API_KEY, + UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL, + UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN, + STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, + STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET, + DISCORD_WEBHOOK_URL: process.env.DISCORD_WEBHOOK_URL, + TRIGGER_SECRET_KEY: process.env.TRIGGER_SECRET_KEY, + TRIGGER_API_KEY: process.env.TRIGGER_API_KEY, + TRIGGER_API_URL: process.env.TRIGGER_API_URL, + REVALIDATION_SECRET: process.env.REVALIDATION_SECRET, + NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY, + NEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST, + VERCEL_ACCESS_TOKEN: process.env.VERCEL_ACCESS_TOKEN, + VERCEL_TEAM_ID: process.env.VERCEL_TEAM_ID, + VERCEL_PROJECT_ID: process.env.VERCEL_PROJECT_ID, + NEXT_PUBLIC_VERCEL_URL: process.env.NEXT_PUBLIC_VERCEL_URL, + NEXT_PUBLIC_NOVU_IDENTIFIER: process.env.NEXT_PUBLIC_NOVU_IDENTIFIER, + NODE_ENV: process.env.NODE_ENV, + AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY, + AWS_REGION: process.env.AWS_REGION, + AWS_BUCKET_NAME: process.env.AWS_BUCKET_NAME, + }, - skipValidation: !!process.env.CI || !!process.env.SKIP_ENV_VALIDATION, + skipValidation: !!process.env.CI || !!process.env.SKIP_ENV_VALIDATION, }); diff --git a/apps/app/src/jobs/tasks/organization/create-default-policies.ts b/apps/app/src/jobs/tasks/organization/create-default-policies.ts deleted file mode 100644 index d136b3ed8e..0000000000 --- a/apps/app/src/jobs/tasks/organization/create-default-policies.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { db } from "@bubba/db"; -import { ArtifactType } from "@bubba/db/types"; -import type { JSONContent } from "@tiptap/react"; -import { logger, schemaTask } from "@trigger.dev/sdk/v3"; -import { parseStringPromise } from "xml2js"; -import { z } from "zod"; - -const S3_BUCKET_URL = "https://compai-policies.s3.eu-central-1.amazonaws.com"; - -async function listPolicyFiles(): Promise { - try { - const response = await fetch(`${S3_BUCKET_URL}`); - const text = await response.text(); - - const result = await parseStringPromise(text); - - const keys: string[] = []; - const contents = result.ListBucketResult?.Contents || []; - - for (const content of contents) { - const key = content.Key?.[0]; - if (key?.endsWith(".json")) { - keys.push(key); - } - } - - return keys; - } catch (error) { - logger.error("Error listing policy files from S3:", { error }); - throw error; - } -} - -// Helper to fetch a single policy file -async function fetchPolicyFile(fileName: string) { - const response = await fetch(`${S3_BUCKET_URL}/${fileName}`); - return response.json(); -} - -export const createDefaultPoliciesTask = schemaTask({ - id: "create-default-policies", - maxDuration: 1000 * 60 * 10, // 10 minutes - schema: z.object({ - organizationId: z.string(), - organizationName: z.string(), - ownerId: z.string(), - }), - run: async (payload) => { - const { organizationId, organizationName, ownerId } = payload; - - try { - const policyFiles = await listPolicyFiles(); - - for (const fileName of policyFiles) { - const policyData = (await fetchPolicyFile(fileName)) as { - type: string; - metadata: { controls: string[] }; - content: JSONContent[]; - }; - - const currentDate = new Date().toLocaleDateString("en-US", { - year: "numeric", - month: "short", - day: "numeric", - }); - - // Replace placeholders in content while preserving node structure - const replaceInContent = (node: any): any => { - if (!node) return node; - - if (typeof node === "string") { - return node - .replace(/\{\{organization\}\}/g, organizationName) - .replace(/\{\{date\}\}/g, currentDate); - } - - if (Array.isArray(node)) { - return node.map((item) => replaceInContent(item)); - } - - if (typeof node === "object") { - const result: any = {}; - for (const [key, value] of Object.entries(node)) { - if (key === "type" || key === "attrs") { - result[key] = value; - } else { - result[key] = replaceInContent(value); - } - } - return result; - } - - return node; - }; - - const processedPolicy = { - type: policyData.type, - metadata: policyData.metadata, - content: replaceInContent(policyData.content), - }; - - const policyName = - processedPolicy.content?.find( - (node: any) => node.type === "heading" && node.attrs?.level === 1 - )?.content?.[0]?.text || fileName.replace(".json", ""); - - const artifact = await db.artifact.create({ - data: { - name: policyName, - content: processedPolicy, - organizationId, - type: ArtifactType.policy, - published: false, - needsReview: true, - version: 1, - ownerId, - }, - }); - - const controls = processedPolicy.metadata?.controls; - if (controls && Array.isArray(controls)) { - const dbControls = await db.control.findMany({ - where: { - code: { - in: controls, - }, - }, - }); - - for (const control of dbControls) { - const orgControl = await db.organizationControl.upsert({ - where: { - id: `${organizationId}-${control.id}`, - }, - create: { - id: `${organizationId}-${control.id}`, - organizationId, - controlId: control.id, - }, - update: {}, - }); - - await db.controlArtifact.create({ - data: { - organizationControlId: orgControl.id, - artifactId: artifact.id, - }, - }); - } - } - - logger.info(`Created policy: ${policyName}`); - } - - return { - success: true, - message: `Successfully copied and customized ${policyFiles.length} policies for organization ${organizationId}`, - }; - } catch (error) { - logger.error("Error creating default policies:", { error }); - throw error; - } - }, -}); - -export default createDefaultPoliciesTask; diff --git a/apps/app/src/jobs/tasks/organization/create-organization.ts b/apps/app/src/jobs/tasks/organization/create-organization.ts new file mode 100644 index 0000000000..f6fc4de4af --- /dev/null +++ b/apps/app/src/jobs/tasks/organization/create-organization.ts @@ -0,0 +1,387 @@ +import { db } from "@bubba/db"; +import { logger, schemaTask } from "@trigger.dev/sdk/v3"; +import { z } from "zod"; +import { setupOrgFrameworksTask } from "./setup-org-frameworks"; +import { RequirementType, type Policy, type User } from "@bubba/db/types"; +import type { InputJsonValue } from "@prisma/client/runtime/library"; + +export const createOrganizationTask = schemaTask({ + id: "create-organization", + maxDuration: 1000 * 60 * 3, // 3 minutes + schema: z.object({ + organizationId: z.string(), + userId: z.string(), + fullName: z.string(), + website: z.string(), + frameworkIds: z.array(z.string()), + }), + run: async ({ organizationId, userId, fullName, website, frameworkIds }) => { + logger.info("Creating organization", { + organizationId, + userId, + fullName, + website, + frameworkIds, + }); + + const organization = await db.organization.findFirst({ + where: { + id: organizationId, + users: { + some: { + id: userId, + }, + }, + }, + }); + + if (!organization) { + throw new Error("Organization not found"); + } + + try { + await db.$transaction(async () => { + await db.organization.upsert({ + where: { + id: organization.id, + }, + update: { + name: fullName, + website, + }, + create: { + name: fullName, + website, + }, + }); + + await db.user.update({ + where: { + id: userId, + }, + data: { + onboarded: true, + }, + }); + }); + + await createOrganizationCategories(organizationId, frameworkIds); + + const organizationFrameworks = await Promise.all( + frameworkIds.map((frameworkId) => + createOrganizationFramework(organizationId, frameworkId), + ), + ); + + await createOrganizationPolicy(organizationId, frameworkIds); + + await createOrganizationControlRequirements( + organizationId, + organizationFrameworks.map((framework) => framework.id), + ); + + await createOrganizationEvidence(organizationId, frameworkIds, userId); + + await db.organization.update({ + where: { + id: organizationId, + }, + data: { + setup: true, + }, + }); + + return { + success: true, + }; + } catch (error) { + logger.error("Error creating organization", { + error, + }); + + throw error; + } + }, +}); + +const createOrganizationFramework = async ( + organizationId: string, + frameworkId: string, +) => { + // First verify the organization exists + const organization = await db.organization.findUnique({ + where: { id: organizationId }, + }); + + if (!organization) { + logger.error("Organization not found when creating framework", { + organizationId, + frameworkId, + }); + throw new Error(`Organization with ID ${organizationId} not found`); + } + + // Verify the framework exists + const framework = await db.framework.findUnique({ + where: { id: frameworkId }, + }); + + if (!framework) { + logger.error("Framework not found when creating organization framework", { + organizationId, + frameworkId, + }); + throw new Error(`Framework with ID ${frameworkId} not found`); + } + + const organizationFramework = await db.organizationFramework.create({ + data: { + organizationId, + frameworkId, + status: "not_started", + }, + select: { + id: true, + }, + }); + + logger.info("Created organization framework", { + organizationId, + frameworkId, + organizationFrameworkId: organizationFramework.id, + }); + + const frameworkCategories = await db.frameworkCategory.findMany({ + where: { frameworkId }, + include: { + controls: true, + }, + }); + + const organizationCategories = await db.organizationCategory.findMany({ + where: { + organizationId, + frameworkId, + }, + }); + + for (const frameworkCategory of frameworkCategories) { + const organizationCategory = organizationCategories.find( + (oc) => oc.name === frameworkCategory.name, + ); + + if (!organizationCategory) continue; + + await db.organizationControl.createMany({ + data: frameworkCategory.controls.map((control) => ({ + organizationFrameworkId: organizationFramework.id, + controlId: control.id, + organizationId, + status: "not_started", + organizationCategoryId: organizationCategory.id, + })), + }); + } + + return organizationFramework; +}; + +const createOrganizationPolicy = async ( + organizationId: string, + frameworkIds: string[], +) => { + if (!organizationId) { + throw new Error("Not authorized - no organization found"); + } + + const policies = await db.policy.findMany(); + const policiesForFrameworks: Policy[] = []; + + for (const policy of policies) { + const usedBy = policy.usedBy; + + if (!usedBy) { + continue; + } + + const usedByFrameworkIds = Object.keys(usedBy); + + if ( + usedByFrameworkIds.some((frameworkId) => + frameworkIds.includes(frameworkId), + ) + ) { + policiesForFrameworks.push(policy); + } + } + + const organizationPolicies = await db.organizationPolicy.createMany({ + data: policiesForFrameworks.map((policy) => ({ + organizationId, + policyId: policy.id, + status: "draft", + content: policy.content as InputJsonValue[], + frequency: policy.frequency, + })), + }); + + return organizationPolicies; +}; + +const createOrganizationCategories = async ( + organizationId: string, + frameworkIds: string[], +) => { + if (!organizationId) { + throw new Error("Not authorized - no organization found"); + } + + // For each frameworkCategory we need to get the controls. + const frameworkCategories = await db.frameworkCategory.findMany({ + where: { + frameworkId: { in: frameworkIds }, + }, + }); + + // Create the organization categories. + const organizationCategories = await db.organizationCategory.createMany({ + data: frameworkCategories.map((category) => ({ + name: category.name, + description: category.description, + organizationId, + frameworkId: category.frameworkId, + })), + }); + + return organizationCategories; +}; + +const createOrganizationControlRequirements = async ( + organizationId: string, + organizationFrameworkIds: string[], +) => { + if (!organizationId) { + throw new Error("Not authorized - no organization found"); + } + + const controls = await db.organizationControl.findMany({ + where: { + organizationId, + organizationFrameworkId: { + in: organizationFrameworkIds, + }, + }, + include: { + control: true, + }, + }); + + // Create control requirements for each control + const controlRequirements = await db.controlRequirement.findMany({ + where: { + controlId: { in: controls.map((control) => control.controlId) }, + }, + include: { + policy: true, // Include the policy to get its ID + evidence: true, // Include the evidence to get its ID + }, + }); + + // Get all organization policies for this organization + const organizationPolicies = await db.organizationPolicy.findMany({ + where: { + organizationId, + }, + }); + + // Get all organization evidences for this organization + const organizationEvidences = await db.organizationEvidence.findMany({ + where: { + organizationId, + }, + }); + + for (const control of controls) { + const requirements = controlRequirements.filter( + (req) => req.controlId === control.controlId, + ); + + await db.organizationControlRequirement.createMany({ + data: requirements.map((requirement) => { + // Find the corresponding organization policy if this is a policy requirement + const policyId = + requirement.type === "policy" ? requirement.policy?.id : null; + const organizationPolicy = policyId + ? organizationPolicies.find((op) => op.policyId === policyId) + : null; + + const evidenceId = + requirement.type === "evidence" ? requirement.evidenceId : null; + + console.log({ + evidenceId, + }); + + const organizationEvidence = evidenceId + ? organizationEvidences.find((e) => e.evidenceId === evidenceId) + : null; + + console.log({ + organizationEvidence, + }); + + return { + organizationControlId: control.id, + controlRequirementId: requirement.id, + type: requirement.type, + description: requirement.description, + organizationPolicyId: organizationPolicy?.id || null, + organizationEvidenceId: organizationEvidence?.id || null, + }; + }), + }); + } + + return controlRequirements; +}; + +const createOrganizationEvidence = async ( + organizationId: string, + frameworkIds: string[], + userId: string, +) => { + if (!organizationId) { + throw new Error("Not authorized - no organization found"); + } + + const evidence = await db.controlRequirement.findMany({ + where: { + type: RequirementType.evidence, + }, + include: { + control: { + include: { + frameworkCategory: { + include: { + framework: true, + }, + }, + }, + }, + }, + }); + + const organizationEvidence = await db.organizationEvidence.createMany({ + data: evidence.map((evidence) => ({ + organizationId, + evidenceId: evidence.id, + name: evidence.name, + description: evidence.description, + frequency: evidence.frequency, + frameworkId: evidence.control.frameworkCategory?.framework.id || "", + assigneeId: userId, + })), + }); + + return organizationEvidence; +}; diff --git a/apps/app/src/jobs/tasks/organization/new-organization.ts b/apps/app/src/jobs/tasks/organization/new-organization.ts deleted file mode 100644 index 367b49cb2a..0000000000 --- a/apps/app/src/jobs/tasks/organization/new-organization.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { logger, schemaTask } from "@trigger.dev/sdk/v3"; -import { z } from "zod"; - -export const newOrganizationTask = schemaTask({ - id: "new-organization", - maxDuration: 1000 * 60 * 10, // 10 minutes - schema: z.object({ - organizationId: z.string(), - userId: z.string(), - }), - run: async ({ organizationId, userId }) => { - logger.info("New organization task started", { - organizationId, - userId, - }); - - logger.info("New organization task completed", { - organizationId, - userId, - }); - }, -}); diff --git a/apps/app/src/jobs/tasks/organization/setup-org-frameworks.ts b/apps/app/src/jobs/tasks/organization/setup-org-frameworks.ts new file mode 100644 index 0000000000..84d3d91024 --- /dev/null +++ b/apps/app/src/jobs/tasks/organization/setup-org-frameworks.ts @@ -0,0 +1,342 @@ +import { RequirementType, type Policy, type User } from "@bubba/db/types"; +import type { InputJsonValue } from "@prisma/client/runtime/library"; +import { db } from "@bubba/db"; +import { logger, schemaTask } from "@trigger.dev/sdk/v3"; +import { z } from "zod"; + +export const setupOrgFrameworksTask = schemaTask({ + id: "setup-org-frameworks", + maxDuration: 1000 * 60 * 3, // 3 minutes + schema: z.object({ + userId: z.string(), + organizationId: z.string(), + frameworkIds: z.array(z.string()), + }), + run: async ({ organizationId, frameworkIds, userId }) => { + logger.info("Setting up organization frameworks", { + organizationId, + frameworkIds, + }); + + const organization = await db.organization.findFirst({ + where: { + id: organizationId, + }, + }); + + if (!organization) { + throw new Error("Organization not found"); + } + + try { + await createOrganizationCategories(organizationId, frameworkIds); + + const organizationFrameworks = await Promise.all( + frameworkIds.map((frameworkId) => + createOrganizationFramework(organizationId, frameworkId), + ), + ); + + await createOrganizationPolicy(organizationId, frameworkIds); + + await createOrganizationControlRequirements( + organizationId, + organizationFrameworks.map((framework) => framework.id), + ); + + await createOrganizationEvidence(organizationId, frameworkIds, userId); + + return { + success: true, + }; + } catch (error) { + logger.error("Error setting up organization frameworks", { + error, + }); + + throw error; + } + }, +}); + +const createOrganizationFramework = async ( + organizationId: string, + frameworkId: string, +) => { + // First verify the organization exists + const organization = await db.organization.findUnique({ + where: { id: organizationId }, + }); + + if (!organization) { + logger.error("Organization not found when creating framework", { + organizationId, + frameworkId, + }); + throw new Error(`Organization with ID ${organizationId} not found`); + } + + // Verify the framework exists + const framework = await db.framework.findUnique({ + where: { id: frameworkId }, + }); + + if (!framework) { + logger.error("Framework not found when creating organization framework", { + organizationId, + frameworkId, + }); + throw new Error(`Framework with ID ${frameworkId} not found`); + } + + const organizationFramework = await db.organizationFramework.create({ + data: { + organizationId, + frameworkId, + status: "not_started", + }, + select: { + id: true, + }, + }); + + logger.info("Created organization framework", { + organizationId, + frameworkId, + organizationFrameworkId: organizationFramework.id, + }); + + const frameworkCategories = await db.frameworkCategory.findMany({ + where: { frameworkId }, + include: { + controls: true, + }, + }); + + const organizationCategories = await db.organizationCategory.findMany({ + where: { + organizationId, + frameworkId, + }, + }); + + for (const frameworkCategory of frameworkCategories) { + const organizationCategory = organizationCategories.find( + (oc) => oc.name === frameworkCategory.name, + ); + + if (!organizationCategory) continue; + + await db.organizationControl.createMany({ + data: frameworkCategory.controls.map((control) => ({ + organizationFrameworkId: organizationFramework.id, + controlId: control.id, + organizationId, + status: "not_started", + organizationCategoryId: organizationCategory.id, + })), + }); + } + + return organizationFramework; +}; + +const createOrganizationPolicy = async ( + organizationId: string, + frameworkIds: string[], +) => { + if (!organizationId) { + throw new Error("Not authorized - no organization found"); + } + + const policies = await db.policy.findMany(); + const policiesForFrameworks: Policy[] = []; + + for (const policy of policies) { + const usedBy = policy.usedBy; + + if (!usedBy) { + continue; + } + + const usedByFrameworkIds = Object.keys(usedBy); + + if ( + usedByFrameworkIds.some((frameworkId) => + frameworkIds.includes(frameworkId), + ) + ) { + policiesForFrameworks.push(policy); + } + } + + const organizationPolicies = await db.organizationPolicy.createMany({ + data: policiesForFrameworks.map((policy) => ({ + organizationId, + policyId: policy.id, + status: "draft", + content: policy.content as InputJsonValue[], + frequency: policy.frequency, + })), + }); + + return organizationPolicies; +}; + +const createOrganizationCategories = async ( + organizationId: string, + frameworkIds: string[], +) => { + if (!organizationId) { + throw new Error("Not authorized - no organization found"); + } + + // For each frameworkCategory we need to get the controls. + const frameworkCategories = await db.frameworkCategory.findMany({ + where: { + frameworkId: { in: frameworkIds }, + }, + }); + + // Create the organization categories. + const organizationCategories = await db.organizationCategory.createMany({ + data: frameworkCategories.map((category) => ({ + name: category.name, + description: category.description, + organizationId, + frameworkId: category.frameworkId, + })), + }); + + return organizationCategories; +}; + +const createOrganizationControlRequirements = async ( + organizationId: string, + organizationFrameworkIds: string[], +) => { + if (!organizationId) { + throw new Error("Not authorized - no organization found"); + } + + const controls = await db.organizationControl.findMany({ + where: { + organizationId, + organizationFrameworkId: { + in: organizationFrameworkIds, + }, + }, + include: { + control: true, + }, + }); + + // Create control requirements for each control + const controlRequirements = await db.controlRequirement.findMany({ + where: { + controlId: { in: controls.map((control) => control.controlId) }, + }, + include: { + policy: true, // Include the policy to get its ID + evidence: true, // Include the evidence to get its ID + }, + }); + + // Get all organization policies for this organization + const organizationPolicies = await db.organizationPolicy.findMany({ + where: { + organizationId, + }, + }); + + // Get all organization evidences for this organization + const organizationEvidences = await db.organizationEvidence.findMany({ + where: { + organizationId, + }, + }); + + for (const control of controls) { + const requirements = controlRequirements.filter( + (req) => req.controlId === control.controlId, + ); + + await db.organizationControlRequirement.createMany({ + data: requirements.map((requirement) => { + // Find the corresponding organization policy if this is a policy requirement + const policyId = + requirement.type === "policy" ? requirement.policy?.id : null; + const organizationPolicy = policyId + ? organizationPolicies.find((op) => op.policyId === policyId) + : null; + + const evidenceId = + requirement.type === "evidence" ? requirement.evidenceId : null; + + console.log({ + evidenceId, + }); + + const organizationEvidence = evidenceId + ? organizationEvidences.find((e) => e.evidenceId === evidenceId) + : null; + + console.log({ + organizationEvidence, + }); + + return { + organizationControlId: control.id, + controlRequirementId: requirement.id, + type: requirement.type, + description: requirement.description, + organizationPolicyId: organizationPolicy?.id || null, + organizationEvidenceId: organizationEvidence?.id || null, + }; + }), + }); + } + + return controlRequirements; +}; + +const createOrganizationEvidence = async ( + organizationId: string, + frameworkIds: string[], + userId: string, +) => { + if (!organizationId) { + throw new Error("Not authorized - no organization found"); + } + + const evidence = await db.controlRequirement.findMany({ + where: { + type: RequirementType.evidence, + }, + include: { + control: { + include: { + frameworkCategory: { + include: { + framework: true, + }, + }, + }, + }, + }, + }); + + const organizationEvidence = await db.organizationEvidence.createMany({ + data: evidence.map((evidence) => ({ + organizationId, + evidenceId: evidence.id, + name: evidence.name, + description: evidence.description, + frequency: evidence.frequency, + frameworkId: evidence.control.frameworkCategory?.framework.id || "", + assigneeId: userId, + })), + }); + + return organizationEvidence; +}; diff --git a/apps/app/src/locales/en.ts b/apps/app/src/locales/en.ts index 82d00b69a8..88b4d0f9dd 100644 --- a/apps/app/src/locales/en.ts +++ b/apps/app/src/locales/en.ts @@ -1,1094 +1,1104 @@ export default { - language: { - title: "Languages", - description: "Change the language used in the user interface.", - placeholder: "Select language", - }, - languages: { - en: "English", - es: "Spanish", - fr: "French", - no: "Norwegian", - pt: "Portuguese", - }, - common: { - frequency: { - daily: "Daily", - weekly: "Weekly", - monthly: "Monthly", - quarterly: "Quarterly", - yearly: "Yearly", - }, - notifications: { - inbox: "Inbox", - archive: "Archive", - archive_all: "Archive all", - no_notifications: "No new notifications", - }, - actions: { - save: "Save", - edit: "Edit", - delete: "Delete", - cancel: "Cancel", - clear: "Clear", - create: "Create", - addNew: "Add New", - send: "Send", - return: "Return", - success: "Success", - error: "Error", - next: "Next", - complete: "Complete", - }, - assignee: { - label: "Assignee", - placeholder: "Select assignee", - }, - date: { - pick: "Pick a date", - due_date: "Due Date", - }, - status: { - open: "Open", - pending: "Pending", - closed: "Closed", - archived: "Archived", - compliant: "Compliant", - non_compliant: "Non Compliant", - not_started: "Not Started", - in_progress: "In Progress", - published: "Published", - needs_review: "Needs Review", - draft: "Draft", - not_assessed: "Not Assessed", - assessed: "Assessed", - active: "Active", - inactive: "Inactive", - title: "Status", - }, - filters: { - clear: "Clear filters", - search: "Search...", - status: "Status", - department: "Department", - owner: { - label: "Assignee", - placeholder: "Filter by assignee", - }, - }, - table: { - title: "Title", - status: "Status", - assigned_to: "Assigned To", - due_date: "Due Date", - last_updated: "Last Updated", - no_results: "No results found", - }, - empty_states: { - no_results: { - title: "No results found", - title_tasks: "No tasks found", - title_risks: "No risks found", - description: "Try another search, or adjusting the filters", - description_filters: "Try another search, or adjusting the filters", - description_no_tasks: "Create a task to get started", - description_no_risks: "Create a risk to get started", - }, - no_items: { - title: "No items found", - description: "Try adjusting your search or filters", - }, - }, - pagination: { - of: "of", - items_per_page: "Items per page", - rows_per_page: "Rows per page", - page_x_of_y: "Page {{current}} of {{total}}", - go_to_first_page: "Go to first page", - go_to_previous_page: "Go to previous page", - go_to_next_page: "Go to next page", - go_to_last_page: "Go to last page", - }, - comments: { - title: "Comments", - description: "Add a comment using the form below.", - add: "New Comment", - new: "New Comment", - save: "Save Comment", - success: "Comment added successfully", - error: "Failed to add comment", - placeholder: "Write your comment here...", - empty: { - title: "No comments yet", - description: "Be the first to add a comment", - }, - }, - upload: { - fileUpload: { - uploadingText: "Uploading...", - uploadingFile: "Uploading file...", - dropFileHere: "Drop file here", - dropFileHereAlt: "Drop file here", - releaseToUpload: "Release to upload", - addFiles: "Add Files", - uploadAdditionalEvidence: "Upload a file or document", - dragDropOrClick: "Drag and drop or click to browse", - dragDropOrClickToSelect: "Drag and drop or click to select file", - maxFileSize: "Max file size: {size}MB", - }, - fileUrl: { - additionalLinks: "Additional Links", - add: "Add", - linksAdded: "{count} link{s} added", - enterUrl: "Enter URL", - addAnotherLink: "Add Another Link", - saveLinks: "Save Links", - urlBadge: "URL", - copyLink: "Copy Link", - openLink: "Open Link", - deleteLink: "Delete Link", - }, - fileCard: { - preview: "Preview", - filePreview: "File Preview: {fileName}", - previewNotAvailable: "Preview not available for this file type", - openFile: "Open File", - deleteFile: "Delete File", - deleteFileConfirmTitle: "Delete File", - deleteFileConfirmDescription: - "This action cannot be undone. The file will be permanently deleted.", - }, - fileSection: { - filesUploaded: "{count} files uploaded", - }, - }, - attachments: { - title: "Attachments", - description: "Add a file by clicking 'Add Attachment'.", - upload: "Upload attachment", - upload_description: - "Upload an attachment or add a link to an external resource.", - drop: "Drop the files here", - drop_description: - "Drop files here or click to choose files from your device.", - drop_files_description: "Files can be up to ", - empty: { - title: "No attachments", - description: "Add a file by clicking 'Add Attachment'.", - }, - toasts: { - error: "Something went wrong, please try again.", - error_uploading_files: "Cannot upload more than 1 file at a time", - error_uploading_files_multiple: "Cannot upload more than 10 files", - error_no_files_selected: "No files selected", - error_file_rejected: "File {file} was rejected", - error_failed_to_upload_files: "Failed to upload files", - error_failed_to_upload_files_multiple: "Failed to upload files", - error_failed_to_upload_files_single: "Failed to upload file", - success_uploading_files: "Files uploaded successfully", - success_uploading_files_multiple: "Files uploaded successfully", - success_uploading_files_single: "File uploaded successfully", - success_uploading_files_target: "Files uploaded", - uploading_files: "Uploading {target}...", - remove_file: "Remove file", - }, - }, - edit: "Edit", - errors: { - unexpected_error: "An unexpected error occurred", - }, - description: "Description", - last_updated: "Last Updated", - }, - header: { - discord: { - button: "Join us on Discord", - }, - feedback: { - button: "Feedback", - title: "Thank you for your feedback!", - description: "We will be back with you as soon as possible", - placeholder: "Ideas to improve this page or issues you are experiencing.", - success: "Thank you for your feedback!", - error: "Error sending feedback - try again?", - send: "Send Feedback", - }, - }, - not_found: { - title: "404 - Page not found", - description: "The page you are looking for does not exist.", - return: "Return to dashboard", - }, - theme: { - options: { - light: "Light", - dark: "Dark", - system: "System", - }, - }, - sidebar: { - overview: "Overview", - policies: "Policies", - risk: "Risk Management", - vendors: "Vendors", - integrations: "Integrations", - settings: "Settings", - evidence: "Evidence Tasks", - people: "People", - tests: "Cloud Tests", - }, - sub_pages: { - evidence: { - title: "Evidence", - list: "Evidence List", - overview: "Evidence Overview", - }, - risk: { - overview: "Risk Management", - register: "Risk Register", - risk_overview: "Risk Overview", - risk_comments: "Risk Comments", - tasks: { - task_overview: "Task Overview", - }, - }, - policies: { - all: "All Policies", - editor: "Policy Editor", - policy_details: "Policy Details", - }, - people: { - all: "People", - employee_details: "Employee Details", - }, - settings: { - members: "Team Members", - }, - frameworks: { - overview: "Frameworks", - }, - tests: { - overview: "Cloud Tests", - test_details: "Test Details", - }, - }, - auth: { - title: "Automate SOC 2, ISO 27001 and GDPR compliance with AI.", - description: - "Create a free account or log in with an existing account to continue.", - options: "More options", - google: "Continue with Google", - email: { - description: "Enter your email address to continue.", - placeholder: "Enter email address", - button: "Continue with email", - magic_link_sent: "Magic link sent", - magic_link_description: "Check your inbox for a magic link.", - magic_link_try_again: "Try again.", - success: "Email sent - check your inbox!", - error: "Error sending email - try again?", - }, - terms: - "By clicking continue, you acknowledge that you have read and agree to the Terms of Service and Privacy Policy.", - }, - onboarding: { - title: "Create an organization", - setup: "Setup", - description: "Tell us a bit about your organization.", - fields: { - fullName: { - label: "Your Name", - placeholder: "Your full name", - }, - name: { - label: "Organization Name", - placeholder: "Your organization name", - }, - subdomain: { - label: "Subdomain", - placeholder: "example", - }, - website: { - label: "Website", - placeholder: "Your organization website", - }, - }, - success: "Thanks, you're all set!", - error: "Something went wrong, please try again.", - check_availability: "Checking availability", - available: "Available", - unavailable: "Unavailable", - }, - overview: { - title: "Overview", - framework_chart: { - title: "Framework Progress", - }, - requirement_chart: { - title: "Compliance Status", - }, - }, - policies: { - dashboard: { - title: "Dashboard", - all: "All Policies", - policy_status: "Policy by Status", - policies_by_assignee: "Policies by Assignee", - policies_by_framework: "Policies by Framework", - sub_pages: { - overview: "Overview", - edit_policy: "Edit Policy", - }, - }, - overview: { - title: "Policy Overview", - form: { - update_policy: "Update Policy", - update_policy_description: "Update the policy title or description.", - update_policy_success: "Policy updated successfully", - update_policy_error: "Failed to update policy", - update_policy_title: "Policy Name", - review_frequency: "Review Frequency", - review_frequency_placeholder: "Select a review frequency", - review_date: "Review Date", - review_date_placeholder: "Select a review date", - required_to_sign: "Required to be signed by employees", - signature_required: "Require employees signature", - signature_not_required: "Do not ask employees to sign", - signature_requirement: "Signature Requirement", - signature_requirement_placeholder: "Select signature requirement", - }, - }, - new: { - success: "Policy successfully created", - error: "Failed to create policy", - details: "Policy Details", - title: "Enter a title for the policy", - description: "Enter a description for the policy", - }, - table: { - name: "Policy Name", - statuses: { - draft: "Draft", - published: "Published", - archived: "Archived", - }, - filters: { - owner: { - label: "Assignee", - placeholder: "Filter by assignee", - }, - }, - }, - filters: { - search: "Search policies...", - all: "All Policies", - }, - status: { - draft: "Draft", - published: "Published", - needs_review: "Needs Review", - archived: "Archived", - }, - policies: "policies", - title: "Policies", - create_new: "Create New Policy", - search_placeholder: "Search policies...", - status_filter: "Filter by status", - all_statuses: "All statuses", - no_policies_title: "No policies yet", - no_policies_description: "Get started by creating your first policy", - create_first: "Create first policy", - no_description: "No description provided", - last_updated: "Last updated: {{date}}", - save: "Save", - saving: "Saving...", - saved_success: "Policy saved successfully", - saved_error: "Failed to save policy", - }, - evidence_tasks: { - evidence_tasks: "Evidence Tasks", - overview: "Overview", - }, - risk: { - risks: "risks", - overview: "Overview", - create: "Create New Risk", - vendor: { - title: "Vendor Management", - dashboard: { - title: "Vendor Dashboard", - overview: "Vendor Overview", - vendor_status: "Vendor Status", - vendor_category: "Vendor Categories", - vendors_by_assignee: "Vendors by Assignee", - inherent_risk_description: - "Initial risk level before any controls are applied", - residual_risk_description: - "Remaining risk level after controls are applied", - }, - register: { - title: "Vendor Register", - table: { - name: "Name", - category: "Category", - status: "Status", - owner: "Owner", - }, - }, - assessment: { - title: "Vendor Assessment", - update_success: "Vendor risk assessment updated successfully", - update_error: "Failed to update vendor risk assessment", - inherent_risk: "Inherent Risk", - residual_risk: "Residual Risk", - }, - form: { - vendor_details: "Vendor Details", - vendor_name: "Name", - vendor_name_placeholder: "Enter vendor name", - vendor_website: "Website", - vendor_website_placeholder: "Enter vendor website", - vendor_description: "Description", - vendor_description_placeholder: "Enter vendor description", - vendor_category: "Category", - vendor_category_placeholder: "Select category", - vendor_status: "Status", - vendor_status_placeholder: "Select status", - create_vendor_success: "Vendor created successfully", - create_vendor_error: "Failed to create vendor", - update_vendor: "Update Vendor", - update_vendor_success: "Vendor updated successfully", - update_vendor_error: "Failed to update vendor", - add_comment: "Add Comment", - }, - table: { - name: "Name", - category: "Category", - status: "Status", - owner: "Owner", - }, - filters: { - search_placeholder: "Search vendors...", - status_placeholder: "Filter by status", - category_placeholder: "Filter by category", - owner_placeholder: "Filter by owner", - }, - empty_states: { - no_vendors: { - title: "No vendors yet", - description: "Get started by creating your first vendor", - }, - no_results: { - title: "No results found", - description: "No vendors match your search", - description_with_filters: "Try adjusting your filters", - }, - }, - actions: { - create: "Create Vendor", - }, - status: { - not_assessed: "Not Assessed", - in_progress: "In Progress", - assessed: "Assessed", - }, - category: { - cloud: "Cloud", - infrastructure: "Infrastructure", - software_as_a_service: "Software as a Service", - finance: "Finance", - marketing: "Marketing", - sales: "Sales", - hr: "HR", - other: "Other", - }, - risk_level: { - low: "Low Risk", - medium: "Medium Risk", - high: "High Risk", - unknown: "Unknown Risk", - }, - }, - dashboard: { - title: "Dashboard", - overview: "Risk Overview", - risk_status: "Risk Status", - risks_by_department: "Risks by Department", - risks_by_assignee: "Risks by Assignee", - inherent_risk_description: - "Inherent risk is calculated as likelihood * impact. Calculated before any controls are applied.", - residual_risk_description: - "Residual risk is calculated as likelihood * impact. This is the risk level after controls are applied.", - risk_assessment_description: "Compare inherent and residual risk levels", - }, - register: { - title: "Risk Register", - table: { - risk: "Risk", - }, - empty: { - no_risks: { - title: "Create a risk to get started", - description: - "Track and score risks, create and assign mitigation tasks for your team, and manage your risk register all in one simple interface.", - }, - create_risk: "Create a risk", - }, - }, - metrics: { - probability: "Probability", - impact: "Impact", - inherentRisk: "Inherent Risk", - residualRisk: "Residual Risk", - }, - form: { - update_inherent_risk: "Save Inherent Risk", - update_inherent_risk_description: - "Update the inherent risk of the risk. This is the risk level before any controls are applied.", - update_inherent_risk_success: "Inherent risk updated successfully", - update_inherent_risk_error: "Failed to update inherent risk", - update_residual_risk: "Save Residual Risk", - update_residual_risk_description: - "Update the residual risk of the risk. This is the risk level after controls are applied.", - update_residual_risk_success: "Residual risk updated successfully", - update_residual_risk_error: "Failed to update residual risk", - update_risk: "Update Risk", - update_risk_description: "Update the risk title or description.", - update_risk_success: "Risk updated successfully", - update_risk_error: "Failed to update risk", - create_risk_success: "Risk created successfully", - create_risk_error: "Failed to create risk", - risk_details: "Risk Details", - risk_title: "Risk Title", - risk_title_description: "Enter a name for the risk", - risk_description: "Description", - risk_description_description: "Enter a description for the risk", - risk_category: "Category", - risk_category_placeholder: "Select a category", - risk_department: "Department", - risk_department_placeholder: "Select a department", - risk_status: "Risk Status", - risk_status_placeholder: "Select a risk status", - }, - tasks: { - title: "Tasks", - attachments: "Attachments", - overview: "Task Overview", - form: { - title: "Task Details", - task_title: "Task Title", - status: "Task Status", - status_placeholder: "Select a task status", - task_title_description: "Enter a name for the task", - description: "Description", - description_description: "Enter a description for the task", - due_date: "Due Date", - due_date_description: "Select the due date for the task", - success: "Task created successfully", - error: "Failed to create task", - }, - sheet: { - title: "Create Task", - update: "Update Task", - update_description: "Update the task title or description.", - }, - empty: { - description_create: - "Create a mitigation task for this risk, add a treatment plan, and assign it to a team member.", - }, - }, - }, - people: { - title: "People", - details: { - taskProgress: "Task Progress", - tasks: "Tasks", - noTasks: "No tasks assigned yet", - }, - description: "Manage your team members and their roles.", - filters: { - search: "Search people...", - role: "Filter by role", - }, - actions: { - invite: "Add Employee", - clear: "Clear filters", - }, - table: { - name: "Name", - email: "Email", - department: "Department", - externalId: "External ID", - status: "Status", - }, - empty: { - no_employees: { - title: "No employees yet", - description: "Get started by inviting your first team member.", - }, - no_results: { - title: "No results found", - description: "No employees match your search", - description_with_filters: "Try adjusting your filters", - }, - }, - invite: { - title: "Add Employee", - description: "Add an employee to your organization.", - email: { - label: "Email address", - placeholder: "Enter email address", - }, - role: { - label: "Role", - placeholder: "Select a role", - }, - name: { - label: "Name", - placeholder: "Enter name", - }, - department: { - label: "Department", - placeholder: "Select a department", - }, - submit: "Add Employee", - success: "Employee added successfully", - error: "Failed to add employee", - }, - }, - settings: { - general: { - title: "General", - org_name: "Organization name", - org_name_description: - "This is your organizations visible name. You should use the legal name of your organization.", - org_name_tip: "Please use 32 characters at maximum.", - org_website: "Organization Website", - org_website_description: - "This is your organization's official website URL. Make sure to include the full URL with https://.", - org_website_tip: "Please enter a valid URL including https://", - org_website_error: "Error updating organization website", - org_website_updated: "Organization website updated", - org_delete: "Delete organization", - org_delete_description: - "Permanently remove your organization and all of its contents from the Comp AI platform. This action is not reversible - please continue with caution.", - org_delete_alert_title: "Are you absolutely sure?", - org_delete_alert_description: - "This action cannot be undone. This will permanently delete your organization and remove your data from our servers.", - org_delete_error: "Error deleting organization", - org_delete_success: "Organization deleted", - org_name_updated: "Organization name updated", - org_name_error: "Error updating organization name", - save_button: "Save", - delete_button: "Delete", - delete_confirm: "DELETE", - delete_confirm_tip: "Type DELETE to confirm.", - cancel_button: "Cancel", - }, - members: { - title: "Members", - }, - api_keys: { - title: "API Keys", - description: - "Manage API keys for programmatic access to your organization's data.", - list_title: "API Keys", - list_description: - "API keys allow secure access to your organization's data via our API.", - create: "New API Key", - create_title: "New API Key", - create_description: - "Create a new API key for programmatic access to your organization's data.", - created_title: "API Key Created", - created_description: - "Your API key has been created. Make sure to copy it now as you won't be able to see it again.", - name: "Name", - name_label: "Name", - name_placeholder: "Enter a name for this API key", - expiration: "Expiration", - expiration_placeholder: "Select expiration", - expires_label: "Expires", - expires_placeholder: "Select expiration", - expires_30days: "30 days", - expires_90days: "90 days", - expires_1year: "1 year", - expires_never: "Never", - thirty_days: "30 days", - ninety_days: "90 days", - one_year: "1 year", - your_key: "Your API Key", - api_key: "API Key", - save_warning: - "This key will only be shown once. Make sure to copy it now.", - copied: "API key copied to clipboard", - close_confirm: - "Are you sure you want to close? You won't be able to see this API key again.", - revoke_confirm: - "Are you sure you want to revoke this API key? This action cannot be undone.", - revoke_title: "Revoke API Key", - revoke: "Revoke", - created: "Created", - expires: "Expires", - last_used: "Last Used", - actions: "Actions", - never: "Never", - never_used: "Never used", - no_keys: "No API keys found. Create one to get started.", - security_note: - "API keys provide full access to your organization's data. Keep them secure and rotate them regularly.", - fetch_error: "Failed to fetch API keys", - create_error: "Failed to create API key", - revoked_success: "API key revoked successfully", - revoked_error: "Failed to revoke API key", - done: "Done", - }, - billing: { - title: "Billing", - }, - team: { - tabs: { - members: "Team Members", - invite: "Invite Members", - }, - members: { - title: "Team Members", - empty: { - no_organization: { - title: "No Organization", - description: "You are not part of any organization", - }, - no_members: { - title: "No Members", - description: "There are no active members in your organization", - }, - }, - role: { - owner: "Owner", - admin: "Admin", - member: "Member", - viewer: "Viewer", - }, - }, - invitations: { - title: "Pending Invitations", - description: "Users who have been invited but haven't accepted yet", - empty: { - no_organization: { - title: "No Organization", - description: "You are not part of any organization", - }, - no_invitations: { - title: "No Pending Invitations", - description: "There are no pending invitations", - }, - }, - invitation_sent: "Invitation sent", - actions: { - resend: "Resend Invite", - sending: "Sending Invite", - revoke: "Revoke", - revoke_title: "Revoke Invitation", - revoke_description_prefix: - "Are you sure you want to revoke the invitation for", - revoke_description_suffix: "This action cannot be undone.", - }, - toast: { - resend_success_prefix: "An invitation email has been sent to", - resend_error: "Failed to send invitation", - resend_unexpected: - "An unexpected error occurred while sending the invitation", - revoke_success_prefix: "Invitation to", - revoke_success_suffix: "has been revoked", - revoke_error: "Failed to revoke invitation", - revoke_unexpected: - "An unexpected error occurred while revoking the invitation", - }, - }, - invite: { - title: "Invite Team Member", - description: - "Send an invitation to a new team member to join your organization", - form: { - email: { - label: "Email", - placeholder: "member@example.com", - error: "Please enter a valid email address", - }, - role: { - label: "Role", - placeholder: "Select a role", - error: "Please select a role", - }, - department: { - label: "Department", - placeholder: "Select a department", - error: "Please select a department", - }, - departments: { - none: "None", - it: "IT", - hr: "HR", - admin: "Admin", - gov: "Government", - itsm: "ITSM", - qms: "QMS", - }, - }, - button: { - send: "Send Invitation", - sending: "Sending invitation...", - sent: "Invitation Sent", - }, - toast: { - error: "Failed to send invitation", - unexpected: - "An unexpected error occurred while sending the invitation", - }, - }, - member_actions: { - actions: "Actions", - change_role: "Change Role", - remove_member: "Remove Member", - remove_confirm: { - title: "Remove Team Member", - description_prefix: "Are you sure you want to remove", - description_suffix: "This action cannot be undone.", - }, - role_dialog: { - title: "Change Role", - description_prefix: "Update the role for", - role_label: "Role", - role_placeholder: "Select a role", - role_descriptions: { - admin: "Admins can manage team members and settings.", - member: - "Members can use all features but cannot manage team members.", - viewer: "Viewers can only view content without making changes.", - }, - cancel: "Cancel", - update: "Update Role", - }, - toast: { - remove_success: "has been removed from the organization", - remove_error: "Failed to remove member", - remove_unexpected: - "An unexpected error occurred while removing the member", - update_role_success: "has had their role updated to", - update_role_error: "Failed to update member role", - update_role_unexpected: - "An unexpected error occurred while updating the member's role", - }, - }, - }, - }, - tests: { - dashboard: { - overview: "Overview", - all: "All Tests", - tests_by_assignee: "Tests by Assignee", - passed: "Passed", - failed: "Failed", - severity_distribution: "Test Severity Distribution", - }, - severity: { - low: "Low", - medium: "Medium", - high: "High", - critical: "Critical", - }, - name: "Cloud Tests", - title: "Cloud Tests", - actions: { - create: "Add Cloud Test", - clear: "Clear filters", - refresh: "Refresh", - refresh_success: "Tests refreshed successfully", - refresh_error: "Failed to refresh tests", - }, - empty: { - no_tests: { - title: "No cloud tests yet", - description: "Get started by creating your first cloud test.", - }, - no_results: { - title: "No results found", - description: "No tests match your search", - description_with_filters: "Try adjusting your filters", - }, - }, - filters: { - search: "Search tests...", - role: "Filter by vendor", - }, - register: { - title: "Add Cloud Test", - description: "Configure a new cloud compliance test.", - submit: "Create Test", - success: "Test created successfully", - invalid_json: "Invalid JSON configuration provided", + language: { + title: "Languages", + description: "Change the language used in the user interface.", + placeholder: "Select language", + }, + languages: { + en: "English", + es: "Spanish", + fr: "French", + no: "Norwegian", + pt: "Portuguese", + }, + common: { + frequency: { + daily: "Daily", + weekly: "Weekly", + monthly: "Monthly", + quarterly: "Quarterly", + yearly: "Yearly", + }, + notifications: { + inbox: "Inbox", + archive: "Archive", + archive_all: "Archive all", + no_notifications: "No new notifications", + }, + actions: { + save: "Save", + edit: "Edit", + delete: "Delete", + cancel: "Cancel", + clear: "Clear", + create: "Create", + addNew: "Add New", + send: "Send", + return: "Return", + success: "Success", + error: "Error", + next: "Next", + complete: "Complete", + }, + assignee: { + label: "Assignee", + placeholder: "Select assignee", + }, + date: { + pick: "Pick a date", + due_date: "Due Date", + }, + status: { + open: "Open", + pending: "Pending", + closed: "Closed", + archived: "Archived", + compliant: "Compliant", + non_compliant: "Non Compliant", + not_started: "Not Started", + in_progress: "In Progress", + published: "Published", + needs_review: "Needs Review", + draft: "Draft", + not_assessed: "Not Assessed", + assessed: "Assessed", + active: "Active", + inactive: "Inactive", + title: "Status", + }, + filters: { + clear: "Clear filters", + search: "Search...", + status: "Status", + department: "Department", + owner: { + label: "Assignee", + placeholder: "Filter by assignee", + }, + }, + table: { + title: "Title", + status: "Status", + assigned_to: "Assigned To", + due_date: "Due Date", + last_updated: "Last Updated", + no_results: "No results found", + }, + empty_states: { + no_results: { + title: "No results found", + title_tasks: "No tasks found", + title_risks: "No risks found", + description: "Try another search, or adjusting the filters", + description_filters: "Try another search, or adjusting the filters", + description_no_tasks: "Create a task to get started", + description_no_risks: "Create a risk to get started", + }, + no_items: { + title: "No items found", + description: "Try adjusting your search or filters", + }, + }, + pagination: { + of: "of", + items_per_page: "Items per page", + rows_per_page: "Rows per page", + page_x_of_y: "Page {{current}} of {{total}}", + go_to_first_page: "Go to first page", + go_to_previous_page: "Go to previous page", + go_to_next_page: "Go to next page", + go_to_last_page: "Go to last page", + }, + comments: { + title: "Comments", + description: "Add a comment using the form below.", + add: "New Comment", + new: "New Comment", + save: "Save Comment", + success: "Comment added successfully", + error: "Failed to add comment", + placeholder: "Write your comment here...", + empty: { + title: "No comments yet", + description: "Be the first to add a comment", + }, + }, + upload: { + fileUpload: { + uploadingText: "Uploading...", + uploadingFile: "Uploading file...", + dropFileHere: "Drop file here", + dropFileHereAlt: "Drop file here", + releaseToUpload: "Release to upload", + addFiles: "Add Files", + uploadAdditionalEvidence: "Upload a file or document", + dragDropOrClick: "Drag and drop or click to browse", + dragDropOrClickToSelect: "Drag and drop or click to select file", + maxFileSize: "Max file size: {size}MB", + }, + fileUrl: { + additionalLinks: "Additional Links", + add: "Add", + linksAdded: "{count} link{s} added", + enterUrl: "Enter URL", + addAnotherLink: "Add Another Link", + saveLinks: "Save Links", + urlBadge: "URL", + copyLink: "Copy Link", + openLink: "Open Link", + deleteLink: "Delete Link", + }, + fileCard: { + preview: "Preview", + filePreview: "File Preview: {fileName}", + previewNotAvailable: "Preview not available for this file type", + openFile: "Open File", + deleteFile: "Delete File", + deleteFileConfirmTitle: "Delete File", + deleteFileConfirmDescription: + "This action cannot be undone. The file will be permanently deleted.", + }, + fileSection: { + filesUploaded: "{count} files uploaded", + }, + }, + attachments: { + title: "Attachments", + description: "Add a file by clicking 'Add Attachment'.", + upload: "Upload attachment", + upload_description: + "Upload an attachment or add a link to an external resource.", + drop: "Drop the files here", + drop_description: + "Drop files here or click to choose files from your device.", + drop_files_description: "Files can be up to ", + empty: { + title: "No attachments", + description: "Add a file by clicking 'Add Attachment'.", + }, + toasts: { + error: "Something went wrong, please try again.", + error_uploading_files: "Cannot upload more than 1 file at a time", + error_uploading_files_multiple: "Cannot upload more than 10 files", + error_no_files_selected: "No files selected", + error_file_rejected: "File {file} was rejected", + error_failed_to_upload_files: "Failed to upload files", + error_failed_to_upload_files_multiple: "Failed to upload files", + error_failed_to_upload_files_single: "Failed to upload file", + success_uploading_files: "Files uploaded successfully", + success_uploading_files_multiple: "Files uploaded successfully", + success_uploading_files_single: "File uploaded successfully", + success_uploading_files_target: "Files uploaded", + uploading_files: "Uploading {target}...", + remove_file: "Remove file", + }, + }, + edit: "Edit", + errors: { + unexpected_error: "An unexpected error occurred", + }, + description: "Description", + last_updated: "Last Updated", + }, + header: { + discord: { + button: "Join us on Discord", + }, + feedback: { + button: "Feedback", + title: "Thank you for your feedback!", + description: "We will be back with you as soon as possible", + placeholder: "Ideas to improve this page or issues you are experiencing.", + success: "Thank you for your feedback!", + error: "Error sending feedback - try again?", + send: "Send Feedback", + }, + }, + not_found: { + title: "404 - Page not found", + description: "The page you are looking for does not exist.", + return: "Return to dashboard", + }, + theme: { + options: { + light: "Light", + dark: "Dark", + system: "System", + }, + }, + sidebar: { + overview: "Overview", + policies: "Policies", + risk: "Risk Management", + vendors: "Vendors", + integrations: "Integrations", + settings: "Settings", + evidence: "Evidence Tasks", + people: "People", + tests: "Cloud Tests", + }, + sub_pages: { + evidence: { + title: "Evidence", + list: "Evidence List", + overview: "Evidence Overview", + }, + risk: { + overview: "Risk Management", + register: "Risk Register", + risk_overview: "Risk Overview", + risk_comments: "Risk Comments", + tasks: { + task_overview: "Task Overview", + }, + }, + policies: { + all: "All Policies", + editor: "Policy Editor", + policy_details: "Policy Details", + }, + people: { + all: "People", + employee_details: "Employee Details", + }, + settings: { + members: "Team Members", + }, + frameworks: { + overview: "Frameworks", + }, + tests: { + overview: "Cloud Tests", + test_details: "Test Details", + }, + }, + auth: { + title: "Automate SOC 2, ISO 27001 and GDPR compliance with AI.", + description: + "Create a free account or log in with an existing account to continue.", + options: "More options", + google: "Continue with Google", + email: { + description: "Enter your email address to continue.", + placeholder: "Enter email address", + button: "Continue with email", + magic_link_sent: "Magic link sent", + magic_link_description: "Check your inbox for a magic link.", + magic_link_try_again: "Try again.", + success: "Email sent - check your inbox!", + error: "Error sending email - try again?", + }, + terms: + "By clicking continue, you acknowledge that you have read and agree to the Terms of Service and Privacy Policy.", + }, + onboarding: { + title: "Create an organization", + submit: "Finish setup", + setup: "Welcome to Comp AI", + description: + "Tell us a bit about your organization and what framework(s) you want to get started with.", + trigger: { + title: "Hold tight, we're creating your organization", + creating: "This may take a minute or two...", + completed: "Organization created successfully", + continue: "Continue to dashboard", + error: "Something went wrong, please try again.", + }, + fields: { + fullName: { + label: "Your Name", + placeholder: "Your full name", + }, + name: { + label: "Organization Name", + placeholder: "Your organization name", + }, + subdomain: { + label: "Subdomain", + placeholder: "example", + }, + website: { + label: "Website", + placeholder: "Your organization website", + }, + }, + success: "Thanks, you're all set!", + error: "Something went wrong, please try again.", + check_availability: "Checking availability", + available: "Available", + unavailable: "Unavailable", + creating: "Creating your organization...", + }, + overview: { + title: "Overview", + framework_chart: { + title: "Framework Progress", + }, + requirement_chart: { + title: "Compliance Status", + }, + }, + policies: { + dashboard: { + title: "Dashboard", + all: "All Policies", + policy_status: "Policy by Status", + policies_by_assignee: "Policies by Assignee", + policies_by_framework: "Policies by Framework", + sub_pages: { + overview: "Overview", + edit_policy: "Edit Policy", + }, + }, + overview: { + title: "Policy Overview", + form: { + update_policy: "Update Policy", + update_policy_description: "Update the policy title or description.", + update_policy_success: "Policy updated successfully", + update_policy_error: "Failed to update policy", + update_policy_title: "Policy Name", + review_frequency: "Review Frequency", + review_frequency_placeholder: "Select a review frequency", + review_date: "Review Date", + review_date_placeholder: "Select a review date", + required_to_sign: "Required to be signed by employees", + signature_required: "Require employees signature", + signature_not_required: "Do not ask employees to sign", + signature_requirement: "Signature Requirement", + signature_requirement_placeholder: "Select signature requirement", + }, + }, + new: { + success: "Policy successfully created", + error: "Failed to create policy", + details: "Policy Details", + title: "Enter a title for the policy", + description: "Enter a description for the policy", + }, + table: { + name: "Policy Name", + statuses: { + draft: "Draft", + published: "Published", + archived: "Archived", + }, + filters: { + owner: { + label: "Assignee", + placeholder: "Filter by assignee", + }, + }, + }, + filters: { + search: "Search policies...", + all: "All Policies", + }, + status: { + draft: "Draft", + published: "Published", + needs_review: "Needs Review", + archived: "Archived", + }, + policies: "policies", + title: "Policies", + create_new: "Create New Policy", + search_placeholder: "Search policies...", + status_filter: "Filter by status", + all_statuses: "All statuses", + no_policies_title: "No policies yet", + no_policies_description: "Get started by creating your first policy", + create_first: "Create first policy", + no_description: "No description provided", + last_updated: "Last updated: {{date}}", + save: "Save", + saving: "Saving...", + saved_success: "Policy saved successfully", + saved_error: "Failed to save policy", + }, + evidence_tasks: { + evidence_tasks: "Evidence Tasks", + overview: "Overview", + }, + risk: { + risks: "risks", + overview: "Overview", + create: "Create New Risk", + vendor: { + title: "Vendor Management", + dashboard: { + title: "Vendor Dashboard", + overview: "Vendor Overview", + vendor_status: "Vendor Status", + vendor_category: "Vendor Categories", + vendors_by_assignee: "Vendors by Assignee", + inherent_risk_description: + "Initial risk level before any controls are applied", + residual_risk_description: + "Remaining risk level after controls are applied", + }, + register: { + title: "Vendor Register", + table: { + name: "Name", + category: "Category", + status: "Status", + owner: "Owner", + }, + }, + assessment: { + title: "Vendor Assessment", + update_success: "Vendor risk assessment updated successfully", + update_error: "Failed to update vendor risk assessment", + inherent_risk: "Inherent Risk", + residual_risk: "Residual Risk", + }, + form: { + vendor_details: "Vendor Details", + vendor_name: "Name", + vendor_name_placeholder: "Enter vendor name", + vendor_website: "Website", + vendor_website_placeholder: "Enter vendor website", + vendor_description: "Description", + vendor_description_placeholder: "Enter vendor description", + vendor_category: "Category", + vendor_category_placeholder: "Select category", + vendor_status: "Status", + vendor_status_placeholder: "Select status", + create_vendor_success: "Vendor created successfully", + create_vendor_error: "Failed to create vendor", + update_vendor: "Update Vendor", + update_vendor_success: "Vendor updated successfully", + update_vendor_error: "Failed to update vendor", + add_comment: "Add Comment", + }, + table: { + name: "Name", + category: "Category", + status: "Status", + owner: "Owner", + }, + filters: { + search_placeholder: "Search vendors...", + status_placeholder: "Filter by status", + category_placeholder: "Filter by category", + owner_placeholder: "Filter by owner", + }, + empty_states: { + no_vendors: { + title: "No vendors yet", + description: "Get started by creating your first vendor", + }, + no_results: { + title: "No results found", + description: "No vendors match your search", + description_with_filters: "Try adjusting your filters", + }, + }, + actions: { + create: "Create Vendor", + }, + status: { + not_assessed: "Not Assessed", + in_progress: "In Progress", + assessed: "Assessed", + }, + category: { + cloud: "Cloud", + infrastructure: "Infrastructure", + software_as_a_service: "Software as a Service", + finance: "Finance", + marketing: "Marketing", + sales: "Sales", + hr: "HR", + other: "Other", + }, + risk_level: { + low: "Low Risk", + medium: "Medium Risk", + high: "High Risk", + unknown: "Unknown Risk", + }, + }, + dashboard: { + title: "Dashboard", + overview: "Risk Overview", + risk_status: "Risk Status", + risks_by_department: "Risks by Department", + risks_by_assignee: "Risks by Assignee", + inherent_risk_description: + "Inherent risk is calculated as likelihood * impact. Calculated before any controls are applied.", + residual_risk_description: + "Residual risk is calculated as likelihood * impact. This is the risk level after controls are applied.", + risk_assessment_description: "Compare inherent and residual risk levels", + }, + register: { + title: "Risk Register", + table: { + risk: "Risk", + }, + empty: { + no_risks: { + title: "Create a risk to get started", + description: + "Track and score risks, create and assign mitigation tasks for your team, and manage your risk register all in one simple interface.", + }, + create_risk: "Create a risk", + }, + }, + metrics: { + probability: "Probability", + impact: "Impact", + inherentRisk: "Inherent Risk", + residualRisk: "Residual Risk", + }, + form: { + update_inherent_risk: "Save Inherent Risk", + update_inherent_risk_description: + "Update the inherent risk of the risk. This is the risk level before any controls are applied.", + update_inherent_risk_success: "Inherent risk updated successfully", + update_inherent_risk_error: "Failed to update inherent risk", + update_residual_risk: "Save Residual Risk", + update_residual_risk_description: + "Update the residual risk of the risk. This is the risk level after controls are applied.", + update_residual_risk_success: "Residual risk updated successfully", + update_residual_risk_error: "Failed to update residual risk", + update_risk: "Update Risk", + update_risk_description: "Update the risk title or description.", + update_risk_success: "Risk updated successfully", + update_risk_error: "Failed to update risk", + create_risk_success: "Risk created successfully", + create_risk_error: "Failed to create risk", + risk_details: "Risk Details", + risk_title: "Risk Title", + risk_title_description: "Enter a name for the risk", + risk_description: "Description", + risk_description_description: "Enter a description for the risk", + risk_category: "Category", + risk_category_placeholder: "Select a category", + risk_department: "Department", + risk_department_placeholder: "Select a department", + risk_status: "Risk Status", + risk_status_placeholder: "Select a risk status", + }, + tasks: { + title: "Tasks", + attachments: "Attachments", + overview: "Task Overview", + form: { + title: "Task Details", + task_title: "Task Title", + status: "Task Status", + status_placeholder: "Select a task status", + task_title_description: "Enter a name for the task", + description: "Description", + description_description: "Enter a description for the task", + due_date: "Due Date", + due_date_description: "Select the due date for the task", + success: "Task created successfully", + error: "Failed to create task", + }, + sheet: { + title: "Create Task", + update: "Update Task", + update_description: "Update the task title or description.", + }, + empty: { + description_create: + "Create a mitigation task for this risk, add a treatment plan, and assign it to a team member.", + }, + }, + }, + people: { + title: "People", + details: { + taskProgress: "Task Progress", + tasks: "Tasks", + noTasks: "No tasks assigned yet", + }, + description: "Manage your team members and their roles.", + filters: { + search: "Search people...", + role: "Filter by role", + }, + actions: { + invite: "Add Employee", + clear: "Clear filters", + }, + table: { + name: "Name", + email: "Email", + department: "Department", + externalId: "External ID", + status: "Status", + }, + empty: { + no_employees: { + title: "No employees yet", + description: "Get started by inviting your first team member.", + }, + no_results: { + title: "No results found", + description: "No employees match your search", + description_with_filters: "Try adjusting your filters", + }, + }, + invite: { + title: "Add Employee", + description: "Add an employee to your organization.", + email: { + label: "Email address", + placeholder: "Enter email address", + }, + role: { + label: "Role", + placeholder: "Select a role", + }, + name: { + label: "Name", + placeholder: "Enter name", + }, + department: { + label: "Department", + placeholder: "Select a department", + }, + submit: "Add Employee", + success: "Employee added successfully", + error: "Failed to add employee", + }, + }, + settings: { + general: { + title: "General", + org_name: "Organization name", + org_name_description: + "This is your organizations visible name. You should use the legal name of your organization.", + org_name_tip: "Please use 32 characters at maximum.", + org_website: "Organization Website", + org_website_description: + "This is your organization's official website URL. Make sure to include the full URL with https://.", + org_website_tip: "Please enter a valid URL including https://", + org_website_error: "Error updating organization website", + org_website_updated: "Organization website updated", + org_delete: "Delete organization", + org_delete_description: + "Permanently remove your organization and all of its contents from the Comp AI platform. This action is not reversible - please continue with caution.", + org_delete_alert_title: "Are you absolutely sure?", + org_delete_alert_description: + "This action cannot be undone. This will permanently delete your organization and remove your data from our servers.", + org_delete_error: "Error deleting organization", + org_delete_success: "Organization deleted", + org_name_updated: "Organization name updated", + org_name_error: "Error updating organization name", + save_button: "Save", + delete_button: "Delete", + delete_confirm: "DELETE", + delete_confirm_tip: "Type DELETE to confirm.", + cancel_button: "Cancel", + }, + members: { + title: "Members", + }, + api_keys: { + title: "API Keys", + description: + "Manage API keys for programmatic access to your organization's data.", + list_title: "API Keys", + list_description: + "API keys allow secure access to your organization's data via our API.", + create: "New API Key", + create_title: "New API Key", + create_description: + "Create a new API key for programmatic access to your organization's data.", + created_title: "API Key Created", + created_description: + "Your API key has been created. Make sure to copy it now as you won't be able to see it again.", + name: "Name", + name_label: "Name", + name_placeholder: "Enter a name for this API key", + expiration: "Expiration", + expiration_placeholder: "Select expiration", + expires_label: "Expires", + expires_placeholder: "Select expiration", + expires_30days: "30 days", + expires_90days: "90 days", + expires_1year: "1 year", + expires_never: "Never", + thirty_days: "30 days", + ninety_days: "90 days", + one_year: "1 year", + your_key: "Your API Key", + api_key: "API Key", + save_warning: + "This key will only be shown once. Make sure to copy it now.", + copied: "API key copied to clipboard", + close_confirm: + "Are you sure you want to close? You won't be able to see this API key again.", + revoke_confirm: + "Are you sure you want to revoke this API key? This action cannot be undone.", + revoke_title: "Revoke API Key", + revoke: "Revoke", + created: "Created", + expires: "Expires", + last_used: "Last Used", + actions: "Actions", + never: "Never", + never_used: "Never used", + no_keys: "No API keys found. Create one to get started.", + security_note: + "API keys provide full access to your organization's data. Keep them secure and rotate them regularly.", + fetch_error: "Failed to fetch API keys", + create_error: "Failed to create API key", + revoked_success: "API key revoked successfully", + revoked_error: "Failed to revoke API key", + done: "Done", + }, + billing: { + title: "Billing", + }, + team: { + tabs: { + members: "Team Members", + invite: "Invite Members", + }, + members: { + title: "Team Members", + empty: { + no_organization: { + title: "No Organization", + description: "You are not part of any organization", + }, + no_members: { + title: "No Members", + description: "There are no active members in your organization", + }, + }, + role: { + owner: "Owner", + admin: "Admin", + member: "Member", + viewer: "Viewer", + }, + }, + invitations: { + title: "Pending Invitations", + description: "Users who have been invited but haven't accepted yet", + empty: { + no_organization: { + title: "No Organization", + description: "You are not part of any organization", + }, + no_invitations: { + title: "No Pending Invitations", + description: "There are no pending invitations", + }, + }, + invitation_sent: "Invitation sent", + actions: { + resend: "Resend Invite", + sending: "Sending Invite", + revoke: "Revoke", + revoke_title: "Revoke Invitation", + revoke_description_prefix: + "Are you sure you want to revoke the invitation for", + revoke_description_suffix: "This action cannot be undone.", + }, + toast: { + resend_success_prefix: "An invitation email has been sent to", + resend_error: "Failed to send invitation", + resend_unexpected: + "An unexpected error occurred while sending the invitation", + revoke_success_prefix: "Invitation to", + revoke_success_suffix: "has been revoked", + revoke_error: "Failed to revoke invitation", + revoke_unexpected: + "An unexpected error occurred while revoking the invitation", + }, + }, + invite: { + title: "Invite Team Member", + description: + "Send an invitation to a new team member to join your organization", + form: { + email: { + label: "Email", + placeholder: "member@example.com", + error: "Please enter a valid email address", + }, + role: { + label: "Role", + placeholder: "Select a role", + error: "Please select a role", + }, + department: { + label: "Department", + placeholder: "Select a department", + error: "Please select a department", + }, + departments: { + none: "None", + it: "IT", + hr: "HR", + admin: "Admin", + gov: "Government", + itsm: "ITSM", + qms: "QMS", + }, + }, + button: { + send: "Send Invitation", + sending: "Sending invitation...", + sent: "Invitation Sent", + }, + toast: { + error: "Failed to send invitation", + unexpected: + "An unexpected error occurred while sending the invitation", + }, + }, + member_actions: { + actions: "Actions", + change_role: "Change Role", + remove_member: "Remove Member", + remove_confirm: { + title: "Remove Team Member", + description_prefix: "Are you sure you want to remove", + description_suffix: "This action cannot be undone.", + }, + role_dialog: { + title: "Change Role", + description_prefix: "Update the role for", + role_label: "Role", + role_placeholder: "Select a role", + role_descriptions: { + admin: "Admins can manage team members and settings.", + member: + "Members can use all features but cannot manage team members.", + viewer: "Viewers can only view content without making changes.", + }, + cancel: "Cancel", + update: "Update Role", + }, + toast: { + remove_success: "has been removed from the organization", + remove_error: "Failed to remove member", + remove_unexpected: + "An unexpected error occurred while removing the member", + update_role_success: "has had their role updated to", + update_role_error: "Failed to update member role", + update_role_unexpected: + "An unexpected error occurred while updating the member's role", + }, + }, + }, + }, + tests: { + dashboard: { + overview: "Overview", + all: "All Tests", + tests_by_assignee: "Tests by Assignee", + passed: "Passed", + failed: "Failed", + severity_distribution: "Test Severity Distribution", + }, + severity: { + low: "Low", + medium: "Medium", + high: "High", + critical: "Critical", + }, + name: "Cloud Tests", + title: "Cloud Tests", + actions: { + create: "Add Cloud Test", + clear: "Clear filters", + refresh: "Refresh", + refresh_success: "Tests refreshed successfully", + refresh_error: "Failed to refresh tests", + }, + empty: { + no_tests: { + title: "No cloud tests yet", + description: "Get started by creating your first cloud test.", + }, + no_results: { + title: "No results found", + description: "No tests match your search", + description_with_filters: "Try adjusting your filters", + }, + }, + filters: { + search: "Search tests...", + role: "Filter by vendor", + }, + register: { + title: "Add Cloud Test", + description: "Configure a new cloud compliance test.", + submit: "Create Test", + success: "Test created successfully", + invalid_json: "Invalid JSON configuration provided", - title_field: { - label: "Test Title", - placeholder: "Enter test title", - }, - description_field: { - label: "Description", - placeholder: "Enter test description", - }, - provider: { - label: "Cloud Provider", - placeholder: "Select cloud provider", - }, - config: { - label: "Test Configuration", - placeholder: "Enter JSON configuration for the test", - }, - auth_config: { - label: "Authentication Configuration", - placeholder: "Enter JSON authentication configuration", - }, - }, - table: { - title: "Title", - provider: "Provider", - status: "Status", - severity: "Severity", - result: "Result", - createdAt: "Created At", - assignedUser: "Assigned User", - assignedUserEmpty: "Not Assigned", - no_results: "No results found", - }, - }, - user_menu: { - theme: "Theme", - language: "Language", - sign_out: "Sign out", - account: "Account", - support: "Support", - settings: "Settings", - teams: "Teams", - }, - frameworks: { - title: "Frameworks", - overview: { - error: "Failed to load frameworks", - loading: "Loading frameworks...", - empty: { - title: "No frameworks selected", - description: - "Select frameworks to get started with your compliance journey", - }, - progress: { - title: "Framework Progress", - empty: { - title: "No frameworks yet", - description: - "Get started by adding a compliance framework to track your progress", - action: "Add Framework", - }, - }, - grid: { - welcome: { - title: "Welcome to Comp AI", - description: - "Get started by selecting the compliance frameworks you would like to implement. We'll help you manage and track your compliance journey across multiple standards.", - action: "Get Started", - }, - title: "Select Frameworks", - version: "Version", - actions: { - clear: "Clear", - confirm: "Confirm Selection", - }, - }, - }, - controls: { - title: "Controls", - description: "Review and manage compliance controls", - table: { - status: "Status", - control: "Control", - artifacts: "Artifacts", - actions: "Actions", - }, - statuses: { - completed: "Completed", - in_progress: "In Progress", - not_started: "Not Started", - }, - }, - }, - errors: { - unexpected: "Something went wrong, please try again", - }, - editor: { - ai: { - thinking: "AI is thinking", - thinking_spinner: "AI is thinking", - edit_or_generate: "Edit or generate...", - tell_ai_what_to_do_next: "Tell AI what to do next", - request_limit_reached: "You have reached your request limit for the day.", - }, - ai_selector: { - improve: "Improve writing", - fix: "Fix grammar", - shorter: "Make shorter", - longer: "Make longer", - continue: "Continue writing", - replace: "Replace selection", - insert: "Insert below", - discard: "Discard", - }, - }, - evidence: { - title: "Evidence", - list: "All Evidence", - overview: "Evidence Overview", - edit: "Uploaded Evidence", - dashboard: { - layout: "Dashboard", - layout_back_button: "Back", - title: "Evidence Dashboard", - by_department: "By Department", - by_assignee: "By Assignee", - by_framework: "By Framework", - }, - items: "items", - status: { - up_to_date: "Up to Date", - needs_review: "Needs Review", - draft: "Draft", - empty: "Empty", - }, - departments: { - none: "Uncategorized", - admin: "Administration", - gov: "Governance", - hr: "Human Resources", - it: "Information Technology", - itsm: "IT Service Management", - qms: "Quality Management", - }, - details: { - review_section: "Review Information", - content: "Evidence Content", - }, - }, - vendors: { - title: "Vendors", - register: { - title: "Vendor Register", - }, - dashboard: { - title: "Vendor Overview", - }, - }, - dashboard: { - risk_status: "Risk Status", - risks_by_department: "Risks by Department", - vendor_status: "Vendor Status", - vendors_by_category: "Vendors by Category", - }, + title_field: { + label: "Test Title", + placeholder: "Enter test title", + }, + description_field: { + label: "Description", + placeholder: "Enter test description", + }, + provider: { + label: "Cloud Provider", + placeholder: "Select cloud provider", + }, + config: { + label: "Test Configuration", + placeholder: "Enter JSON configuration for the test", + }, + auth_config: { + label: "Authentication Configuration", + placeholder: "Enter JSON authentication configuration", + }, + }, + table: { + title: "Title", + provider: "Provider", + status: "Status", + severity: "Severity", + result: "Result", + createdAt: "Created At", + assignedUser: "Assigned User", + assignedUserEmpty: "Not Assigned", + no_results: "No results found", + }, + }, + user_menu: { + theme: "Theme", + language: "Language", + sign_out: "Sign out", + account: "Account", + support: "Support", + settings: "Settings", + teams: "Teams", + }, + frameworks: { + title: "Frameworks", + overview: { + error: "Failed to load frameworks", + loading: "Loading frameworks...", + empty: { + title: "No frameworks selected", + description: + "Select frameworks to get started with your compliance journey", + }, + progress: { + title: "Framework Progress", + empty: { + title: "No frameworks yet", + description: + "Get started by adding a compliance framework to track your progress", + action: "Add Framework", + }, + }, + grid: { + welcome: { + title: "Welcome to Comp AI", + description: + "Get started by selecting the compliance frameworks you would like to implement. We'll help you manage and track your compliance journey across multiple standards.", + action: "Get Started", + }, + title: "Select Frameworks", + version: "Version", + actions: { + clear: "Clear", + confirm: "Confirm Selection", + }, + }, + }, + controls: { + title: "Controls", + description: "Review and manage compliance controls", + table: { + status: "Status", + control: "Control", + artifacts: "Artifacts", + actions: "Actions", + }, + statuses: { + completed: "Completed", + in_progress: "In Progress", + not_started: "Not Started", + }, + }, + }, + errors: { + unexpected: "Something went wrong, please try again", + }, + editor: { + ai: { + thinking: "AI is thinking", + thinking_spinner: "AI is thinking", + edit_or_generate: "Edit or generate...", + tell_ai_what_to_do_next: "Tell AI what to do next", + request_limit_reached: "You have reached your request limit for the day.", + }, + ai_selector: { + improve: "Improve writing", + fix: "Fix grammar", + shorter: "Make shorter", + longer: "Make longer", + continue: "Continue writing", + replace: "Replace selection", + insert: "Insert below", + discard: "Discard", + }, + }, + evidence: { + title: "Evidence", + list: "All Evidence", + overview: "Evidence Overview", + edit: "Uploaded Evidence", + dashboard: { + layout: "Dashboard", + layout_back_button: "Back", + title: "Evidence Dashboard", + by_department: "By Department", + by_assignee: "By Assignee", + by_framework: "By Framework", + }, + items: "items", + status: { + up_to_date: "Up to Date", + needs_review: "Needs Review", + draft: "Draft", + empty: "Empty", + }, + departments: { + none: "Uncategorized", + admin: "Administration", + gov: "Governance", + hr: "Human Resources", + it: "Information Technology", + itsm: "IT Service Management", + qms: "Quality Management", + }, + details: { + review_section: "Review Information", + content: "Evidence Content", + }, + }, + vendors: { + title: "Vendors", + register: { + title: "Vendor Register", + }, + dashboard: { + title: "Vendor Overview", + }, + }, + dashboard: { + risk_status: "Risk Status", + risks_by_department: "Risks by Department", + vendor_status: "Vendor Status", + vendors_by_category: "Vendors by Category", + }, } as const; diff --git a/apps/app/src/locales/fr.ts b/apps/app/src/locales/fr.ts index 599a795c32..2e6a00cb08 100644 --- a/apps/app/src/locales/fr.ts +++ b/apps/app/src/locales/fr.ts @@ -1,1291 +1,1366 @@ export default { - languages: { - es: "Espagnol", - fr: "Français", - no: "Norvégien", - pt: "Portugais", - en: "Anglais" - }, - language: { - title: "Langues", - description: "Changer la langue utilisée dans l'interface utilisateur.", - placeholder: "Sélectionner la langue" - }, - common: { - actions: { - save: "Enregistrer", - edit: "Modifier", - "delete": "Supprimer", - cancel: "Annuler", - clear: "Effacer", - create: "Créer", - send: "Envoyer", - "return": "Retourner", - success: "Succès", - error: "Erreur", - next: "Suivant", - complete: "Compléter", - addNew: "Ajouter nouveau" - }, - assignee: { - label: "Assigné", - placeholder: "Sélectionner l'assigné" - }, - date: { - pick: "Choisir une date", - due_date: "Date d'échéance" - }, - status: { - open: "Ouvert", - pending: "En attente", - closed: "Fermé", - archived: "Archivé", - compliant: "Conforme", - non_compliant: "Non conforme", - not_started: "Non commencé", - in_progress: "En cours", - published: "Publié", - needs_review: "Besoin de révision", - draft: "Brouillon", - not_assessed: "Non évalué", - assessed: "Évalué", - active: "Actif", - inactive: "Inactif", - title: "Statut" - }, - filters: { - clear: "Effacer les filtres", - search: "Recherche...", - status: "Statut", - department: "Département", - owner: { - label: "Assigné", - placeholder: "Filtrer par assigné" - } - }, - table: { - title: "Titre", - status: "Statut", - assigned_to: "Assigné à", - due_date: "Date d'échéance", - last_updated: "Dernière mise à jour", - no_results: "Aucun résultat trouvé" - }, - empty_states: { - no_results: { - title: "Aucun résultat trouvé", - title_tasks: "Aucune tâche trouvée", - title_risks: "Aucun risque trouvé", - description: "Essayez une autre recherche ou ajustez les filtres", - description_filters: "Essayez une autre recherche ou ajustez les filtres", - description_no_tasks: "Créez une tâche pour commencer", - description_no_risks: "Créez un risque pour commencer" - }, - no_items: { - title: "Aucun élément trouvé", - description: "Essayez d'ajuster votre recherche ou vos filtres" - } - }, - pagination: { - of: "de", - items_per_page: "Éléments par page", - rows_per_page: "Lignes par page", - page_x_of_y: "Page {{current}} sur {{total}}", - go_to_first_page: "Aller à la première page", - go_to_previous_page: "Aller à la page précédente", - go_to_next_page: "Aller à la page suivante", - go_to_last_page: "Aller à la dernière page" - }, - comments: { - title: "Commentaires", - description: "Ajoutez un commentaire en utilisant le formulaire ci-dessous.", - add: "Nouveau commentaire", - "new": "Nouveau commentaire", - save: "Enregistrer le commentaire", - success: "Commentaire ajouté avec succès", - error: "Échec de l'ajout du commentaire", - placeholder: "Écrivez votre commentaire ici...", - empty: { - title: "Aucun commentaire pour le moment", - description: "Soyez le premier à ajouter un commentaire" - } - }, - attachments: { - title: "Pièces jointes", - description: "Ajoutez un fichier en cliquant sur 'Ajouter une pièce jointe'.", - upload: "Télécharger la pièce jointe", - upload_description: "Téléchargez une pièce jointe ou ajoutez un lien vers une ressource externe.", - drop: "Déposez les fichiers ici", - drop_description: "Déposez des fichiers ici ou cliquez pour choisir des fichiers depuis votre appareil.", - drop_files_description: "Les fichiers peuvent faire jusqu'à ", - empty: { - title: "Aucune pièce jointe", - description: "Ajoutez un fichier en cliquant sur 'Ajouter une pièce jointe'." - }, - toasts: { - error: "Quelque chose a mal tourné, veuillez réessayer.", - error_uploading_files: "Impossible de télécharger plus d'un fichier à la fois", - error_uploading_files_multiple: "Impossible de télécharger plus de 10 fichiers", - error_no_files_selected: "Aucun fichier sélectionné", - error_file_rejected: "Le fichier {file} a été rejeté", - error_failed_to_upload_files: "Échec du téléchargement des fichiers", - error_failed_to_upload_files_multiple: "Échec du téléchargement des fichiers", - error_failed_to_upload_files_single: "Échec du téléchargement du fichier", - success_uploading_files: "Fichiers téléchargés avec succès", - success_uploading_files_multiple: "Fichiers téléchargés avec succès", - success_uploading_files_single: "Fichier téléchargé avec succès", - success_uploading_files_target: "Fichiers téléchargés", - uploading_files: "Téléchargement de {target}...", - remove_file: "Supprimer le fichier" - } - }, - notifications: { - inbox: "Boîte de réception", - archive: "Archive", - archive_all: "Archiver tout", - no_notifications: "Aucune nouvelle notification" - }, - edit: "Modifier", - errors: { - unexpected_error: "Une erreur inattendue est survenue" - }, - description: "Description", - last_updated: "Dernière mise à jour", - frequency: { - daily: "Quotidien", - weekly: "Hebdomadaire", - monthly: "Mensuel", - quarterly: "Trimestriel", - yearly: "Annuel" - }, - upload: { - fileUpload: { - uploadingText: "Téléchargement...", - uploadingFile: "Téléchargement du fichier...", - dropFileHere: "Déposez le fichier ici", - dropFileHereAlt: "Déposez le fichier ici", - releaseToUpload: "Relâchez pour télécharger", - addFiles: "Ajouter des fichiers", - uploadAdditionalEvidence: "Téléchargez un fichier ou un document", - dragDropOrClick: "Faites glisser et déposez ou cliquez pour parcourir", - dragDropOrClickToSelect: "Faites glisser et déposez ou cliquez pour sélectionner le fichier", - maxFileSize: "Taille maximale du fichier : {size} Mo" - }, - fileUrl: { - additionalLinks: "Liens supplémentaires", - add: "Ajouter", - linksAdded: "{count} lien{s} ajouté{s}", - enterUrl: "Entrez l'URL", - addAnotherLink: "Ajouter un autre lien", - saveLinks: "Enregistrer les liens", - urlBadge: "URL", - copyLink: "Copier le lien", - openLink: "Ouvrir le lien", - deleteLink: "Supprimer le lien" - }, - fileCard: { - preview: "Aperçu", - filePreview: "Aperçu du fichier : {fileName}", - previewNotAvailable: "Aperçu non disponible pour ce type de fichier", - openFile: "Ouvrir le fichier", - deleteFile: "Supprimer le fichier", - deleteFileConfirmTitle: "Supprimer le fichier", - deleteFileConfirmDescription: "Cette action ne peut pas être annulée. Le fichier sera définitivement supprimé." - }, - fileSection: { - filesUploaded: "{count} fichiers téléchargés" - } - } - }, - header: { - discord: { - button: "Rejoignez-nous sur Discord" - }, - feedback: { - button: "Retour d'information", - title: "Merci pour vos retours !", - description: "Nous reviendrons vers vous dès que possible", - placeholder: "Idées pour améliorer cette page ou problèmes que vous rencontrez.", - success: "Merci pour vos retours !", - error: "Erreur lors de l'envoi des retours - réessayer ?", - send: "Envoyer des retours" - } - }, - not_found: { - title: "404 - Page non trouvée", - description: "La page que vous recherchez n'existe pas.", - "return": "Retour au tableau de bord" - }, - theme: { - options: { - light: "Clair", - dark: "Sombre", - system: "Système" - } - }, - sidebar: { - overview: "Aperçu", - policies: "Politiques", - risk: "Gestion des risques", - vendors: "Fournisseurs", - integrations: "Intégrations", - settings: "Paramètres", - evidence: "Tâches de preuve", - people: "Personnes", - tests: "Tests Cloud" - }, - auth: { - title: "Automatisez la conformité SOC 2, ISO 27001 et RGPD avec l'IA.", - description: "Créez un compte gratuit ou connectez-vous avec un compte existant pour continuer.", - options: "Plus d'options", - google: "Continuer avec Google", - email: { - description: "Entrez votre adresse e-mail pour continuer.", - placeholder: "Entrer l'adresse e-mail", - button: "Continuer avec l'e-mail", - magic_link_sent: "Lien magique envoyé", - magic_link_description: "Vérifiez votre boîte de réception pour un lien magique.", - magic_link_try_again: "Réessayer.", - success: "E-mail envoyé - vérifiez votre boîte de réception !", - error: "Erreur lors de l'envoi de l'e-mail - réessayer ?" - }, - terms: "En cliquant sur continuer, vous reconnaissez avoir lu et accepté les Conditions d'utilisation et la Politique de confidentialité." - }, - onboarding: { - title: "Créer une organisation", - setup: "Configuration", - description: "Parlez-nous un peu de votre organisation.", - fields: { - name: { - label: "Nom de l'organisation", - placeholder: "Le nom de votre organisation" - }, - website: { - label: "Site web", - placeholder: "Le site web de votre organisation" - }, - subdomain: { - label: "Sous-domaine", - placeholder: "exemple" - }, - fullName: { - label: "Votre nom", - placeholder: "Votre nom complet" - } - }, - success: "Merci, vous êtes prêt !", - error: "Quelque chose s'est mal passé, veuillez réessayer.", - unavailable: "Indisponible", - check_availability: "Vérification de la disponibilité", - available: "Disponible" - }, - overview: { - title: "Aperçu", - framework_chart: { - title: "Progrès du cadre" - }, - requirement_chart: { - title: "Statut de conformité" - } - }, - policies: { - dashboard: { - title: "Tableau de bord", - all: "Toutes les politiques", - policy_status: "Politique par statut", - policies_by_assignee: "Politiques par responsable", - policies_by_framework: "Politiques par cadre", - sub_pages: { - overview: "Aperçu", - edit_policy: "Modifier la politique" - } - }, - table: { - name: "Nom de la politique", - statuses: { - draft: "Brouillon", - published: "Publié", - archived: "Archivé" - }, - filters: { - owner: { - label: "Responsable", - placeholder: "Filtrer par responsable" - } - } - }, - filters: { - search: "Rechercher des politiques...", - all: "Toutes les politiques" - }, - status: { - draft: "Brouillon", - published: "Publié", - needs_review: "Besoin de révision", - archived: "Archivé" - }, - policies: "politiques", - title: "Politiques", - create_new: "Créer une nouvelle politique", - search_placeholder: "Rechercher des politiques...", - status_filter: "Filtrer par statut", - all_statuses: "Tous les statuts", - no_policies_title: "Aucune politique pour le moment", - no_policies_description: "Commencez par créer votre première politique", - create_first: "Créer la première politique", - no_description: "Aucune description fournie", - last_updated: "Dernière mise à jour : {{date}}", - save: "Enregistrer", - saving: "Enregistrement...", - saved_success: "Politique enregistrée avec succès", - saved_error: "Échec de l'enregistrement de la politique", - overview: { - title: "Aperçu de la politique", - form: { - update_policy: "Mettre à jour la politique", - update_policy_description: "Mettez à jour le titre ou la description de la politique.", - update_policy_success: "Politique mise à jour avec succès", - update_policy_error: "Échec de la mise à jour de la politique", - update_policy_title: "Nom de la politique", - review_frequency: "Fréquence de révision", - review_frequency_placeholder: "Sélectionnez une fréquence de révision", - review_date: "Date de révision", - review_date_placeholder: "Sélectionnez une date de révision", - required_to_sign: "Doit être signé par les employés", - signature_required: "Exiger la signature des employés", - signature_not_required: "Ne pas demander aux employés de signer", - signature_requirement: "Exigence de signature", - signature_requirement_placeholder: "Sélectionner l'exigence de signature" - } - }, - "new": { - success: "Politique créée avec succès", - error: "Échec de la création de la politique", - details: "Détails de la politique", - title: "Entrez un titre pour la politique", - description: "Entrez une description pour la politique" - } - }, - evidence_tasks: { - evidence_tasks: "Tâches de preuve", - overview: "Aperçu" - }, - risk: { - risks: "risques", - overview: "Aperçu", - create: "Créer un nouveau risque", - vendor: { - title: "Gestion des fournisseurs", - dashboard: { - title: "Tableau de bord des fournisseurs", - overview: "Aperçu des fournisseurs", - vendor_status: "Statut du fournisseur", - vendor_category: "Catégories de fournisseurs", - vendors_by_assignee: "Fournisseurs par responsable", - inherent_risk_description: "Niveau de risque initial avant l'application de tout contrôle", - residual_risk_description: "Niveau de risque restant après l'application des contrôles" - }, - register: { - title: "Registre des fournisseurs", - table: { - name: "Nom", - category: "Catégorie", - status: "Statut", - owner: "Propriétaire" - } - }, - assessment: { - title: "Évaluation des fournisseurs", - update_success: "Évaluation du risque fournisseur mise à jour avec succès", - update_error: "Échec de la mise à jour de l'évaluation du risque fournisseur", - inherent_risk: "Risque inhérent", - residual_risk: "Risque résiduel" - }, - form: { - vendor_details: "Détails du fournisseur", - vendor_name: "Nom", - vendor_name_placeholder: "Entrez le nom du fournisseur", - vendor_website: "Site web", - vendor_website_placeholder: "Entrez le site web du fournisseur", - vendor_description: "Description", - vendor_description_placeholder: "Entrez la description du fournisseur", - vendor_category: "Catégorie", - vendor_category_placeholder: "Sélectionner une catégorie", - vendor_status: "Statut", - vendor_status_placeholder: "Sélectionner un statut", - create_vendor_success: "Fournisseur créé avec succès", - create_vendor_error: "Échec de la création du fournisseur", - update_vendor: "Mettre à jour le fournisseur", - update_vendor_success: "Fournisseur mis à jour avec succès", - update_vendor_error: "Échec de la mise à jour du fournisseur", - add_comment: "Ajouter un commentaire" - }, - table: { - name: "Nom", - category: "Catégorie", - status: "Statut", - owner: "Propriétaire" - }, - filters: { - search_placeholder: "Rechercher des fournisseurs...", - status_placeholder: "Filtrer par statut", - category_placeholder: "Filtrer par catégorie", - owner_placeholder: "Filtrer par propriétaire" - }, - empty_states: { - no_vendors: { - title: "Aucun fournisseur pour le moment", - description: "Commencez par créer votre premier fournisseur" - }, - no_results: { - title: "Aucun résultat trouvé", - description: "Aucun fournisseur ne correspond à votre recherche", - description_with_filters: "Essayez d'ajuster vos filtres" - } - }, - actions: { - create: "Créer un fournisseur" - }, - status: { - not_assessed: "Non évalué", - in_progress: "En cours", - assessed: "Évalué" - }, - category: { - cloud: "Cloud", - infrastructure: "Infrastructure", - software_as_a_service: "Logiciel en tant que service", - finance: "Finance", - marketing: "Marketing", - sales: "Ventes", - hr: "Ressources humaines", - other: "Autre" - }, - risk_level: { - low: "Risque faible", - medium: "Risque moyen", - high: "Risque élevé", - unknown: "Risque inconnu" - } - }, - dashboard: { - title: "Tableau de bord", - overview: "Aperçu des risques", - risk_status: "Statut du risque", - risks_by_department: "Risques par département", - risks_by_assignee: "Risques par assigné", - inherent_risk_description: "Le risque inhérent est calculé comme probabilité * impact. Calculé avant l'application de tout contrôle.", - residual_risk_description: "Le risque résiduel est calculé comme probabilité * impact. C'est le niveau de risque après l'application des contrôles.", - risk_assessment_description: "Comparer les niveaux de risque inhérent et résiduel" - }, - register: { - title: "Registre des risques", - table: { - risk: "Risque" - }, - empty: { - no_risks: { - title: "Créez un risque pour commencer", - description: "Suivez et évaluez les risques, créez et assignez des tâches d'atténuation pour votre équipe, et gérez votre registre des risques dans une interface simple." - }, - create_risk: "Créer un risque" - } - }, - metrics: { - probability: "Probabilité", - impact: "Impact", - inherentRisk: "Risque inhérent", - residualRisk: "Risque résiduel" - }, - form: { - update_inherent_risk: "Enregistrer le risque inhérent", - update_inherent_risk_description: "Mettez à jour le risque inhérent du risque. C'est le niveau de risque avant l'application de tout contrôle.", - update_inherent_risk_success: "Risque inhérent mis à jour avec succès", - update_inherent_risk_error: "Échec de la mise à jour du risque inhérent", - update_residual_risk: "Enregistrer le risque résiduel", - update_residual_risk_description: "Mettez à jour le risque résiduel du risque. C'est le niveau de risque après l'application des contrôles.", - update_residual_risk_success: "Risque résiduel mis à jour avec succès", - update_residual_risk_error: "Échec de la mise à jour du risque résiduel", - update_risk: "Mettre à jour le risque", - update_risk_description: "Mettez à jour le titre ou la description du risque.", - update_risk_success: "Risque mis à jour avec succès", - update_risk_error: "Échec de la mise à jour du risque", - create_risk_success: "Risque créé avec succès", - create_risk_error: "Échec de la création du risque", - risk_details: "Détails du risque", - risk_title: "Titre du risque", - risk_title_description: "Entrez un nom pour le risque", - risk_description: "Description", - risk_description_description: "Entrez une description pour le risque", - risk_category: "Catégorie", - risk_category_placeholder: "Sélectionnez une catégorie", - risk_department: "Département", - risk_department_placeholder: "Sélectionnez un département", - risk_status: "Statut du risque", - risk_status_placeholder: "Sélectionnez un statut de risque" - }, - tasks: { - title: "Tâches", - attachments: "Pièces jointes", - overview: "Aperçu des tâches", - form: { - title: "Détails de la tâche", - task_title: "Titre de la tâche", - status: "Statut de la tâche", - status_placeholder: "Sélectionnez un statut de tâche", - task_title_description: "Entrez un nom pour la tâche", - description: "Description", - description_description: "Entrez une description pour la tâche", - due_date: "Date d'échéance", - due_date_description: "Sélectionnez la date d'échéance pour la tâche", - success: "Tâche créée avec succès", - error: "Échec de la création de la tâche" - }, - sheet: { - title: "Créer une tâche", - update: "Mettre à jour la tâche", - update_description: "Mettez à jour le titre ou la description de la tâche." - }, - empty: { - description_create: "Créez une tâche d'atténuation pour ce risque, ajoutez un plan de traitement et assignez-le à un membre de l'équipe." - } - } - }, - settings: { - general: { - title: "Général", - org_name: "Nom de l'organisation", - org_name_description: "C'est le nom visible de votre organisation. Vous devez utiliser le nom légal de votre organisation.", - org_name_tip: "Veuillez utiliser un maximum de 32 caractères.", - org_website: "Site Web de l'organisation", - org_website_description: "C'est l'URL du site Web officiel de votre organisation. Assurez-vous d'inclure l'URL complète avec https://.", - org_website_tip: "Veuillez entrer une URL valide incluant https://", - org_website_error: "Erreur lors de la mise à jour du site Web de l'organisation", - org_website_updated: "Site Web de l'organisation mis à jour", - org_delete: "Supprimer l'organisation", - org_delete_description: "Supprimez définitivement votre organisation et tout son contenu de la plateforme Comp AI. Cette action n'est pas réversible - veuillez continuer avec prudence.", - org_delete_alert_title: "Êtes-vous absolument sûr ?", - org_delete_alert_description: "Cette action ne peut pas être annulée. Cela supprimera définitivement votre organisation et retirera vos données de nos serveurs.", - org_delete_error: "Erreur lors de la suppression de l'organisation", - org_delete_success: "Organisation supprimée", - org_name_updated: "Nom de l'organisation mis à jour", - org_name_error: "Erreur lors de la mise à jour du nom de l'organisation", - save_button: "Enregistrer", - delete_button: "Supprimer", - delete_confirm: "SUPPRIMER", - delete_confirm_tip: "Tapez SUPPRIMER pour confirmer.", - cancel_button: "Annuler" - }, - members: { - title: "Membres" - }, - billing: { - title: "Facturation" - }, - api_keys: { - title: "Clés API", - description: "Gérez les clés API pour un accès programmatique aux données de votre organisation.", - list_title: "Clés API", - list_description: "Les clés API permettent un accès sécurisé aux données de votre organisation via notre API.", - create: "Nouvelle clé API", - create_title: "Nouvelle clé API", - create_description: "Créez une nouvelle clé API pour un accès programmatique aux données de votre organisation.", - created_title: "Clé API créée", - created_description: "Votre clé API a été créée. Assurez-vous de la copier maintenant car vous ne pourrez plus la voir.", - name: "Nom", - name_label: "Nom", - name_placeholder: "Entrez un nom pour cette clé API", - expiration: "Expiration", - expiration_placeholder: "Sélectionner l'expiration", - expires_label: "Expire", - expires_placeholder: "Sélectionner l'expiration", - expires_30days: "30 jours", - expires_90days: "90 jours", - expires_1year: "1 an", - expires_never: "Jamais", - thirty_days: "30 jours", - ninety_days: "90 jours", - one_year: "1 an", - your_key: "Votre clé API", - api_key: "Clé API", - save_warning: "Cette clé ne sera affichée qu'une seule fois. Assurez-vous de la copier maintenant.", - copied: "Clé API copiée dans le presse-papiers", - close_confirm: "Êtes-vous sûr de vouloir fermer ? Vous ne pourrez plus voir cette clé API.", - revoke_confirm: "Êtes-vous sûr de vouloir révoquer cette clé API ? Cette action ne peut pas être annulée.", - revoke_title: "Révoquer la clé API", - revoke: "Révoquer", - created: "Créée", - expires: "Expire", - last_used: "Dernière utilisation", - actions: "Actions", - never: "Jamais", - never_used: "Jamais utilisée", - no_keys: "Aucune clé API trouvée. Créez-en une pour commencer.", - security_note: "Les clés API fournissent un accès complet aux données de votre organisation. Gardez-les sécurisées et renouvelez-les régulièrement.", - fetch_error: "Échec de la récupération des clés API", - create_error: "Échec de la création de la clé API", - revoked_success: "Clé API révoquée avec succès", - revoked_error: "Échec de la révocation de la clé API", - done: "Terminé" - }, - team: { - tabs: { - members: "Membres de l'équipe", - invite: "Inviter des membres" - }, - members: { - title: "Membres de l'équipe", - empty: { - no_organization: { - title: "Aucune organisation", - description: "Vous ne faites partie d'aucune organisation" - }, - no_members: { - title: "Aucun membre", - description: "Il n'y a aucun membre actif dans votre organisation" - } - }, - role: { - owner: "Propriétaire", - admin: "Administrateur", - member: "Membre", - viewer: "Spectateur" - } - }, - invitations: { - title: "Invitations en attente", - description: "Utilisateurs qui ont été invités mais n'ont pas encore accepté", - empty: { - no_organization: { - title: "Aucune organisation", - description: "Vous ne faites partie d'aucune organisation" - }, - no_invitations: { - title: "Aucune invitation en attente", - description: "Il n'y a aucune invitation en attente" - } - }, - invitation_sent: "Invitation envoyée", - actions: { - resend: "Renvoyer l'invitation", - sending: "Envoi de l'invitation", - revoke: "Révoquer", - revoke_title: "Révoquer l'invitation", - revoke_description_prefix: "Êtes-vous sûr de vouloir révoquer l'invitation pour", - revoke_description_suffix: "Cette action ne peut pas être annulée." - }, - toast: { - resend_success_prefix: "Un e-mail d'invitation a été envoyé à", - resend_error: "Échec de l'envoi de l'invitation", - resend_unexpected: "Une erreur inattendue s'est produite lors de l'envoi de l'invitation", - revoke_success_prefix: "Invitation à", - revoke_success_suffix: "a été révoquée", - revoke_error: "Échec de la révocation de l'invitation", - revoke_unexpected: "Une erreur inattendue s'est produite lors de la révocation de l'invitation" - } - }, - invite: { - title: "Inviter un membre de l'équipe", - description: "Envoyer une invitation à un nouveau membre de l'équipe pour rejoindre votre organisation", - form: { - email: { - label: "E-mail", - placeholder: "membre@example.com", - error: "Veuillez entrer une adresse e-mail valide" - }, - role: { - label: "Rôle", - placeholder: "Sélectionner un rôle", - error: "Veuillez sélectionner un rôle" - }, - department: { - label: "Département", - placeholder: "Sélectionner un département", - error: "Veuillez sélectionner un département" - }, - departments: { - none: "Aucun", - it: "Informatique", - hr: "Ressources humaines", - admin: "Administrateur", - gov: "Gouvernement", - itsm: "ITSM", - qms: "QMS" - } - }, - button: { - send: "Envoyer l'invitation", - sending: "Envoi de l'invitation...", - sent: "Invitation envoyée" - }, - toast: { - error: "Échec de l'envoi de l'invitation", - unexpected: "Une erreur inattendue s'est produite lors de l'envoi de l'invitation" - } - }, - member_actions: { - actions: "Actions", - change_role: "Changer de rôle", - remove_member: "Supprimer un membre", - remove_confirm: { - title: "Supprimer un membre de l'équipe", - description_prefix: "Êtes-vous sûr de vouloir supprimer", - description_suffix: "Cette action ne peut pas être annulée." - }, - role_dialog: { - title: "Changer de rôle", - description_prefix: "Mettre à jour le rôle de", - role_label: "Rôle", - role_placeholder: "Sélectionner un rôle", - role_descriptions: { - admin: "Les administrateurs peuvent gérer les membres de l'équipe et les paramètres.", - member: "Les membres peuvent utiliser toutes les fonctionnalités mais ne peuvent pas gérer les membres de l'équipe.", - viewer: "Les spectateurs ne peuvent que visualiser le contenu sans apporter de modifications." - }, - cancel: "Annuler", - update: "Mettre à jour le rôle" - }, - toast: { - remove_success: "a été retiré de l'organisation", - remove_error: "Échec de la suppression du membre", - remove_unexpected: "Une erreur inattendue s'est produite lors de la suppression du membre", - update_role_success: "a eu son rôle mis à jour en", - update_role_error: "Échec de la mise à jour du rôle du membre", - update_role_unexpected: "Une erreur inattendue s'est produite lors de la mise à jour du rôle du membre" - } - } - } - }, - user_menu: { - theme: "Thème", - language: "Langue", - sign_out: "Se déconnecter", - account: "Compte", - support: "Assistance", - settings: "Paramètres", - teams: "Équipes" - }, - frameworks: { - title: "Cadres", - controls: { - title: "Contrôles", - description: "Examiner et gérer les contrôles de conformité", - table: { - status: "Statut", - control: "Contrôle", - artifacts: "Artifacts", - actions: "Actions" - }, - statuses: { - not_started: "Non commencé", - completed: "Terminé", - in_progress: "En cours" - } - }, - overview: { - error: "Échec du chargement des cadres", - loading: "Chargement des cadres...", - empty: { - title: "Aucun cadre sélectionné", - description: "Sélectionnez des cadres pour commencer votre parcours de conformité" - }, - progress: { - title: "Progression du cadre", - empty: { - title: "Pas encore de cadres", - description: "Commencez par ajouter un cadre de conformité pour suivre vos progrès", - action: "Ajouter un cadre" - } - }, - grid: { - welcome: { - title: "Bienvenue dans Comp AI", - description: "Commencez par sélectionner les cadres de conformité que vous souhaitez mettre en œuvre. Nous vous aiderons à gérer et à suivre votre parcours de conformité à travers plusieurs normes.", - action: "Commencer" - }, - title: "Sélectionner des cadres", - version: "Version", - actions: { - clear: "Effacer", - confirm: "Confirmer la sélection" - } - } - } - }, - vendor: { - title: "Tableau de bord", - register_title: "Gestion des fournisseurs", - dashboard: { - title: "Tableau de bord", - overview: "Aperçu des fournisseurs", - vendor_status: "Statut du fournisseur", - vendor_category: "Catégories de fournisseurs", - vendors_by_assignee: "Fournisseurs par responsable", - inherent_risk_description: "Niveau de risque initial avant l'application de tout contrôle", - residual_risk_description: "Niveau de risque restant après l'application des contrôles" - }, - register: { - title: "Registre des fournisseurs", - table: { - name: "Nom", - category: "Catégorie", - status: "Statut", - owner: "Propriétaire" - } - }, - category: { - cloud: "Cloud", - infrastructure: "Infrastructure", - software_as_a_service: "SaaS", - finance: "Finance", - marketing: "Marketing", - sales: "Ventes", - hr: "Ressources humaines", - other: "Autre" - }, - vendors: "fournisseurs", - form: { - vendor_details: "Détails du fournisseur", - vendor_name: "Nom", - vendor_name_placeholder: "Entrez le nom du fournisseur", - vendor_website: "Site web", - vendor_website_placeholder: "Entrez le site web du fournisseur", - vendor_description: "Description", - vendor_description_placeholder: "Entrez la description du fournisseur", - vendor_category: "Catégorie", - vendor_category_placeholder: "Sélectionner une catégorie", - vendor_status: "Statut", - vendor_status_placeholder: "Sélectionner un statut", - create_vendor_success: "Fournisseur créé avec succès", - create_vendor_error: "Échec de la création du fournisseur", - update_vendor_success: "Fournisseur mis à jour avec succès", - update_vendor_error: "Échec de la mise à jour du fournisseur", - contacts: "Contacts du fournisseur", - contact_name: "Nom du contact", - contact_email: "Email du contact", - contact_role: "Rôle du contact", - add_contact: "Ajouter un contact", - new_contact: "Nouveau contact", - min_one_contact_required: "Un fournisseur doit avoir au moins un contact" - }, - empty_states: { - no_vendors: { - title: "Pas encore de fournisseurs", - description: "Commencez par créer votre premier fournisseur" - }, - no_results: { - title: "Aucun résultat trouvé", - description: "Aucun fournisseur ne correspond à votre recherche", - description_with_filters: "Essayez d'ajuster vos filtres" - } - } - }, - people: { - title: "Personnes", - details: { - taskProgress: "Progression de la tâche", - tasks: "Tâches", - noTasks: "Aucune tâche assignée pour le moment" - }, - description: "Gérez vos membres d'équipe et leurs rôles.", - filters: { - search: "Rechercher des personnes...", - role: "Filtrer par rôle" - }, - actions: { - invite: "Ajouter un employé", - clear: "Effacer les filtres" - }, - table: { - name: "Nom", - email: "Email", - department: "Département", - externalId: "ID externe", - status: "Statut" - }, - empty: { - no_employees: { - title: "Aucun employé pour le moment", - description: "Commencez par inviter votre premier membre d'équipe." - }, - no_results: { - title: "Aucun résultat trouvé", - description: "Aucun employé ne correspond à votre recherche", - description_with_filters: "Essayez d'ajuster vos filtres" - } - }, - invite: { - title: "Ajouter un employé", - description: "Ajoutez un employé à votre organisation.", - email: { - label: "Adresse e-mail", - placeholder: "Entrez l'adresse e-mail" - }, - role: { - label: "Rôle", - placeholder: "Sélectionnez un rôle" - }, - name: { - label: "Nom", - placeholder: "Entrez le nom" - }, - department: { - label: "Département", - placeholder: "Sélectionnez un département" - }, - submit: "Ajouter un employé", - success: "Employé ajouté avec succès", - error: "Échec de l'ajout de l'employé" - } - }, - errors: { - unexpected: "Quelque chose s'est mal passé, veuillez réessayer" - }, - sub_pages: { - risk: { - overview: "Gestion des risques", - register: "Registre des risques", - risk_overview: "Aperçu des risques", - risk_comments: "Commentaires sur les risques", - tasks: { - task_overview: "Aperçu des tâches" - } - }, - policies: { - all: "Toutes les politiques", - editor: "Éditeur de politique", - policy_details: "Détails de la politique" - }, - people: { - all: "Personnes", - employee_details: "Détails de l'employé" - }, - settings: { - members: "Membres de l'équipe" - }, - frameworks: { - overview: "Cadres" - }, - evidence: { - title: "Preuve", - list: "Liste des preuves", - overview: "Aperçu des preuves" - }, - tests: { - overview: "Tests en nuage", - test_details: "Détails du test" - } - }, - editor: { - ai: { - thinking: "L'IA réfléchit", - thinking_spinner: "L'IA réfléchit", - edit_or_generate: "Modifier ou générer...", - tell_ai_what_to_do_next: "Dites à l'IA quoi faire ensuite", - request_limit_reached: "Vous avez atteint votre limite de demandes pour la journée." - }, - ai_selector: { - improve: "Améliorer l'écriture", - fix: "Corriger la grammaire", - shorter: "Raccourcir", - longer: "Allonger", - "continue": "Continuer l'écriture", - replace: "Remplacer la sélection", - insert: "Insérer ci-dessous", - discard: "Jeter" - } - }, - evidence: { - title: "Preuve", - list: "Toutes les preuves", - overview: "Aperçu des preuves", - edit: "Preuve Téléchargée", - dashboard: { - layout: "Tableau de bord", - layout_back_button: "Retour", - title: "Tableau de bord des preuves", - by_department: "Par département", - by_assignee: "Par responsable", - by_framework: "Par cadre" - }, - items: "articles", - status: { - up_to_date: "À jour", - needs_review: "Nécessite une révision", - draft: "Brouillon", - empty: "Vide" - }, - departments: { - none: "Non classé", - admin: "Administration", - gov: "Gouvernance", - hr: "Ressources humaines", - it: "Technologies de l'information", - itsm: "Gestion des services informatiques", - qms: "Gestion de la qualité" - }, - details: { - review_section: "Informations de révision", - content: "Contenu de la preuve" - } - }, - upload: { - fileSection: { - filesUploaded: "{count} fichier(s) téléchargé(s)", - upload: "{count} fichier(s) téléchargé(s)" - }, - fileUpload: { - uploadingText: "Téléchargement en cours...", - dropFileHere: "Déposez le fichier ici", - releaseToUpload: "Relâchez pour télécharger", - addFiles: "Ajouter des fichiers", - uploadAdditionalEvidence: "Télécharger un fichier", - dragDropOrClick: "Glissez-déposez ou cliquez pour télécharger", - dropFileHereAlt: "Déposez le fichier ici", - dragDropOrClickToSelect: "Glissez-déposez un fichier ici, ou cliquez pour sélectionner", - maxFileSize: "Taille maximale du fichier : {size} Mo", - uploadingFile: "Téléchargement du fichier..." - }, - fileCard: { - preview: "Aperçu", - previewNotAvailable: "Aperçu non disponible. Cliquez sur le bouton de téléchargement pour voir le fichier.", - filePreview: "Aperçu du fichier : {fileName}", - openFile: "Ouvrir le fichier", - deleteFile: "Supprimer le fichier", - deleteFileConfirmTitle: "Supprimer le fichier", - deleteFileConfirmDescription: "Êtes-vous sûr de vouloir supprimer ce fichier ? Cette action ne peut pas être annulée." - }, - fileUrl: { - additionalLinks: "Liens supplémentaires", - add: "Ajouter", - linksAdded: "{count} lien{s} ajouté{s}", - enterUrl: "Entrez l'URL", - addAnotherLink: "Ajouter un autre lien", - saveLinks: "Enregistrer les liens", - urlBadge: "URL", - copyLink: "Copier le lien", - openLink: "Ouvrir le lien", - deleteLink: "Supprimer le lien" - } - }, - tests: { - name: "Tests Cloud", - title: "Tests Cloud", - actions: { - create: "Ajouter un test Cloud", - clear: "Effacer les filtres", - refresh: "Rafraîchir", - refresh_success: "Tests actualisés avec succès", - refresh_error: "Échec de l'actualisation des tests" - }, - empty: { - no_tests: { - title: "Aucun test cloud pour le moment", - description: "Commencez par créer votre premier test dans le cloud." - }, - no_results: { - title: "Aucun résultat trouvé", - description: "Aucun test ne correspond à votre recherche", - description_with_filters: "Essayez d'ajuster vos filtres" - } - }, - filters: { - search: "Rechercher des tests...", - role: "Filtrer par fournisseur" - }, - register: { - title: "Ajouter un test Cloud", - description: "Configurer un nouveau test de conformité cloud.", - submit: "Créer un test", - success: "Test créé avec succès", - invalid_json: "Configuration JSON invalide fournie", - title_field: { - label: "Titre du test", - placeholder: "Entrez le titre du test" - }, - description_field: { - label: "Description", - placeholder: "Entrez la description du test" - }, - provider: { - label: "Fournisseur Cloud", - placeholder: "Sélectionnez le fournisseur cloud" - }, - config: { - label: "Configuration du test", - placeholder: "Entrez la configuration JSON pour le test" - }, - auth_config: { - label: "Configuration d'authentification", - placeholder: "Entrez la configuration d'authentification JSON" - } - }, - table: { - title: "Titre", - provider: "Fournisseur", - severity: "Gravité", - result: "Résultat", - createdAt: "Créé le", - assignedUser: "Utilisateur assigné", - assignedUserEmpty: "Non assigné", - no_results: "Aucun résultat trouvé", - status: "Statut" - }, - dashboard: { - overview: "Aperçu", - all: "Tous les tests", - tests_by_assignee: "Tests par le responsable", - passed: "Réussi", - failed: "Échoué", - severity_distribution: "Distribution de la gravité des tests" - }, - severity: { - low: "Faible", - medium: "Moyen", - high: "Élevé", - critical: "Critique" - } - }, - vendors: { - title: "Fournisseurs", - register: { - title: "Enregistrement des fournisseurs" - }, - dashboard: { - title: "Aperçu des fournisseurs" - } - }, - dashboard: { - risk_status: "Statut de risque", - risks_by_department: "Risques par département", - vendor_status: "Statut des fournisseurs", - vendors_by_category: "Fournisseurs par catégorie" - }, - team: { - tabs: { - members: "Membres de l'équipe", - invite: "Inviter des membres" - }, - members: { - title: "Membres de l'équipe", - empty: { - no_organization: { - title: "Aucune organisation", - description: "Vous ne faites partie d'aucune organisation" - }, - no_members: { - title: "Aucun membre", - description: "Il n'y a aucun membre actif dans votre organisation" - } - }, - role: { - owner: "Propriétaire", - admin: "Administrateur", - member: "Membre", - viewer: "Spectateur" - } - }, - invitations: { - title: "Invitations en attente", - description: "Utilisateurs qui ont été invités mais n'ont pas encore accepté", - empty: { - no_organization: { - title: "Aucune organisation", - description: "Vous ne faites partie d'aucune organisation" - }, - no_invitations: { - title: "Aucune invitation en attente", - description: "Il n'y a aucune invitation en attente" - } - }, - invitation_sent: "Invitation envoyée", - actions: { - resend: "Renvoyer l'invitation", - sending: "Envoi de l'invitation", - revoke: "Révoquer", - revoke_title: "Révoquer l'invitation", - revoke_description_prefix: "Êtes-vous sûr de vouloir révoquer l'invitation pour", - revoke_description_suffix: "Cette action ne peut pas être annulée." - }, - toast: { - resend_success_prefix: "Un e-mail d'invitation a été envoyé à", - resend_error: "Échec de l'envoi de l'invitation", - resend_unexpected: "Une erreur inattendue s'est produite lors de l'envoi de l'invitation", - revoke_success_prefix: "Invitation à", - revoke_success_suffix: "a été révoquée", - revoke_error: "Échec de la révocation de l'invitation", - revoke_unexpected: "Une erreur inattendue s'est produite lors de la révocation de l'invitation" - } - }, - invite: { - title: "Inviter un membre de l'équipe", - description: "Envoyer une invitation à un nouveau membre de l'équipe pour rejoindre votre organisation", - form: { - email: { - label: "E-mail", - placeholder: "membre@exemple.com", - error: "Veuillez entrer une adresse e-mail valide" - }, - role: { - label: "Rôle", - placeholder: "Sélectionner un rôle", - error: "Veuillez sélectionner un rôle" - }, - department: { - label: "Département", - placeholder: "Sélectionner un département", - error: "Veuillez sélectionner un département" - }, - departments: { - none: "Aucun", - it: "Informatique", - hr: "Ressources humaines", - admin: "Administrateur", - gov: "Gouvernement", - itsm: "ITSM", - qms: "QMS" - } - }, - button: { - send: "Envoyer l'invitation", - sending: "Envoi de l'invitation...", - sent: "Invitation envoyée" - }, - toast: { - error: "Échec de l'envoi de l'invitation", - unexpected: "Une erreur inattendue s'est produite lors de l'envoi de l'invitation" - } - }, - member_actions: { - actions: "Actions", - change_role: "Changer de rôle", - remove_member: "Supprimer un membre", - remove_confirm: { - title: "Supprimer un membre de l'équipe", - description_prefix: "Êtes-vous sûr de vouloir supprimer", - description_suffix: "Cette action ne peut pas être annulée." - }, - role_dialog: { - title: "Changer de rôle", - description_prefix: "Mettre à jour le rôle de", - role_label: "Rôle", - role_placeholder: "Sélectionner un rôle", - role_descriptions: { - admin: "Les administrateurs peuvent gérer les membres de l'équipe et les paramètres.", - member: "Les membres peuvent utiliser toutes les fonctionnalités mais ne peuvent pas gérer les membres de l'équipe.", - viewer: "Les spectateurs peuvent uniquement visualiser le contenu sans apporter de modifications." - }, - cancel: "Annuler", - update: "Mettre à jour le rôle" - }, - toast: { - remove_success: "a été retiré de l'organisation", - remove_error: "Échec de la suppression du membre", - remove_unexpected: "Une erreur inattendue s'est produite lors de la suppression du membre", - update_role_success: "a vu son rôle mis à jour en", - update_role_error: "Échec de la mise à jour du rôle du membre", - update_role_unexpected: "Une erreur inattendue s'est produite lors de la mise à jour du rôle du membre" - } - } - } + languages: { + es: "Espagnol", + fr: "Français", + no: "Norvégien", + pt: "Portugais", + en: "Anglais", + }, + language: { + title: "Langues", + description: "Changer la langue utilisée dans l'interface utilisateur.", + placeholder: "Sélectionner la langue", + }, + common: { + actions: { + save: "Enregistrer", + edit: "Modifier", + delete: "Supprimer", + cancel: "Annuler", + clear: "Effacer", + create: "Créer", + send: "Envoyer", + return: "Retourner", + success: "Succès", + error: "Erreur", + next: "Suivant", + complete: "Compléter", + addNew: "Ajouter nouveau", + }, + assignee: { + label: "Assigné", + placeholder: "Sélectionner l'assigné", + }, + date: { + pick: "Choisir une date", + due_date: "Date d'échéance", + }, + status: { + open: "Ouvert", + pending: "En attente", + closed: "Fermé", + archived: "Archivé", + compliant: "Conforme", + non_compliant: "Non conforme", + not_started: "Non commencé", + in_progress: "En cours", + published: "Publié", + needs_review: "Besoin de révision", + draft: "Brouillon", + not_assessed: "Non évalué", + assessed: "Évalué", + active: "Actif", + inactive: "Inactif", + title: "Statut", + }, + filters: { + clear: "Effacer les filtres", + search: "Recherche...", + status: "Statut", + department: "Département", + owner: { + label: "Assigné", + placeholder: "Filtrer par assigné", + }, + }, + table: { + title: "Titre", + status: "Statut", + assigned_to: "Assigné à", + due_date: "Date d'échéance", + last_updated: "Dernière mise à jour", + no_results: "Aucun résultat trouvé", + }, + empty_states: { + no_results: { + title: "Aucun résultat trouvé", + title_tasks: "Aucune tâche trouvée", + title_risks: "Aucun risque trouvé", + description: "Essayez une autre recherche ou ajustez les filtres", + description_filters: + "Essayez une autre recherche ou ajustez les filtres", + description_no_tasks: "Créez une tâche pour commencer", + description_no_risks: "Créez un risque pour commencer", + }, + no_items: { + title: "Aucun élément trouvé", + description: "Essayez d'ajuster votre recherche ou vos filtres", + }, + }, + pagination: { + of: "de", + items_per_page: "Éléments par page", + rows_per_page: "Lignes par page", + page_x_of_y: "Page {{current}} sur {{total}}", + go_to_first_page: "Aller à la première page", + go_to_previous_page: "Aller à la page précédente", + go_to_next_page: "Aller à la page suivante", + go_to_last_page: "Aller à la dernière page", + }, + comments: { + title: "Commentaires", + description: + "Ajoutez un commentaire en utilisant le formulaire ci-dessous.", + add: "Nouveau commentaire", + new: "Nouveau commentaire", + save: "Enregistrer le commentaire", + success: "Commentaire ajouté avec succès", + error: "Échec de l'ajout du commentaire", + placeholder: "Écrivez votre commentaire ici...", + empty: { + title: "Aucun commentaire pour le moment", + description: "Soyez le premier à ajouter un commentaire", + }, + }, + attachments: { + title: "Pièces jointes", + description: + "Ajoutez un fichier en cliquant sur 'Ajouter une pièce jointe'.", + upload: "Télécharger la pièce jointe", + upload_description: + "Téléchargez une pièce jointe ou ajoutez un lien vers une ressource externe.", + drop: "Déposez les fichiers ici", + drop_description: + "Déposez des fichiers ici ou cliquez pour choisir des fichiers depuis votre appareil.", + drop_files_description: "Les fichiers peuvent faire jusqu'à ", + empty: { + title: "Aucune pièce jointe", + description: + "Ajoutez un fichier en cliquant sur 'Ajouter une pièce jointe'.", + }, + toasts: { + error: "Quelque chose a mal tourné, veuillez réessayer.", + error_uploading_files: + "Impossible de télécharger plus d'un fichier à la fois", + error_uploading_files_multiple: + "Impossible de télécharger plus de 10 fichiers", + error_no_files_selected: "Aucun fichier sélectionné", + error_file_rejected: "Le fichier {file} a été rejeté", + error_failed_to_upload_files: "Échec du téléchargement des fichiers", + error_failed_to_upload_files_multiple: + "Échec du téléchargement des fichiers", + error_failed_to_upload_files_single: + "Échec du téléchargement du fichier", + success_uploading_files: "Fichiers téléchargés avec succès", + success_uploading_files_multiple: "Fichiers téléchargés avec succès", + success_uploading_files_single: "Fichier téléchargé avec succès", + success_uploading_files_target: "Fichiers téléchargés", + uploading_files: "Téléchargement de {target}...", + remove_file: "Supprimer le fichier", + }, + }, + notifications: { + inbox: "Boîte de réception", + archive: "Archive", + archive_all: "Archiver tout", + no_notifications: "Aucune nouvelle notification", + }, + edit: "Modifier", + errors: { + unexpected_error: "Une erreur inattendue est survenue", + }, + description: "Description", + last_updated: "Dernière mise à jour", + frequency: { + daily: "Quotidien", + weekly: "Hebdomadaire", + monthly: "Mensuel", + quarterly: "Trimestriel", + yearly: "Annuel", + }, + upload: { + fileUpload: { + uploadingText: "Téléchargement...", + uploadingFile: "Téléchargement du fichier...", + dropFileHere: "Déposez le fichier ici", + dropFileHereAlt: "Déposez le fichier ici", + releaseToUpload: "Relâchez pour télécharger", + addFiles: "Ajouter des fichiers", + uploadAdditionalEvidence: "Téléchargez un fichier ou un document", + dragDropOrClick: "Faites glisser et déposez ou cliquez pour parcourir", + dragDropOrClickToSelect: + "Faites glisser et déposez ou cliquez pour sélectionner le fichier", + maxFileSize: "Taille maximale du fichier : {size} Mo", + }, + fileUrl: { + additionalLinks: "Liens supplémentaires", + add: "Ajouter", + linksAdded: "{count} lien{s} ajouté{s}", + enterUrl: "Entrez l'URL", + addAnotherLink: "Ajouter un autre lien", + saveLinks: "Enregistrer les liens", + urlBadge: "URL", + copyLink: "Copier le lien", + openLink: "Ouvrir le lien", + deleteLink: "Supprimer le lien", + }, + fileCard: { + preview: "Aperçu", + filePreview: "Aperçu du fichier : {fileName}", + previewNotAvailable: "Aperçu non disponible pour ce type de fichier", + openFile: "Ouvrir le fichier", + deleteFile: "Supprimer le fichier", + deleteFileConfirmTitle: "Supprimer le fichier", + deleteFileConfirmDescription: + "Cette action ne peut pas être annulée. Le fichier sera définitivement supprimé.", + }, + fileSection: { + filesUploaded: "{count} fichiers téléchargés", + }, + }, + }, + header: { + discord: { + button: "Rejoignez-nous sur Discord", + }, + feedback: { + button: "Retour d'information", + title: "Merci pour vos retours !", + description: "Nous reviendrons vers vous dès que possible", + placeholder: + "Idées pour améliorer cette page ou problèmes que vous rencontrez.", + success: "Merci pour vos retours !", + error: "Erreur lors de l'envoi des retours - réessayer ?", + send: "Envoyer des retours", + }, + }, + not_found: { + title: "404 - Page non trouvée", + description: "La page que vous recherchez n'existe pas.", + return: "Retour au tableau de bord", + }, + theme: { + options: { + light: "Clair", + dark: "Sombre", + system: "Système", + }, + }, + sidebar: { + overview: "Aperçu", + policies: "Politiques", + risk: "Gestion des risques", + vendors: "Fournisseurs", + integrations: "Intégrations", + settings: "Paramètres", + evidence: "Tâches de preuve", + people: "Personnes", + tests: "Tests Cloud", + }, + auth: { + title: "Automatisez la conformité SOC 2, ISO 27001 et RGPD avec l'IA.", + description: + "Créez un compte gratuit ou connectez-vous avec un compte existant pour continuer.", + options: "Plus d'options", + google: "Continuer avec Google", + email: { + description: "Entrez votre adresse e-mail pour continuer.", + placeholder: "Entrer l'adresse e-mail", + button: "Continuer avec l'e-mail", + magic_link_sent: "Lien magique envoyé", + magic_link_description: + "Vérifiez votre boîte de réception pour un lien magique.", + magic_link_try_again: "Réessayer.", + success: "E-mail envoyé - vérifiez votre boîte de réception !", + error: "Erreur lors de l'envoi de l'e-mail - réessayer ?", + }, + terms: + "En cliquant sur continuer, vous reconnaissez avoir lu et accepté les Conditions d'utilisation et la Politique de confidentialité.", + }, + onboarding: { + title: "Créer une organisation", + setup: "Configuration", + description: "Parlez-nous un peu de votre organisation.", + fields: { + name: { + label: "Nom de l'organisation", + placeholder: "Le nom de votre organisation", + }, + website: { + label: "Site web", + placeholder: "Le site web de votre organisation", + }, + subdomain: { + label: "Sous-domaine", + placeholder: "exemple", + }, + fullName: { + label: "Votre nom", + placeholder: "Votre nom complet", + }, + }, + success: "Merci, vous êtes prêt !", + error: "Quelque chose s'est mal passé, veuillez réessayer.", + unavailable: "Indisponible", + check_availability: "Vérification de la disponibilité", + available: "Disponible", + }, + overview: { + title: "Aperçu", + framework_chart: { + title: "Progrès du cadre", + }, + requirement_chart: { + title: "Statut de conformité", + }, + }, + policies: { + dashboard: { + title: "Tableau de bord", + all: "Toutes les politiques", + policy_status: "Politique par statut", + policies_by_assignee: "Politiques par responsable", + policies_by_framework: "Politiques par cadre", + sub_pages: { + overview: "Aperçu", + edit_policy: "Modifier la politique", + }, + }, + table: { + name: "Nom de la politique", + statuses: { + draft: "Brouillon", + published: "Publié", + archived: "Archivé", + }, + filters: { + owner: { + label: "Responsable", + placeholder: "Filtrer par responsable", + }, + }, + }, + filters: { + search: "Rechercher des politiques...", + all: "Toutes les politiques", + }, + status: { + draft: "Brouillon", + published: "Publié", + needs_review: "Besoin de révision", + archived: "Archivé", + }, + policies: "politiques", + title: "Politiques", + create_new: "Créer une nouvelle politique", + search_placeholder: "Rechercher des politiques...", + status_filter: "Filtrer par statut", + all_statuses: "Tous les statuts", + no_policies_title: "Aucune politique pour le moment", + no_policies_description: "Commencez par créer votre première politique", + create_first: "Créer la première politique", + no_description: "Aucune description fournie", + last_updated: "Dernière mise à jour : {{date}}", + save: "Enregistrer", + saving: "Enregistrement...", + saved_success: "Politique enregistrée avec succès", + saved_error: "Échec de l'enregistrement de la politique", + overview: { + title: "Aperçu de la politique", + form: { + update_policy: "Mettre à jour la politique", + update_policy_description: + "Mettez à jour le titre ou la description de la politique.", + update_policy_success: "Politique mise à jour avec succès", + update_policy_error: "Échec de la mise à jour de la politique", + update_policy_title: "Nom de la politique", + review_frequency: "Fréquence de révision", + review_frequency_placeholder: "Sélectionnez une fréquence de révision", + review_date: "Date de révision", + review_date_placeholder: "Sélectionnez une date de révision", + required_to_sign: "Doit être signé par les employés", + signature_required: "Exiger la signature des employés", + signature_not_required: "Ne pas demander aux employés de signer", + signature_requirement: "Exigence de signature", + signature_requirement_placeholder: + "Sélectionner l'exigence de signature", + }, + }, + new: { + success: "Politique créée avec succès", + error: "Échec de la création de la politique", + details: "Détails de la politique", + title: "Entrez un titre pour la politique", + description: "Entrez une description pour la politique", + }, + }, + evidence_tasks: { + evidence_tasks: "Tâches de preuve", + overview: "Aperçu", + }, + risk: { + risks: "risques", + overview: "Aperçu", + create: "Créer un nouveau risque", + vendor: { + title: "Gestion des fournisseurs", + dashboard: { + title: "Tableau de bord des fournisseurs", + overview: "Aperçu des fournisseurs", + vendor_status: "Statut du fournisseur", + vendor_category: "Catégories de fournisseurs", + vendors_by_assignee: "Fournisseurs par responsable", + inherent_risk_description: + "Niveau de risque initial avant l'application de tout contrôle", + residual_risk_description: + "Niveau de risque restant après l'application des contrôles", + }, + register: { + title: "Registre des fournisseurs", + table: { + name: "Nom", + category: "Catégorie", + status: "Statut", + owner: "Propriétaire", + }, + }, + assessment: { + title: "Évaluation des fournisseurs", + update_success: + "Évaluation du risque fournisseur mise à jour avec succès", + update_error: + "Échec de la mise à jour de l'évaluation du risque fournisseur", + inherent_risk: "Risque inhérent", + residual_risk: "Risque résiduel", + }, + form: { + vendor_details: "Détails du fournisseur", + vendor_name: "Nom", + vendor_name_placeholder: "Entrez le nom du fournisseur", + vendor_website: "Site web", + vendor_website_placeholder: "Entrez le site web du fournisseur", + vendor_description: "Description", + vendor_description_placeholder: "Entrez la description du fournisseur", + vendor_category: "Catégorie", + vendor_category_placeholder: "Sélectionner une catégorie", + vendor_status: "Statut", + vendor_status_placeholder: "Sélectionner un statut", + create_vendor_success: "Fournisseur créé avec succès", + create_vendor_error: "Échec de la création du fournisseur", + update_vendor: "Mettre à jour le fournisseur", + update_vendor_success: "Fournisseur mis à jour avec succès", + update_vendor_error: "Échec de la mise à jour du fournisseur", + add_comment: "Ajouter un commentaire", + }, + table: { + name: "Nom", + category: "Catégorie", + status: "Statut", + owner: "Propriétaire", + }, + filters: { + search_placeholder: "Rechercher des fournisseurs...", + status_placeholder: "Filtrer par statut", + category_placeholder: "Filtrer par catégorie", + owner_placeholder: "Filtrer par propriétaire", + }, + empty_states: { + no_vendors: { + title: "Aucun fournisseur pour le moment", + description: "Commencez par créer votre premier fournisseur", + }, + no_results: { + title: "Aucun résultat trouvé", + description: "Aucun fournisseur ne correspond à votre recherche", + description_with_filters: "Essayez d'ajuster vos filtres", + }, + }, + actions: { + create: "Créer un fournisseur", + }, + status: { + not_assessed: "Non évalué", + in_progress: "En cours", + assessed: "Évalué", + }, + category: { + cloud: "Cloud", + infrastructure: "Infrastructure", + software_as_a_service: "Logiciel en tant que service", + finance: "Finance", + marketing: "Marketing", + sales: "Ventes", + hr: "Ressources humaines", + other: "Autre", + }, + risk_level: { + low: "Risque faible", + medium: "Risque moyen", + high: "Risque élevé", + unknown: "Risque inconnu", + }, + }, + dashboard: { + title: "Tableau de bord", + overview: "Aperçu des risques", + risk_status: "Statut du risque", + risks_by_department: "Risques par département", + risks_by_assignee: "Risques par assigné", + inherent_risk_description: + "Le risque inhérent est calculé comme probabilité * impact. Calculé avant l'application de tout contrôle.", + residual_risk_description: + "Le risque résiduel est calculé comme probabilité * impact. C'est le niveau de risque après l'application des contrôles.", + risk_assessment_description: + "Comparer les niveaux de risque inhérent et résiduel", + }, + register: { + title: "Registre des risques", + table: { + risk: "Risque", + }, + empty: { + no_risks: { + title: "Créez un risque pour commencer", + description: + "Suivez et évaluez les risques, créez et assignez des tâches d'atténuation pour votre équipe, et gérez votre registre des risques dans une interface simple.", + }, + create_risk: "Créer un risque", + }, + }, + metrics: { + probability: "Probabilité", + impact: "Impact", + inherentRisk: "Risque inhérent", + residualRisk: "Risque résiduel", + }, + form: { + update_inherent_risk: "Enregistrer le risque inhérent", + update_inherent_risk_description: + "Mettez à jour le risque inhérent du risque. C'est le niveau de risque avant l'application de tout contrôle.", + update_inherent_risk_success: "Risque inhérent mis à jour avec succès", + update_inherent_risk_error: "Échec de la mise à jour du risque inhérent", + update_residual_risk: "Enregistrer le risque résiduel", + update_residual_risk_description: + "Mettez à jour le risque résiduel du risque. C'est le niveau de risque après l'application des contrôles.", + update_residual_risk_success: "Risque résiduel mis à jour avec succès", + update_residual_risk_error: "Échec de la mise à jour du risque résiduel", + update_risk: "Mettre à jour le risque", + update_risk_description: + "Mettez à jour le titre ou la description du risque.", + update_risk_success: "Risque mis à jour avec succès", + update_risk_error: "Échec de la mise à jour du risque", + create_risk_success: "Risque créé avec succès", + create_risk_error: "Échec de la création du risque", + risk_details: "Détails du risque", + risk_title: "Titre du risque", + risk_title_description: "Entrez un nom pour le risque", + risk_description: "Description", + risk_description_description: "Entrez une description pour le risque", + risk_category: "Catégorie", + risk_category_placeholder: "Sélectionnez une catégorie", + risk_department: "Département", + risk_department_placeholder: "Sélectionnez un département", + risk_status: "Statut du risque", + risk_status_placeholder: "Sélectionnez un statut de risque", + }, + tasks: { + title: "Tâches", + attachments: "Pièces jointes", + overview: "Aperçu des tâches", + form: { + title: "Détails de la tâche", + task_title: "Titre de la tâche", + status: "Statut de la tâche", + status_placeholder: "Sélectionnez un statut de tâche", + task_title_description: "Entrez un nom pour la tâche", + description: "Description", + description_description: "Entrez une description pour la tâche", + due_date: "Date d'échéance", + due_date_description: "Sélectionnez la date d'échéance pour la tâche", + success: "Tâche créée avec succès", + error: "Échec de la création de la tâche", + }, + sheet: { + title: "Créer une tâche", + update: "Mettre à jour la tâche", + update_description: + "Mettez à jour le titre ou la description de la tâche.", + }, + empty: { + description_create: + "Créez une tâche d'atténuation pour ce risque, ajoutez un plan de traitement et assignez-le à un membre de l'équipe.", + }, + }, + }, + settings: { + general: { + title: "Général", + org_name: "Nom de l'organisation", + org_name_description: + "C'est le nom visible de votre organisation. Vous devez utiliser le nom légal de votre organisation.", + org_name_tip: "Veuillez utiliser un maximum de 32 caractères.", + org_website: "Site Web de l'organisation", + org_website_description: + "C'est l'URL du site Web officiel de votre organisation. Assurez-vous d'inclure l'URL complète avec https://.", + org_website_tip: "Veuillez entrer une URL valide incluant https://", + org_website_error: + "Erreur lors de la mise à jour du site Web de l'organisation", + org_website_updated: "Site Web de l'organisation mis à jour", + org_delete: "Supprimer l'organisation", + org_delete_description: + "Supprimez définitivement votre organisation et tout son contenu de la plateforme Comp AI. Cette action n'est pas réversible - veuillez continuer avec prudence.", + org_delete_alert_title: "Êtes-vous absolument sûr ?", + org_delete_alert_description: + "Cette action ne peut pas être annulée. Cela supprimera définitivement votre organisation et retirera vos données de nos serveurs.", + org_delete_error: "Erreur lors de la suppression de l'organisation", + org_delete_success: "Organisation supprimée", + org_name_updated: "Nom de l'organisation mis à jour", + org_name_error: "Erreur lors de la mise à jour du nom de l'organisation", + save_button: "Enregistrer", + delete_button: "Supprimer", + delete_confirm: "SUPPRIMER", + delete_confirm_tip: "Tapez SUPPRIMER pour confirmer.", + cancel_button: "Annuler", + }, + members: { + title: "Membres", + }, + billing: { + title: "Facturation", + }, + api_keys: { + title: "Clés API", + description: + "Gérez les clés API pour un accès programmatique aux données de votre organisation.", + list_title: "Clés API", + list_description: + "Les clés API permettent un accès sécurisé aux données de votre organisation via notre API.", + create: "Nouvelle clé API", + create_title: "Nouvelle clé API", + create_description: + "Créez une nouvelle clé API pour un accès programmatique aux données de votre organisation.", + created_title: "Clé API créée", + created_description: + "Votre clé API a été créée. Assurez-vous de la copier maintenant car vous ne pourrez plus la voir.", + name: "Nom", + name_label: "Nom", + name_placeholder: "Entrez un nom pour cette clé API", + expiration: "Expiration", + expiration_placeholder: "Sélectionner l'expiration", + expires_label: "Expire", + expires_placeholder: "Sélectionner l'expiration", + expires_30days: "30 jours", + expires_90days: "90 jours", + expires_1year: "1 an", + expires_never: "Jamais", + thirty_days: "30 jours", + ninety_days: "90 jours", + one_year: "1 an", + your_key: "Votre clé API", + api_key: "Clé API", + save_warning: + "Cette clé ne sera affichée qu'une seule fois. Assurez-vous de la copier maintenant.", + copied: "Clé API copiée dans le presse-papiers", + close_confirm: + "Êtes-vous sûr de vouloir fermer ? Vous ne pourrez plus voir cette clé API.", + revoke_confirm: + "Êtes-vous sûr de vouloir révoquer cette clé API ? Cette action ne peut pas être annulée.", + revoke_title: "Révoquer la clé API", + revoke: "Révoquer", + created: "Créée", + expires: "Expire", + last_used: "Dernière utilisation", + actions: "Actions", + never: "Jamais", + never_used: "Jamais utilisée", + no_keys: "Aucune clé API trouvée. Créez-en une pour commencer.", + security_note: + "Les clés API fournissent un accès complet aux données de votre organisation. Gardez-les sécurisées et renouvelez-les régulièrement.", + fetch_error: "Échec de la récupération des clés API", + create_error: "Échec de la création de la clé API", + revoked_success: "Clé API révoquée avec succès", + revoked_error: "Échec de la révocation de la clé API", + done: "Terminé", + }, + team: { + tabs: { + members: "Membres de l'équipe", + invite: "Inviter des membres", + }, + members: { + title: "Membres de l'équipe", + empty: { + no_organization: { + title: "Aucune organisation", + description: "Vous ne faites partie d'aucune organisation", + }, + no_members: { + title: "Aucun membre", + description: "Il n'y a aucun membre actif dans votre organisation", + }, + }, + role: { + owner: "Propriétaire", + admin: "Administrateur", + member: "Membre", + viewer: "Spectateur", + }, + }, + invitations: { + title: "Invitations en attente", + description: + "Utilisateurs qui ont été invités mais n'ont pas encore accepté", + empty: { + no_organization: { + title: "Aucune organisation", + description: "Vous ne faites partie d'aucune organisation", + }, + no_invitations: { + title: "Aucune invitation en attente", + description: "Il n'y a aucune invitation en attente", + }, + }, + invitation_sent: "Invitation envoyée", + actions: { + resend: "Renvoyer l'invitation", + sending: "Envoi de l'invitation", + revoke: "Révoquer", + revoke_title: "Révoquer l'invitation", + revoke_description_prefix: + "Êtes-vous sûr de vouloir révoquer l'invitation pour", + revoke_description_suffix: "Cette action ne peut pas être annulée.", + }, + toast: { + resend_success_prefix: "Un e-mail d'invitation a été envoyé à", + resend_error: "Échec de l'envoi de l'invitation", + resend_unexpected: + "Une erreur inattendue s'est produite lors de l'envoi de l'invitation", + revoke_success_prefix: "Invitation à", + revoke_success_suffix: "a été révoquée", + revoke_error: "Échec de la révocation de l'invitation", + revoke_unexpected: + "Une erreur inattendue s'est produite lors de la révocation de l'invitation", + }, + }, + invite: { + title: "Inviter un membre de l'équipe", + description: + "Envoyer une invitation à un nouveau membre de l'équipe pour rejoindre votre organisation", + form: { + email: { + label: "E-mail", + placeholder: "membre@example.com", + error: "Veuillez entrer une adresse e-mail valide", + }, + role: { + label: "Rôle", + placeholder: "Sélectionner un rôle", + error: "Veuillez sélectionner un rôle", + }, + department: { + label: "Département", + placeholder: "Sélectionner un département", + error: "Veuillez sélectionner un département", + }, + departments: { + none: "Aucun", + it: "Informatique", + hr: "Ressources humaines", + admin: "Administrateur", + gov: "Gouvernement", + itsm: "ITSM", + qms: "QMS", + }, + }, + button: { + send: "Envoyer l'invitation", + sending: "Envoi de l'invitation...", + sent: "Invitation envoyée", + }, + toast: { + error: "Échec de l'envoi de l'invitation", + unexpected: + "Une erreur inattendue s'est produite lors de l'envoi de l'invitation", + }, + }, + member_actions: { + actions: "Actions", + change_role: "Changer de rôle", + remove_member: "Supprimer un membre", + remove_confirm: { + title: "Supprimer un membre de l'équipe", + description_prefix: "Êtes-vous sûr de vouloir supprimer", + description_suffix: "Cette action ne peut pas être annulée.", + }, + role_dialog: { + title: "Changer de rôle", + description_prefix: "Mettre à jour le rôle de", + role_label: "Rôle", + role_placeholder: "Sélectionner un rôle", + role_descriptions: { + admin: + "Les administrateurs peuvent gérer les membres de l'équipe et les paramètres.", + member: + "Les membres peuvent utiliser toutes les fonctionnalités mais ne peuvent pas gérer les membres de l'équipe.", + viewer: + "Les spectateurs ne peuvent que visualiser le contenu sans apporter de modifications.", + }, + cancel: "Annuler", + update: "Mettre à jour le rôle", + }, + toast: { + remove_success: "a été retiré de l'organisation", + remove_error: "Échec de la suppression du membre", + remove_unexpected: + "Une erreur inattendue s'est produite lors de la suppression du membre", + update_role_success: "a eu son rôle mis à jour en", + update_role_error: "Échec de la mise à jour du rôle du membre", + update_role_unexpected: + "Une erreur inattendue s'est produite lors de la mise à jour du rôle du membre", + }, + }, + }, + }, + user_menu: { + theme: "Thème", + language: "Langue", + sign_out: "Se déconnecter", + account: "Compte", + support: "Assistance", + settings: "Paramètres", + teams: "Équipes", + }, + frameworks: { + title: "Cadres", + controls: { + title: "Contrôles", + description: "Examiner et gérer les contrôles de conformité", + table: { + status: "Statut", + control: "Contrôle", + artifacts: "Artifacts", + actions: "Actions", + }, + statuses: { + not_started: "Non commencé", + completed: "Terminé", + in_progress: "En cours", + }, + }, + overview: { + error: "Échec du chargement des cadres", + loading: "Chargement des cadres...", + empty: { + title: "Aucun cadre sélectionné", + description: + "Sélectionnez des cadres pour commencer votre parcours de conformité", + }, + progress: { + title: "Progression du cadre", + empty: { + title: "Pas encore de cadres", + description: + "Commencez par ajouter un cadre de conformité pour suivre vos progrès", + action: "Ajouter un cadre", + }, + }, + grid: { + welcome: { + title: "Bienvenue dans Comp AI", + description: + "Commencez par sélectionner les cadres de conformité que vous souhaitez mettre en œuvre. Nous vous aiderons à gérer et à suivre votre parcours de conformité à travers plusieurs normes.", + action: "Commencer", + }, + title: "Sélectionner des cadres", + version: "Version", + actions: { + clear: "Effacer", + confirm: "Confirmer la sélection", + }, + }, + }, + }, + vendor: { + title: "Tableau de bord", + register_title: "Gestion des fournisseurs", + dashboard: { + title: "Tableau de bord", + overview: "Aperçu des fournisseurs", + vendor_status: "Statut du fournisseur", + vendor_category: "Catégories de fournisseurs", + vendors_by_assignee: "Fournisseurs par responsable", + inherent_risk_description: + "Niveau de risque initial avant l'application de tout contrôle", + residual_risk_description: + "Niveau de risque restant après l'application des contrôles", + }, + register: { + title: "Registre des fournisseurs", + table: { + name: "Nom", + category: "Catégorie", + status: "Statut", + owner: "Propriétaire", + }, + }, + category: { + cloud: "Cloud", + infrastructure: "Infrastructure", + software_as_a_service: "SaaS", + finance: "Finance", + marketing: "Marketing", + sales: "Ventes", + hr: "Ressources humaines", + other: "Autre", + }, + vendors: "fournisseurs", + form: { + vendor_details: "Détails du fournisseur", + vendor_name: "Nom", + vendor_name_placeholder: "Entrez le nom du fournisseur", + vendor_website: "Site web", + vendor_website_placeholder: "Entrez le site web du fournisseur", + vendor_description: "Description", + vendor_description_placeholder: "Entrez la description du fournisseur", + vendor_category: "Catégorie", + vendor_category_placeholder: "Sélectionner une catégorie", + vendor_status: "Statut", + vendor_status_placeholder: "Sélectionner un statut", + create_vendor_success: "Fournisseur créé avec succès", + create_vendor_error: "Échec de la création du fournisseur", + update_vendor_success: "Fournisseur mis à jour avec succès", + update_vendor_error: "Échec de la mise à jour du fournisseur", + contacts: "Contacts du fournisseur", + contact_name: "Nom du contact", + contact_email: "Email du contact", + contact_role: "Rôle du contact", + add_contact: "Ajouter un contact", + new_contact: "Nouveau contact", + min_one_contact_required: "Un fournisseur doit avoir au moins un contact", + }, + empty_states: { + no_vendors: { + title: "Pas encore de fournisseurs", + description: "Commencez par créer votre premier fournisseur", + }, + no_results: { + title: "Aucun résultat trouvé", + description: "Aucun fournisseur ne correspond à votre recherche", + description_with_filters: "Essayez d'ajuster vos filtres", + }, + }, + }, + people: { + title: "Personnes", + details: { + taskProgress: "Progression de la tâche", + tasks: "Tâches", + noTasks: "Aucune tâche assignée pour le moment", + }, + description: "Gérez vos membres d'équipe et leurs rôles.", + filters: { + search: "Rechercher des personnes...", + role: "Filtrer par rôle", + }, + actions: { + invite: "Ajouter un employé", + clear: "Effacer les filtres", + }, + table: { + name: "Nom", + email: "Email", + department: "Département", + externalId: "ID externe", + status: "Statut", + }, + empty: { + no_employees: { + title: "Aucun employé pour le moment", + description: "Commencez par inviter votre premier membre d'équipe.", + }, + no_results: { + title: "Aucun résultat trouvé", + description: "Aucun employé ne correspond à votre recherche", + description_with_filters: "Essayez d'ajuster vos filtres", + }, + }, + invite: { + title: "Ajouter un employé", + description: "Ajoutez un employé à votre organisation.", + email: { + label: "Adresse e-mail", + placeholder: "Entrez l'adresse e-mail", + }, + role: { + label: "Rôle", + placeholder: "Sélectionnez un rôle", + }, + name: { + label: "Nom", + placeholder: "Entrez le nom", + }, + department: { + label: "Département", + placeholder: "Sélectionnez un département", + }, + submit: "Ajouter un employé", + success: "Employé ajouté avec succès", + error: "Échec de l'ajout de l'employé", + }, + }, + errors: { + unexpected: "Quelque chose s'est mal passé, veuillez réessayer", + }, + sub_pages: { + risk: { + overview: "Gestion des risques", + register: "Registre des risques", + risk_overview: "Aperçu des risques", + risk_comments: "Commentaires sur les risques", + tasks: { + task_overview: "Aperçu des tâches", + }, + }, + policies: { + all: "Toutes les politiques", + editor: "Éditeur de politique", + policy_details: "Détails de la politique", + }, + people: { + all: "Personnes", + employee_details: "Détails de l'employé", + }, + settings: { + members: "Membres de l'équipe", + }, + frameworks: { + overview: "Cadres", + }, + evidence: { + title: "Preuve", + list: "Liste des preuves", + overview: "Aperçu des preuves", + }, + tests: { + overview: "Tests en nuage", + test_details: "Détails du test", + }, + }, + editor: { + ai: { + thinking: "L'IA réfléchit", + thinking_spinner: "L'IA réfléchit", + edit_or_generate: "Modifier ou générer...", + tell_ai_what_to_do_next: "Dites à l'IA quoi faire ensuite", + request_limit_reached: + "Vous avez atteint votre limite de demandes pour la journée.", + }, + ai_selector: { + improve: "Améliorer l'écriture", + fix: "Corriger la grammaire", + shorter: "Raccourcir", + longer: "Allonger", + continue: "Continuer l'écriture", + replace: "Remplacer la sélection", + insert: "Insérer ci-dessous", + discard: "Jeter", + }, + }, + evidence: { + title: "Preuve", + list: "Toutes les preuves", + overview: "Aperçu des preuves", + edit: "Preuve Téléchargée", + dashboard: { + layout: "Tableau de bord", + layout_back_button: "Retour", + title: "Tableau de bord des preuves", + by_department: "Par département", + by_assignee: "Par responsable", + by_framework: "Par cadre", + }, + items: "articles", + status: { + up_to_date: "À jour", + needs_review: "Nécessite une révision", + draft: "Brouillon", + empty: "Vide", + }, + departments: { + none: "Non classé", + admin: "Administration", + gov: "Gouvernance", + hr: "Ressources humaines", + it: "Technologies de l'information", + itsm: "Gestion des services informatiques", + qms: "Gestion de la qualité", + }, + details: { + review_section: "Informations de révision", + content: "Contenu de la preuve", + }, + }, + upload: { + fileSection: { + filesUploaded: "{count} fichier(s) téléchargé(s)", + upload: "{count} fichier(s) téléchargé(s)", + }, + fileUpload: { + uploadingText: "Téléchargement en cours...", + dropFileHere: "Déposez le fichier ici", + releaseToUpload: "Relâchez pour télécharger", + addFiles: "Ajouter des fichiers", + uploadAdditionalEvidence: "Télécharger un fichier", + dragDropOrClick: "Glissez-déposez ou cliquez pour télécharger", + dropFileHereAlt: "Déposez le fichier ici", + dragDropOrClickToSelect: + "Glissez-déposez un fichier ici, ou cliquez pour sélectionner", + maxFileSize: "Taille maximale du fichier : {size} Mo", + uploadingFile: "Téléchargement du fichier...", + }, + fileCard: { + preview: "Aperçu", + previewNotAvailable: + "Aperçu non disponible. Cliquez sur le bouton de téléchargement pour voir le fichier.", + filePreview: "Aperçu du fichier : {fileName}", + openFile: "Ouvrir le fichier", + deleteFile: "Supprimer le fichier", + deleteFileConfirmTitle: "Supprimer le fichier", + deleteFileConfirmDescription: + "Êtes-vous sûr de vouloir supprimer ce fichier ? Cette action ne peut pas être annulée.", + }, + fileUrl: { + additionalLinks: "Liens supplémentaires", + add: "Ajouter", + linksAdded: "{count} lien{s} ajouté{s}", + enterUrl: "Entrez l'URL", + addAnotherLink: "Ajouter un autre lien", + saveLinks: "Enregistrer les liens", + urlBadge: "URL", + copyLink: "Copier le lien", + openLink: "Ouvrir le lien", + deleteLink: "Supprimer le lien", + }, + }, + tests: { + name: "Tests Cloud", + title: "Tests Cloud", + actions: { + create: "Ajouter un test Cloud", + clear: "Effacer les filtres", + refresh: "Rafraîchir", + refresh_success: "Tests actualisés avec succès", + refresh_error: "Échec de l'actualisation des tests", + }, + empty: { + no_tests: { + title: "Aucun test cloud pour le moment", + description: "Commencez par créer votre premier test dans le cloud.", + }, + no_results: { + title: "Aucun résultat trouvé", + description: "Aucun test ne correspond à votre recherche", + description_with_filters: "Essayez d'ajuster vos filtres", + }, + }, + filters: { + search: "Rechercher des tests...", + role: "Filtrer par fournisseur", + }, + register: { + title: "Ajouter un test Cloud", + description: "Configurer un nouveau test de conformité cloud.", + submit: "Créer un test", + success: "Test créé avec succès", + invalid_json: "Configuration JSON invalide fournie", + title_field: { + label: "Titre du test", + placeholder: "Entrez le titre du test", + }, + description_field: { + label: "Description", + placeholder: "Entrez la description du test", + }, + provider: { + label: "Fournisseur Cloud", + placeholder: "Sélectionnez le fournisseur cloud", + }, + config: { + label: "Configuration du test", + placeholder: "Entrez la configuration JSON pour le test", + }, + auth_config: { + label: "Configuration d'authentification", + placeholder: "Entrez la configuration d'authentification JSON", + }, + }, + table: { + title: "Titre", + provider: "Fournisseur", + severity: "Gravité", + result: "Résultat", + createdAt: "Créé le", + assignedUser: "Utilisateur assigné", + assignedUserEmpty: "Non assigné", + no_results: "Aucun résultat trouvé", + status: "Statut", + }, + dashboard: { + overview: "Aperçu", + all: "Tous les tests", + tests_by_assignee: "Tests par le responsable", + passed: "Réussi", + failed: "Échoué", + severity_distribution: "Distribution de la gravité des tests", + }, + severity: { + low: "Faible", + medium: "Moyen", + high: "Élevé", + critical: "Critique", + }, + }, + vendors: { + title: "Fournisseurs", + register: { + title: "Enregistrement des fournisseurs", + }, + dashboard: { + title: "Aperçu des fournisseurs", + }, + }, + dashboard: { + risk_status: "Statut de risque", + risks_by_department: "Risques par département", + vendor_status: "Statut des fournisseurs", + vendors_by_category: "Fournisseurs par catégorie", + }, + team: { + tabs: { + members: "Membres de l'équipe", + invite: "Inviter des membres", + }, + members: { + title: "Membres de l'équipe", + empty: { + no_organization: { + title: "Aucune organisation", + description: "Vous ne faites partie d'aucune organisation", + }, + no_members: { + title: "Aucun membre", + description: "Il n'y a aucun membre actif dans votre organisation", + }, + }, + role: { + owner: "Propriétaire", + admin: "Administrateur", + member: "Membre", + viewer: "Spectateur", + }, + }, + invitations: { + title: "Invitations en attente", + description: + "Utilisateurs qui ont été invités mais n'ont pas encore accepté", + empty: { + no_organization: { + title: "Aucune organisation", + description: "Vous ne faites partie d'aucune organisation", + }, + no_invitations: { + title: "Aucune invitation en attente", + description: "Il n'y a aucune invitation en attente", + }, + }, + invitation_sent: "Invitation envoyée", + actions: { + resend: "Renvoyer l'invitation", + sending: "Envoi de l'invitation", + revoke: "Révoquer", + revoke_title: "Révoquer l'invitation", + revoke_description_prefix: + "Êtes-vous sûr de vouloir révoquer l'invitation pour", + revoke_description_suffix: "Cette action ne peut pas être annulée.", + }, + toast: { + resend_success_prefix: "Un e-mail d'invitation a été envoyé à", + resend_error: "Échec de l'envoi de l'invitation", + resend_unexpected: + "Une erreur inattendue s'est produite lors de l'envoi de l'invitation", + revoke_success_prefix: "Invitation à", + revoke_success_suffix: "a été révoquée", + revoke_error: "Échec de la révocation de l'invitation", + revoke_unexpected: + "Une erreur inattendue s'est produite lors de la révocation de l'invitation", + }, + }, + invite: { + title: "Inviter un membre de l'équipe", + description: + "Envoyer une invitation à un nouveau membre de l'équipe pour rejoindre votre organisation", + form: { + email: { + label: "E-mail", + placeholder: "membre@exemple.com", + error: "Veuillez entrer une adresse e-mail valide", + }, + role: { + label: "Rôle", + placeholder: "Sélectionner un rôle", + error: "Veuillez sélectionner un rôle", + }, + department: { + label: "Département", + placeholder: "Sélectionner un département", + error: "Veuillez sélectionner un département", + }, + departments: { + none: "Aucun", + it: "Informatique", + hr: "Ressources humaines", + admin: "Administrateur", + gov: "Gouvernement", + itsm: "ITSM", + qms: "QMS", + }, + }, + button: { + send: "Envoyer l'invitation", + sending: "Envoi de l'invitation...", + sent: "Invitation envoyée", + }, + toast: { + error: "Échec de l'envoi de l'invitation", + unexpected: + "Une erreur inattendue s'est produite lors de l'envoi de l'invitation", + }, + }, + member_actions: { + actions: "Actions", + change_role: "Changer de rôle", + remove_member: "Supprimer un membre", + remove_confirm: { + title: "Supprimer un membre de l'équipe", + description_prefix: "Êtes-vous sûr de vouloir supprimer", + description_suffix: "Cette action ne peut pas être annulée.", + }, + role_dialog: { + title: "Changer de rôle", + description_prefix: "Mettre à jour le rôle de", + role_label: "Rôle", + role_placeholder: "Sélectionner un rôle", + role_descriptions: { + admin: + "Les administrateurs peuvent gérer les membres de l'équipe et les paramètres.", + member: + "Les membres peuvent utiliser toutes les fonctionnalités mais ne peuvent pas gérer les membres de l'équipe.", + viewer: + "Les spectateurs peuvent uniquement visualiser le contenu sans apporter de modifications.", + }, + cancel: "Annuler", + update: "Mettre à jour le rôle", + }, + toast: { + remove_success: "a été retiré de l'organisation", + remove_error: "Échec de la suppression du membre", + remove_unexpected: + "Une erreur inattendue s'est produite lors de la suppression du membre", + update_role_success: "a vu son rôle mis à jour en", + update_role_error: "Échec de la mise à jour du rôle du membre", + update_role_unexpected: + "Une erreur inattendue s'est produite lors de la mise à jour du rôle du membre", + }, + }, + }, } as const; diff --git a/apps/app/src/types/actions.ts b/apps/app/src/types/actions.ts new file mode 100644 index 0000000000..54357c0112 --- /dev/null +++ b/apps/app/src/types/actions.ts @@ -0,0 +1,5 @@ +export interface ActionResponse { + success: boolean; + data?: T; + error?: string; +} diff --git a/packages/db/prisma/migrations/20250318161548_drop_subdomain_add_setup/migration.sql b/packages/db/prisma/migrations/20250318161548_drop_subdomain_add_setup/migration.sql new file mode 100644 index 0000000000..5f15bbd109 --- /dev/null +++ b/packages/db/prisma/migrations/20250318161548_drop_subdomain_add_setup/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - You are about to drop the column `subdomain` on the `Organization` table. All the data in the column will be lost. + +*/ +-- DropIndex +DROP INDEX "Organization_subdomain_key"; + +-- AlterTable +ALTER TABLE "Organization" DROP COLUMN "subdomain", +ADD COLUMN "setup" BOOLEAN NOT NULL DEFAULT false; diff --git a/packages/db/prisma/schema/schema.prisma b/packages/db/prisma/schema/schema.prisma index abb9430371..987f1131a7 100644 --- a/packages/db/prisma/schema/schema.prisma +++ b/packages/db/prisma/schema/schema.prisma @@ -77,7 +77,7 @@ model Organization { id String @id @default(cuid()) stripeCustomerId String? name String - subdomain String @unique + setup Boolean @default(false) website String tier Tier @default(free) policiesCreated Boolean @default(false) From f7f685e6a9b2b570af032c942ba50c6802ab6709 Mon Sep 17 00:00:00 2001 From: Languine Bot Date: Tue, 18 Mar 2025 19:07:38 +0000 Subject: [PATCH 2/2] chore: (i18n) update translations using Languine.ai --- apps/app/languine.lock | 11 +- apps/app/src/locales/es.ts | 15 +- apps/app/src/locales/fr.ts | 2662 ++++++++++++++++++------------------ apps/app/src/locales/no.ts | 15 +- apps/app/src/locales/pt.ts | 15 +- 5 files changed, 1343 insertions(+), 1375 deletions(-) diff --git a/apps/app/languine.lock b/apps/app/languine.lock index 9109e6f58f..386b7d7d77 100644 --- a/apps/app/languine.lock +++ b/apps/app/languine.lock @@ -199,8 +199,14 @@ files: auth.email.error: ed239af1d84d25fd9bb99251114b488c auth.terms: bb73614638d9a468c878278692a71a5e onboarding.title: deecac09e6560d6f0f98401d9852d514 - onboarding.setup: ad2376beebecdcf7846ba973fa1a005b - onboarding.description: 7cd2a7692cfacbbea2045f2e6416b805 + onboarding.submit: 85a18a0474d135c1f4c6da6c383a2d81 + onboarding.setup: 54efd9605180a9a74e6d1a53529f858e + onboarding.description: fcfd2a71d9095421e290f1b1229d1c6e + onboarding.trigger.title: c0a683b95c32208c79a08309225647ee + onboarding.trigger.creating: 5c1f94a8b72c6f33012732e6b8ba44e5 + onboarding.trigger.completed: 435e0545f0bbfbbbe640e9e12e54c663 + onboarding.trigger.continue: 01739cdc86e75ee2fea0aabcdeb2e557 + onboarding.trigger.error: 9e78ff392dee43a6c78a8c4e29ff4dc2 onboarding.fields.fullName.label: 614cffa523202658a898e34a5d94d05e onboarding.fields.fullName.placeholder: df1fc96e5401396fe01e24363a9ec40d onboarding.fields.name.label: c1ca926603dc454ba981aa514db8402b @@ -214,6 +220,7 @@ files: onboarding.check_availability: 118740af1c4521b917e29791d661db82 onboarding.available: 78945de8de090e90045d299651a68a9b onboarding.unavailable: 453e6aa38d87b28ccae545967c53004f + onboarding.creating: 3ab00f2a7303dc9e1aed05d535677b7e overview.title: 3b878279a04dc47d60932cb294d96259 overview.framework_chart.title: 9c53d5a3379ee2c9bc7c1c55f54724b9 overview.requirement_chart.title: 69d9304854b767112e32573d7eeddc24 diff --git a/apps/app/src/locales/es.ts b/apps/app/src/locales/es.ts index 98e161c3bc..9b3041ef7f 100644 --- a/apps/app/src/locales/es.ts +++ b/apps/app/src/locales/es.ts @@ -253,8 +253,8 @@ export default { }, onboarding: { title: "Crear una organización", - setup: "Configuración", - description: "Cuéntanos un poco sobre tu organización.", + setup: "Bienvenido a Comp AI", + description: "Cuéntanos un poco sobre tu organización y con qué marco(s) deseas comenzar.", fields: { name: { label: "Nombre de la Organización", @@ -277,7 +277,16 @@ export default { error: "Algo salió mal, por favor intenta de nuevo.", unavailable: "No disponible", check_availability: "Verificando disponibilidad", - available: "Disponible" + available: "Disponible", + submit: "Finalizar configuración", + trigger: { + title: "Espera un momento, estamos creando tu organización", + creating: "Esto puede tardar uno o dos minutos...", + completed: "Organización creada con éxito", + "continue": "Continuar al panel de control", + error: "Algo salió mal, por favor intenta de nuevo." + }, + creating: "Creando tu organización..." }, overview: { title: "Resumen", diff --git a/apps/app/src/locales/fr.ts b/apps/app/src/locales/fr.ts index 2e6a00cb08..3b363aa3d5 100644 --- a/apps/app/src/locales/fr.ts +++ b/apps/app/src/locales/fr.ts @@ -1,1366 +1,1300 @@ export default { - languages: { - es: "Espagnol", - fr: "Français", - no: "Norvégien", - pt: "Portugais", - en: "Anglais", - }, - language: { - title: "Langues", - description: "Changer la langue utilisée dans l'interface utilisateur.", - placeholder: "Sélectionner la langue", - }, - common: { - actions: { - save: "Enregistrer", - edit: "Modifier", - delete: "Supprimer", - cancel: "Annuler", - clear: "Effacer", - create: "Créer", - send: "Envoyer", - return: "Retourner", - success: "Succès", - error: "Erreur", - next: "Suivant", - complete: "Compléter", - addNew: "Ajouter nouveau", - }, - assignee: { - label: "Assigné", - placeholder: "Sélectionner l'assigné", - }, - date: { - pick: "Choisir une date", - due_date: "Date d'échéance", - }, - status: { - open: "Ouvert", - pending: "En attente", - closed: "Fermé", - archived: "Archivé", - compliant: "Conforme", - non_compliant: "Non conforme", - not_started: "Non commencé", - in_progress: "En cours", - published: "Publié", - needs_review: "Besoin de révision", - draft: "Brouillon", - not_assessed: "Non évalué", - assessed: "Évalué", - active: "Actif", - inactive: "Inactif", - title: "Statut", - }, - filters: { - clear: "Effacer les filtres", - search: "Recherche...", - status: "Statut", - department: "Département", - owner: { - label: "Assigné", - placeholder: "Filtrer par assigné", - }, - }, - table: { - title: "Titre", - status: "Statut", - assigned_to: "Assigné à", - due_date: "Date d'échéance", - last_updated: "Dernière mise à jour", - no_results: "Aucun résultat trouvé", - }, - empty_states: { - no_results: { - title: "Aucun résultat trouvé", - title_tasks: "Aucune tâche trouvée", - title_risks: "Aucun risque trouvé", - description: "Essayez une autre recherche ou ajustez les filtres", - description_filters: - "Essayez une autre recherche ou ajustez les filtres", - description_no_tasks: "Créez une tâche pour commencer", - description_no_risks: "Créez un risque pour commencer", - }, - no_items: { - title: "Aucun élément trouvé", - description: "Essayez d'ajuster votre recherche ou vos filtres", - }, - }, - pagination: { - of: "de", - items_per_page: "Éléments par page", - rows_per_page: "Lignes par page", - page_x_of_y: "Page {{current}} sur {{total}}", - go_to_first_page: "Aller à la première page", - go_to_previous_page: "Aller à la page précédente", - go_to_next_page: "Aller à la page suivante", - go_to_last_page: "Aller à la dernière page", - }, - comments: { - title: "Commentaires", - description: - "Ajoutez un commentaire en utilisant le formulaire ci-dessous.", - add: "Nouveau commentaire", - new: "Nouveau commentaire", - save: "Enregistrer le commentaire", - success: "Commentaire ajouté avec succès", - error: "Échec de l'ajout du commentaire", - placeholder: "Écrivez votre commentaire ici...", - empty: { - title: "Aucun commentaire pour le moment", - description: "Soyez le premier à ajouter un commentaire", - }, - }, - attachments: { - title: "Pièces jointes", - description: - "Ajoutez un fichier en cliquant sur 'Ajouter une pièce jointe'.", - upload: "Télécharger la pièce jointe", - upload_description: - "Téléchargez une pièce jointe ou ajoutez un lien vers une ressource externe.", - drop: "Déposez les fichiers ici", - drop_description: - "Déposez des fichiers ici ou cliquez pour choisir des fichiers depuis votre appareil.", - drop_files_description: "Les fichiers peuvent faire jusqu'à ", - empty: { - title: "Aucune pièce jointe", - description: - "Ajoutez un fichier en cliquant sur 'Ajouter une pièce jointe'.", - }, - toasts: { - error: "Quelque chose a mal tourné, veuillez réessayer.", - error_uploading_files: - "Impossible de télécharger plus d'un fichier à la fois", - error_uploading_files_multiple: - "Impossible de télécharger plus de 10 fichiers", - error_no_files_selected: "Aucun fichier sélectionné", - error_file_rejected: "Le fichier {file} a été rejeté", - error_failed_to_upload_files: "Échec du téléchargement des fichiers", - error_failed_to_upload_files_multiple: - "Échec du téléchargement des fichiers", - error_failed_to_upload_files_single: - "Échec du téléchargement du fichier", - success_uploading_files: "Fichiers téléchargés avec succès", - success_uploading_files_multiple: "Fichiers téléchargés avec succès", - success_uploading_files_single: "Fichier téléchargé avec succès", - success_uploading_files_target: "Fichiers téléchargés", - uploading_files: "Téléchargement de {target}...", - remove_file: "Supprimer le fichier", - }, - }, - notifications: { - inbox: "Boîte de réception", - archive: "Archive", - archive_all: "Archiver tout", - no_notifications: "Aucune nouvelle notification", - }, - edit: "Modifier", - errors: { - unexpected_error: "Une erreur inattendue est survenue", - }, - description: "Description", - last_updated: "Dernière mise à jour", - frequency: { - daily: "Quotidien", - weekly: "Hebdomadaire", - monthly: "Mensuel", - quarterly: "Trimestriel", - yearly: "Annuel", - }, - upload: { - fileUpload: { - uploadingText: "Téléchargement...", - uploadingFile: "Téléchargement du fichier...", - dropFileHere: "Déposez le fichier ici", - dropFileHereAlt: "Déposez le fichier ici", - releaseToUpload: "Relâchez pour télécharger", - addFiles: "Ajouter des fichiers", - uploadAdditionalEvidence: "Téléchargez un fichier ou un document", - dragDropOrClick: "Faites glisser et déposez ou cliquez pour parcourir", - dragDropOrClickToSelect: - "Faites glisser et déposez ou cliquez pour sélectionner le fichier", - maxFileSize: "Taille maximale du fichier : {size} Mo", - }, - fileUrl: { - additionalLinks: "Liens supplémentaires", - add: "Ajouter", - linksAdded: "{count} lien{s} ajouté{s}", - enterUrl: "Entrez l'URL", - addAnotherLink: "Ajouter un autre lien", - saveLinks: "Enregistrer les liens", - urlBadge: "URL", - copyLink: "Copier le lien", - openLink: "Ouvrir le lien", - deleteLink: "Supprimer le lien", - }, - fileCard: { - preview: "Aperçu", - filePreview: "Aperçu du fichier : {fileName}", - previewNotAvailable: "Aperçu non disponible pour ce type de fichier", - openFile: "Ouvrir le fichier", - deleteFile: "Supprimer le fichier", - deleteFileConfirmTitle: "Supprimer le fichier", - deleteFileConfirmDescription: - "Cette action ne peut pas être annulée. Le fichier sera définitivement supprimé.", - }, - fileSection: { - filesUploaded: "{count} fichiers téléchargés", - }, - }, - }, - header: { - discord: { - button: "Rejoignez-nous sur Discord", - }, - feedback: { - button: "Retour d'information", - title: "Merci pour vos retours !", - description: "Nous reviendrons vers vous dès que possible", - placeholder: - "Idées pour améliorer cette page ou problèmes que vous rencontrez.", - success: "Merci pour vos retours !", - error: "Erreur lors de l'envoi des retours - réessayer ?", - send: "Envoyer des retours", - }, - }, - not_found: { - title: "404 - Page non trouvée", - description: "La page que vous recherchez n'existe pas.", - return: "Retour au tableau de bord", - }, - theme: { - options: { - light: "Clair", - dark: "Sombre", - system: "Système", - }, - }, - sidebar: { - overview: "Aperçu", - policies: "Politiques", - risk: "Gestion des risques", - vendors: "Fournisseurs", - integrations: "Intégrations", - settings: "Paramètres", - evidence: "Tâches de preuve", - people: "Personnes", - tests: "Tests Cloud", - }, - auth: { - title: "Automatisez la conformité SOC 2, ISO 27001 et RGPD avec l'IA.", - description: - "Créez un compte gratuit ou connectez-vous avec un compte existant pour continuer.", - options: "Plus d'options", - google: "Continuer avec Google", - email: { - description: "Entrez votre adresse e-mail pour continuer.", - placeholder: "Entrer l'adresse e-mail", - button: "Continuer avec l'e-mail", - magic_link_sent: "Lien magique envoyé", - magic_link_description: - "Vérifiez votre boîte de réception pour un lien magique.", - magic_link_try_again: "Réessayer.", - success: "E-mail envoyé - vérifiez votre boîte de réception !", - error: "Erreur lors de l'envoi de l'e-mail - réessayer ?", - }, - terms: - "En cliquant sur continuer, vous reconnaissez avoir lu et accepté les Conditions d'utilisation et la Politique de confidentialité.", - }, - onboarding: { - title: "Créer une organisation", - setup: "Configuration", - description: "Parlez-nous un peu de votre organisation.", - fields: { - name: { - label: "Nom de l'organisation", - placeholder: "Le nom de votre organisation", - }, - website: { - label: "Site web", - placeholder: "Le site web de votre organisation", - }, - subdomain: { - label: "Sous-domaine", - placeholder: "exemple", - }, - fullName: { - label: "Votre nom", - placeholder: "Votre nom complet", - }, - }, - success: "Merci, vous êtes prêt !", - error: "Quelque chose s'est mal passé, veuillez réessayer.", - unavailable: "Indisponible", - check_availability: "Vérification de la disponibilité", - available: "Disponible", - }, - overview: { - title: "Aperçu", - framework_chart: { - title: "Progrès du cadre", - }, - requirement_chart: { - title: "Statut de conformité", - }, - }, - policies: { - dashboard: { - title: "Tableau de bord", - all: "Toutes les politiques", - policy_status: "Politique par statut", - policies_by_assignee: "Politiques par responsable", - policies_by_framework: "Politiques par cadre", - sub_pages: { - overview: "Aperçu", - edit_policy: "Modifier la politique", - }, - }, - table: { - name: "Nom de la politique", - statuses: { - draft: "Brouillon", - published: "Publié", - archived: "Archivé", - }, - filters: { - owner: { - label: "Responsable", - placeholder: "Filtrer par responsable", - }, - }, - }, - filters: { - search: "Rechercher des politiques...", - all: "Toutes les politiques", - }, - status: { - draft: "Brouillon", - published: "Publié", - needs_review: "Besoin de révision", - archived: "Archivé", - }, - policies: "politiques", - title: "Politiques", - create_new: "Créer une nouvelle politique", - search_placeholder: "Rechercher des politiques...", - status_filter: "Filtrer par statut", - all_statuses: "Tous les statuts", - no_policies_title: "Aucune politique pour le moment", - no_policies_description: "Commencez par créer votre première politique", - create_first: "Créer la première politique", - no_description: "Aucune description fournie", - last_updated: "Dernière mise à jour : {{date}}", - save: "Enregistrer", - saving: "Enregistrement...", - saved_success: "Politique enregistrée avec succès", - saved_error: "Échec de l'enregistrement de la politique", - overview: { - title: "Aperçu de la politique", - form: { - update_policy: "Mettre à jour la politique", - update_policy_description: - "Mettez à jour le titre ou la description de la politique.", - update_policy_success: "Politique mise à jour avec succès", - update_policy_error: "Échec de la mise à jour de la politique", - update_policy_title: "Nom de la politique", - review_frequency: "Fréquence de révision", - review_frequency_placeholder: "Sélectionnez une fréquence de révision", - review_date: "Date de révision", - review_date_placeholder: "Sélectionnez une date de révision", - required_to_sign: "Doit être signé par les employés", - signature_required: "Exiger la signature des employés", - signature_not_required: "Ne pas demander aux employés de signer", - signature_requirement: "Exigence de signature", - signature_requirement_placeholder: - "Sélectionner l'exigence de signature", - }, - }, - new: { - success: "Politique créée avec succès", - error: "Échec de la création de la politique", - details: "Détails de la politique", - title: "Entrez un titre pour la politique", - description: "Entrez une description pour la politique", - }, - }, - evidence_tasks: { - evidence_tasks: "Tâches de preuve", - overview: "Aperçu", - }, - risk: { - risks: "risques", - overview: "Aperçu", - create: "Créer un nouveau risque", - vendor: { - title: "Gestion des fournisseurs", - dashboard: { - title: "Tableau de bord des fournisseurs", - overview: "Aperçu des fournisseurs", - vendor_status: "Statut du fournisseur", - vendor_category: "Catégories de fournisseurs", - vendors_by_assignee: "Fournisseurs par responsable", - inherent_risk_description: - "Niveau de risque initial avant l'application de tout contrôle", - residual_risk_description: - "Niveau de risque restant après l'application des contrôles", - }, - register: { - title: "Registre des fournisseurs", - table: { - name: "Nom", - category: "Catégorie", - status: "Statut", - owner: "Propriétaire", - }, - }, - assessment: { - title: "Évaluation des fournisseurs", - update_success: - "Évaluation du risque fournisseur mise à jour avec succès", - update_error: - "Échec de la mise à jour de l'évaluation du risque fournisseur", - inherent_risk: "Risque inhérent", - residual_risk: "Risque résiduel", - }, - form: { - vendor_details: "Détails du fournisseur", - vendor_name: "Nom", - vendor_name_placeholder: "Entrez le nom du fournisseur", - vendor_website: "Site web", - vendor_website_placeholder: "Entrez le site web du fournisseur", - vendor_description: "Description", - vendor_description_placeholder: "Entrez la description du fournisseur", - vendor_category: "Catégorie", - vendor_category_placeholder: "Sélectionner une catégorie", - vendor_status: "Statut", - vendor_status_placeholder: "Sélectionner un statut", - create_vendor_success: "Fournisseur créé avec succès", - create_vendor_error: "Échec de la création du fournisseur", - update_vendor: "Mettre à jour le fournisseur", - update_vendor_success: "Fournisseur mis à jour avec succès", - update_vendor_error: "Échec de la mise à jour du fournisseur", - add_comment: "Ajouter un commentaire", - }, - table: { - name: "Nom", - category: "Catégorie", - status: "Statut", - owner: "Propriétaire", - }, - filters: { - search_placeholder: "Rechercher des fournisseurs...", - status_placeholder: "Filtrer par statut", - category_placeholder: "Filtrer par catégorie", - owner_placeholder: "Filtrer par propriétaire", - }, - empty_states: { - no_vendors: { - title: "Aucun fournisseur pour le moment", - description: "Commencez par créer votre premier fournisseur", - }, - no_results: { - title: "Aucun résultat trouvé", - description: "Aucun fournisseur ne correspond à votre recherche", - description_with_filters: "Essayez d'ajuster vos filtres", - }, - }, - actions: { - create: "Créer un fournisseur", - }, - status: { - not_assessed: "Non évalué", - in_progress: "En cours", - assessed: "Évalué", - }, - category: { - cloud: "Cloud", - infrastructure: "Infrastructure", - software_as_a_service: "Logiciel en tant que service", - finance: "Finance", - marketing: "Marketing", - sales: "Ventes", - hr: "Ressources humaines", - other: "Autre", - }, - risk_level: { - low: "Risque faible", - medium: "Risque moyen", - high: "Risque élevé", - unknown: "Risque inconnu", - }, - }, - dashboard: { - title: "Tableau de bord", - overview: "Aperçu des risques", - risk_status: "Statut du risque", - risks_by_department: "Risques par département", - risks_by_assignee: "Risques par assigné", - inherent_risk_description: - "Le risque inhérent est calculé comme probabilité * impact. Calculé avant l'application de tout contrôle.", - residual_risk_description: - "Le risque résiduel est calculé comme probabilité * impact. C'est le niveau de risque après l'application des contrôles.", - risk_assessment_description: - "Comparer les niveaux de risque inhérent et résiduel", - }, - register: { - title: "Registre des risques", - table: { - risk: "Risque", - }, - empty: { - no_risks: { - title: "Créez un risque pour commencer", - description: - "Suivez et évaluez les risques, créez et assignez des tâches d'atténuation pour votre équipe, et gérez votre registre des risques dans une interface simple.", - }, - create_risk: "Créer un risque", - }, - }, - metrics: { - probability: "Probabilité", - impact: "Impact", - inherentRisk: "Risque inhérent", - residualRisk: "Risque résiduel", - }, - form: { - update_inherent_risk: "Enregistrer le risque inhérent", - update_inherent_risk_description: - "Mettez à jour le risque inhérent du risque. C'est le niveau de risque avant l'application de tout contrôle.", - update_inherent_risk_success: "Risque inhérent mis à jour avec succès", - update_inherent_risk_error: "Échec de la mise à jour du risque inhérent", - update_residual_risk: "Enregistrer le risque résiduel", - update_residual_risk_description: - "Mettez à jour le risque résiduel du risque. C'est le niveau de risque après l'application des contrôles.", - update_residual_risk_success: "Risque résiduel mis à jour avec succès", - update_residual_risk_error: "Échec de la mise à jour du risque résiduel", - update_risk: "Mettre à jour le risque", - update_risk_description: - "Mettez à jour le titre ou la description du risque.", - update_risk_success: "Risque mis à jour avec succès", - update_risk_error: "Échec de la mise à jour du risque", - create_risk_success: "Risque créé avec succès", - create_risk_error: "Échec de la création du risque", - risk_details: "Détails du risque", - risk_title: "Titre du risque", - risk_title_description: "Entrez un nom pour le risque", - risk_description: "Description", - risk_description_description: "Entrez une description pour le risque", - risk_category: "Catégorie", - risk_category_placeholder: "Sélectionnez une catégorie", - risk_department: "Département", - risk_department_placeholder: "Sélectionnez un département", - risk_status: "Statut du risque", - risk_status_placeholder: "Sélectionnez un statut de risque", - }, - tasks: { - title: "Tâches", - attachments: "Pièces jointes", - overview: "Aperçu des tâches", - form: { - title: "Détails de la tâche", - task_title: "Titre de la tâche", - status: "Statut de la tâche", - status_placeholder: "Sélectionnez un statut de tâche", - task_title_description: "Entrez un nom pour la tâche", - description: "Description", - description_description: "Entrez une description pour la tâche", - due_date: "Date d'échéance", - due_date_description: "Sélectionnez la date d'échéance pour la tâche", - success: "Tâche créée avec succès", - error: "Échec de la création de la tâche", - }, - sheet: { - title: "Créer une tâche", - update: "Mettre à jour la tâche", - update_description: - "Mettez à jour le titre ou la description de la tâche.", - }, - empty: { - description_create: - "Créez une tâche d'atténuation pour ce risque, ajoutez un plan de traitement et assignez-le à un membre de l'équipe.", - }, - }, - }, - settings: { - general: { - title: "Général", - org_name: "Nom de l'organisation", - org_name_description: - "C'est le nom visible de votre organisation. Vous devez utiliser le nom légal de votre organisation.", - org_name_tip: "Veuillez utiliser un maximum de 32 caractères.", - org_website: "Site Web de l'organisation", - org_website_description: - "C'est l'URL du site Web officiel de votre organisation. Assurez-vous d'inclure l'URL complète avec https://.", - org_website_tip: "Veuillez entrer une URL valide incluant https://", - org_website_error: - "Erreur lors de la mise à jour du site Web de l'organisation", - org_website_updated: "Site Web de l'organisation mis à jour", - org_delete: "Supprimer l'organisation", - org_delete_description: - "Supprimez définitivement votre organisation et tout son contenu de la plateforme Comp AI. Cette action n'est pas réversible - veuillez continuer avec prudence.", - org_delete_alert_title: "Êtes-vous absolument sûr ?", - org_delete_alert_description: - "Cette action ne peut pas être annulée. Cela supprimera définitivement votre organisation et retirera vos données de nos serveurs.", - org_delete_error: "Erreur lors de la suppression de l'organisation", - org_delete_success: "Organisation supprimée", - org_name_updated: "Nom de l'organisation mis à jour", - org_name_error: "Erreur lors de la mise à jour du nom de l'organisation", - save_button: "Enregistrer", - delete_button: "Supprimer", - delete_confirm: "SUPPRIMER", - delete_confirm_tip: "Tapez SUPPRIMER pour confirmer.", - cancel_button: "Annuler", - }, - members: { - title: "Membres", - }, - billing: { - title: "Facturation", - }, - api_keys: { - title: "Clés API", - description: - "Gérez les clés API pour un accès programmatique aux données de votre organisation.", - list_title: "Clés API", - list_description: - "Les clés API permettent un accès sécurisé aux données de votre organisation via notre API.", - create: "Nouvelle clé API", - create_title: "Nouvelle clé API", - create_description: - "Créez une nouvelle clé API pour un accès programmatique aux données de votre organisation.", - created_title: "Clé API créée", - created_description: - "Votre clé API a été créée. Assurez-vous de la copier maintenant car vous ne pourrez plus la voir.", - name: "Nom", - name_label: "Nom", - name_placeholder: "Entrez un nom pour cette clé API", - expiration: "Expiration", - expiration_placeholder: "Sélectionner l'expiration", - expires_label: "Expire", - expires_placeholder: "Sélectionner l'expiration", - expires_30days: "30 jours", - expires_90days: "90 jours", - expires_1year: "1 an", - expires_never: "Jamais", - thirty_days: "30 jours", - ninety_days: "90 jours", - one_year: "1 an", - your_key: "Votre clé API", - api_key: "Clé API", - save_warning: - "Cette clé ne sera affichée qu'une seule fois. Assurez-vous de la copier maintenant.", - copied: "Clé API copiée dans le presse-papiers", - close_confirm: - "Êtes-vous sûr de vouloir fermer ? Vous ne pourrez plus voir cette clé API.", - revoke_confirm: - "Êtes-vous sûr de vouloir révoquer cette clé API ? Cette action ne peut pas être annulée.", - revoke_title: "Révoquer la clé API", - revoke: "Révoquer", - created: "Créée", - expires: "Expire", - last_used: "Dernière utilisation", - actions: "Actions", - never: "Jamais", - never_used: "Jamais utilisée", - no_keys: "Aucune clé API trouvée. Créez-en une pour commencer.", - security_note: - "Les clés API fournissent un accès complet aux données de votre organisation. Gardez-les sécurisées et renouvelez-les régulièrement.", - fetch_error: "Échec de la récupération des clés API", - create_error: "Échec de la création de la clé API", - revoked_success: "Clé API révoquée avec succès", - revoked_error: "Échec de la révocation de la clé API", - done: "Terminé", - }, - team: { - tabs: { - members: "Membres de l'équipe", - invite: "Inviter des membres", - }, - members: { - title: "Membres de l'équipe", - empty: { - no_organization: { - title: "Aucune organisation", - description: "Vous ne faites partie d'aucune organisation", - }, - no_members: { - title: "Aucun membre", - description: "Il n'y a aucun membre actif dans votre organisation", - }, - }, - role: { - owner: "Propriétaire", - admin: "Administrateur", - member: "Membre", - viewer: "Spectateur", - }, - }, - invitations: { - title: "Invitations en attente", - description: - "Utilisateurs qui ont été invités mais n'ont pas encore accepté", - empty: { - no_organization: { - title: "Aucune organisation", - description: "Vous ne faites partie d'aucune organisation", - }, - no_invitations: { - title: "Aucune invitation en attente", - description: "Il n'y a aucune invitation en attente", - }, - }, - invitation_sent: "Invitation envoyée", - actions: { - resend: "Renvoyer l'invitation", - sending: "Envoi de l'invitation", - revoke: "Révoquer", - revoke_title: "Révoquer l'invitation", - revoke_description_prefix: - "Êtes-vous sûr de vouloir révoquer l'invitation pour", - revoke_description_suffix: "Cette action ne peut pas être annulée.", - }, - toast: { - resend_success_prefix: "Un e-mail d'invitation a été envoyé à", - resend_error: "Échec de l'envoi de l'invitation", - resend_unexpected: - "Une erreur inattendue s'est produite lors de l'envoi de l'invitation", - revoke_success_prefix: "Invitation à", - revoke_success_suffix: "a été révoquée", - revoke_error: "Échec de la révocation de l'invitation", - revoke_unexpected: - "Une erreur inattendue s'est produite lors de la révocation de l'invitation", - }, - }, - invite: { - title: "Inviter un membre de l'équipe", - description: - "Envoyer une invitation à un nouveau membre de l'équipe pour rejoindre votre organisation", - form: { - email: { - label: "E-mail", - placeholder: "membre@example.com", - error: "Veuillez entrer une adresse e-mail valide", - }, - role: { - label: "Rôle", - placeholder: "Sélectionner un rôle", - error: "Veuillez sélectionner un rôle", - }, - department: { - label: "Département", - placeholder: "Sélectionner un département", - error: "Veuillez sélectionner un département", - }, - departments: { - none: "Aucun", - it: "Informatique", - hr: "Ressources humaines", - admin: "Administrateur", - gov: "Gouvernement", - itsm: "ITSM", - qms: "QMS", - }, - }, - button: { - send: "Envoyer l'invitation", - sending: "Envoi de l'invitation...", - sent: "Invitation envoyée", - }, - toast: { - error: "Échec de l'envoi de l'invitation", - unexpected: - "Une erreur inattendue s'est produite lors de l'envoi de l'invitation", - }, - }, - member_actions: { - actions: "Actions", - change_role: "Changer de rôle", - remove_member: "Supprimer un membre", - remove_confirm: { - title: "Supprimer un membre de l'équipe", - description_prefix: "Êtes-vous sûr de vouloir supprimer", - description_suffix: "Cette action ne peut pas être annulée.", - }, - role_dialog: { - title: "Changer de rôle", - description_prefix: "Mettre à jour le rôle de", - role_label: "Rôle", - role_placeholder: "Sélectionner un rôle", - role_descriptions: { - admin: - "Les administrateurs peuvent gérer les membres de l'équipe et les paramètres.", - member: - "Les membres peuvent utiliser toutes les fonctionnalités mais ne peuvent pas gérer les membres de l'équipe.", - viewer: - "Les spectateurs ne peuvent que visualiser le contenu sans apporter de modifications.", - }, - cancel: "Annuler", - update: "Mettre à jour le rôle", - }, - toast: { - remove_success: "a été retiré de l'organisation", - remove_error: "Échec de la suppression du membre", - remove_unexpected: - "Une erreur inattendue s'est produite lors de la suppression du membre", - update_role_success: "a eu son rôle mis à jour en", - update_role_error: "Échec de la mise à jour du rôle du membre", - update_role_unexpected: - "Une erreur inattendue s'est produite lors de la mise à jour du rôle du membre", - }, - }, - }, - }, - user_menu: { - theme: "Thème", - language: "Langue", - sign_out: "Se déconnecter", - account: "Compte", - support: "Assistance", - settings: "Paramètres", - teams: "Équipes", - }, - frameworks: { - title: "Cadres", - controls: { - title: "Contrôles", - description: "Examiner et gérer les contrôles de conformité", - table: { - status: "Statut", - control: "Contrôle", - artifacts: "Artifacts", - actions: "Actions", - }, - statuses: { - not_started: "Non commencé", - completed: "Terminé", - in_progress: "En cours", - }, - }, - overview: { - error: "Échec du chargement des cadres", - loading: "Chargement des cadres...", - empty: { - title: "Aucun cadre sélectionné", - description: - "Sélectionnez des cadres pour commencer votre parcours de conformité", - }, - progress: { - title: "Progression du cadre", - empty: { - title: "Pas encore de cadres", - description: - "Commencez par ajouter un cadre de conformité pour suivre vos progrès", - action: "Ajouter un cadre", - }, - }, - grid: { - welcome: { - title: "Bienvenue dans Comp AI", - description: - "Commencez par sélectionner les cadres de conformité que vous souhaitez mettre en œuvre. Nous vous aiderons à gérer et à suivre votre parcours de conformité à travers plusieurs normes.", - action: "Commencer", - }, - title: "Sélectionner des cadres", - version: "Version", - actions: { - clear: "Effacer", - confirm: "Confirmer la sélection", - }, - }, - }, - }, - vendor: { - title: "Tableau de bord", - register_title: "Gestion des fournisseurs", - dashboard: { - title: "Tableau de bord", - overview: "Aperçu des fournisseurs", - vendor_status: "Statut du fournisseur", - vendor_category: "Catégories de fournisseurs", - vendors_by_assignee: "Fournisseurs par responsable", - inherent_risk_description: - "Niveau de risque initial avant l'application de tout contrôle", - residual_risk_description: - "Niveau de risque restant après l'application des contrôles", - }, - register: { - title: "Registre des fournisseurs", - table: { - name: "Nom", - category: "Catégorie", - status: "Statut", - owner: "Propriétaire", - }, - }, - category: { - cloud: "Cloud", - infrastructure: "Infrastructure", - software_as_a_service: "SaaS", - finance: "Finance", - marketing: "Marketing", - sales: "Ventes", - hr: "Ressources humaines", - other: "Autre", - }, - vendors: "fournisseurs", - form: { - vendor_details: "Détails du fournisseur", - vendor_name: "Nom", - vendor_name_placeholder: "Entrez le nom du fournisseur", - vendor_website: "Site web", - vendor_website_placeholder: "Entrez le site web du fournisseur", - vendor_description: "Description", - vendor_description_placeholder: "Entrez la description du fournisseur", - vendor_category: "Catégorie", - vendor_category_placeholder: "Sélectionner une catégorie", - vendor_status: "Statut", - vendor_status_placeholder: "Sélectionner un statut", - create_vendor_success: "Fournisseur créé avec succès", - create_vendor_error: "Échec de la création du fournisseur", - update_vendor_success: "Fournisseur mis à jour avec succès", - update_vendor_error: "Échec de la mise à jour du fournisseur", - contacts: "Contacts du fournisseur", - contact_name: "Nom du contact", - contact_email: "Email du contact", - contact_role: "Rôle du contact", - add_contact: "Ajouter un contact", - new_contact: "Nouveau contact", - min_one_contact_required: "Un fournisseur doit avoir au moins un contact", - }, - empty_states: { - no_vendors: { - title: "Pas encore de fournisseurs", - description: "Commencez par créer votre premier fournisseur", - }, - no_results: { - title: "Aucun résultat trouvé", - description: "Aucun fournisseur ne correspond à votre recherche", - description_with_filters: "Essayez d'ajuster vos filtres", - }, - }, - }, - people: { - title: "Personnes", - details: { - taskProgress: "Progression de la tâche", - tasks: "Tâches", - noTasks: "Aucune tâche assignée pour le moment", - }, - description: "Gérez vos membres d'équipe et leurs rôles.", - filters: { - search: "Rechercher des personnes...", - role: "Filtrer par rôle", - }, - actions: { - invite: "Ajouter un employé", - clear: "Effacer les filtres", - }, - table: { - name: "Nom", - email: "Email", - department: "Département", - externalId: "ID externe", - status: "Statut", - }, - empty: { - no_employees: { - title: "Aucun employé pour le moment", - description: "Commencez par inviter votre premier membre d'équipe.", - }, - no_results: { - title: "Aucun résultat trouvé", - description: "Aucun employé ne correspond à votre recherche", - description_with_filters: "Essayez d'ajuster vos filtres", - }, - }, - invite: { - title: "Ajouter un employé", - description: "Ajoutez un employé à votre organisation.", - email: { - label: "Adresse e-mail", - placeholder: "Entrez l'adresse e-mail", - }, - role: { - label: "Rôle", - placeholder: "Sélectionnez un rôle", - }, - name: { - label: "Nom", - placeholder: "Entrez le nom", - }, - department: { - label: "Département", - placeholder: "Sélectionnez un département", - }, - submit: "Ajouter un employé", - success: "Employé ajouté avec succès", - error: "Échec de l'ajout de l'employé", - }, - }, - errors: { - unexpected: "Quelque chose s'est mal passé, veuillez réessayer", - }, - sub_pages: { - risk: { - overview: "Gestion des risques", - register: "Registre des risques", - risk_overview: "Aperçu des risques", - risk_comments: "Commentaires sur les risques", - tasks: { - task_overview: "Aperçu des tâches", - }, - }, - policies: { - all: "Toutes les politiques", - editor: "Éditeur de politique", - policy_details: "Détails de la politique", - }, - people: { - all: "Personnes", - employee_details: "Détails de l'employé", - }, - settings: { - members: "Membres de l'équipe", - }, - frameworks: { - overview: "Cadres", - }, - evidence: { - title: "Preuve", - list: "Liste des preuves", - overview: "Aperçu des preuves", - }, - tests: { - overview: "Tests en nuage", - test_details: "Détails du test", - }, - }, - editor: { - ai: { - thinking: "L'IA réfléchit", - thinking_spinner: "L'IA réfléchit", - edit_or_generate: "Modifier ou générer...", - tell_ai_what_to_do_next: "Dites à l'IA quoi faire ensuite", - request_limit_reached: - "Vous avez atteint votre limite de demandes pour la journée.", - }, - ai_selector: { - improve: "Améliorer l'écriture", - fix: "Corriger la grammaire", - shorter: "Raccourcir", - longer: "Allonger", - continue: "Continuer l'écriture", - replace: "Remplacer la sélection", - insert: "Insérer ci-dessous", - discard: "Jeter", - }, - }, - evidence: { - title: "Preuve", - list: "Toutes les preuves", - overview: "Aperçu des preuves", - edit: "Preuve Téléchargée", - dashboard: { - layout: "Tableau de bord", - layout_back_button: "Retour", - title: "Tableau de bord des preuves", - by_department: "Par département", - by_assignee: "Par responsable", - by_framework: "Par cadre", - }, - items: "articles", - status: { - up_to_date: "À jour", - needs_review: "Nécessite une révision", - draft: "Brouillon", - empty: "Vide", - }, - departments: { - none: "Non classé", - admin: "Administration", - gov: "Gouvernance", - hr: "Ressources humaines", - it: "Technologies de l'information", - itsm: "Gestion des services informatiques", - qms: "Gestion de la qualité", - }, - details: { - review_section: "Informations de révision", - content: "Contenu de la preuve", - }, - }, - upload: { - fileSection: { - filesUploaded: "{count} fichier(s) téléchargé(s)", - upload: "{count} fichier(s) téléchargé(s)", - }, - fileUpload: { - uploadingText: "Téléchargement en cours...", - dropFileHere: "Déposez le fichier ici", - releaseToUpload: "Relâchez pour télécharger", - addFiles: "Ajouter des fichiers", - uploadAdditionalEvidence: "Télécharger un fichier", - dragDropOrClick: "Glissez-déposez ou cliquez pour télécharger", - dropFileHereAlt: "Déposez le fichier ici", - dragDropOrClickToSelect: - "Glissez-déposez un fichier ici, ou cliquez pour sélectionner", - maxFileSize: "Taille maximale du fichier : {size} Mo", - uploadingFile: "Téléchargement du fichier...", - }, - fileCard: { - preview: "Aperçu", - previewNotAvailable: - "Aperçu non disponible. Cliquez sur le bouton de téléchargement pour voir le fichier.", - filePreview: "Aperçu du fichier : {fileName}", - openFile: "Ouvrir le fichier", - deleteFile: "Supprimer le fichier", - deleteFileConfirmTitle: "Supprimer le fichier", - deleteFileConfirmDescription: - "Êtes-vous sûr de vouloir supprimer ce fichier ? Cette action ne peut pas être annulée.", - }, - fileUrl: { - additionalLinks: "Liens supplémentaires", - add: "Ajouter", - linksAdded: "{count} lien{s} ajouté{s}", - enterUrl: "Entrez l'URL", - addAnotherLink: "Ajouter un autre lien", - saveLinks: "Enregistrer les liens", - urlBadge: "URL", - copyLink: "Copier le lien", - openLink: "Ouvrir le lien", - deleteLink: "Supprimer le lien", - }, - }, - tests: { - name: "Tests Cloud", - title: "Tests Cloud", - actions: { - create: "Ajouter un test Cloud", - clear: "Effacer les filtres", - refresh: "Rafraîchir", - refresh_success: "Tests actualisés avec succès", - refresh_error: "Échec de l'actualisation des tests", - }, - empty: { - no_tests: { - title: "Aucun test cloud pour le moment", - description: "Commencez par créer votre premier test dans le cloud.", - }, - no_results: { - title: "Aucun résultat trouvé", - description: "Aucun test ne correspond à votre recherche", - description_with_filters: "Essayez d'ajuster vos filtres", - }, - }, - filters: { - search: "Rechercher des tests...", - role: "Filtrer par fournisseur", - }, - register: { - title: "Ajouter un test Cloud", - description: "Configurer un nouveau test de conformité cloud.", - submit: "Créer un test", - success: "Test créé avec succès", - invalid_json: "Configuration JSON invalide fournie", - title_field: { - label: "Titre du test", - placeholder: "Entrez le titre du test", - }, - description_field: { - label: "Description", - placeholder: "Entrez la description du test", - }, - provider: { - label: "Fournisseur Cloud", - placeholder: "Sélectionnez le fournisseur cloud", - }, - config: { - label: "Configuration du test", - placeholder: "Entrez la configuration JSON pour le test", - }, - auth_config: { - label: "Configuration d'authentification", - placeholder: "Entrez la configuration d'authentification JSON", - }, - }, - table: { - title: "Titre", - provider: "Fournisseur", - severity: "Gravité", - result: "Résultat", - createdAt: "Créé le", - assignedUser: "Utilisateur assigné", - assignedUserEmpty: "Non assigné", - no_results: "Aucun résultat trouvé", - status: "Statut", - }, - dashboard: { - overview: "Aperçu", - all: "Tous les tests", - tests_by_assignee: "Tests par le responsable", - passed: "Réussi", - failed: "Échoué", - severity_distribution: "Distribution de la gravité des tests", - }, - severity: { - low: "Faible", - medium: "Moyen", - high: "Élevé", - critical: "Critique", - }, - }, - vendors: { - title: "Fournisseurs", - register: { - title: "Enregistrement des fournisseurs", - }, - dashboard: { - title: "Aperçu des fournisseurs", - }, - }, - dashboard: { - risk_status: "Statut de risque", - risks_by_department: "Risques par département", - vendor_status: "Statut des fournisseurs", - vendors_by_category: "Fournisseurs par catégorie", - }, - team: { - tabs: { - members: "Membres de l'équipe", - invite: "Inviter des membres", - }, - members: { - title: "Membres de l'équipe", - empty: { - no_organization: { - title: "Aucune organisation", - description: "Vous ne faites partie d'aucune organisation", - }, - no_members: { - title: "Aucun membre", - description: "Il n'y a aucun membre actif dans votre organisation", - }, - }, - role: { - owner: "Propriétaire", - admin: "Administrateur", - member: "Membre", - viewer: "Spectateur", - }, - }, - invitations: { - title: "Invitations en attente", - description: - "Utilisateurs qui ont été invités mais n'ont pas encore accepté", - empty: { - no_organization: { - title: "Aucune organisation", - description: "Vous ne faites partie d'aucune organisation", - }, - no_invitations: { - title: "Aucune invitation en attente", - description: "Il n'y a aucune invitation en attente", - }, - }, - invitation_sent: "Invitation envoyée", - actions: { - resend: "Renvoyer l'invitation", - sending: "Envoi de l'invitation", - revoke: "Révoquer", - revoke_title: "Révoquer l'invitation", - revoke_description_prefix: - "Êtes-vous sûr de vouloir révoquer l'invitation pour", - revoke_description_suffix: "Cette action ne peut pas être annulée.", - }, - toast: { - resend_success_prefix: "Un e-mail d'invitation a été envoyé à", - resend_error: "Échec de l'envoi de l'invitation", - resend_unexpected: - "Une erreur inattendue s'est produite lors de l'envoi de l'invitation", - revoke_success_prefix: "Invitation à", - revoke_success_suffix: "a été révoquée", - revoke_error: "Échec de la révocation de l'invitation", - revoke_unexpected: - "Une erreur inattendue s'est produite lors de la révocation de l'invitation", - }, - }, - invite: { - title: "Inviter un membre de l'équipe", - description: - "Envoyer une invitation à un nouveau membre de l'équipe pour rejoindre votre organisation", - form: { - email: { - label: "E-mail", - placeholder: "membre@exemple.com", - error: "Veuillez entrer une adresse e-mail valide", - }, - role: { - label: "Rôle", - placeholder: "Sélectionner un rôle", - error: "Veuillez sélectionner un rôle", - }, - department: { - label: "Département", - placeholder: "Sélectionner un département", - error: "Veuillez sélectionner un département", - }, - departments: { - none: "Aucun", - it: "Informatique", - hr: "Ressources humaines", - admin: "Administrateur", - gov: "Gouvernement", - itsm: "ITSM", - qms: "QMS", - }, - }, - button: { - send: "Envoyer l'invitation", - sending: "Envoi de l'invitation...", - sent: "Invitation envoyée", - }, - toast: { - error: "Échec de l'envoi de l'invitation", - unexpected: - "Une erreur inattendue s'est produite lors de l'envoi de l'invitation", - }, - }, - member_actions: { - actions: "Actions", - change_role: "Changer de rôle", - remove_member: "Supprimer un membre", - remove_confirm: { - title: "Supprimer un membre de l'équipe", - description_prefix: "Êtes-vous sûr de vouloir supprimer", - description_suffix: "Cette action ne peut pas être annulée.", - }, - role_dialog: { - title: "Changer de rôle", - description_prefix: "Mettre à jour le rôle de", - role_label: "Rôle", - role_placeholder: "Sélectionner un rôle", - role_descriptions: { - admin: - "Les administrateurs peuvent gérer les membres de l'équipe et les paramètres.", - member: - "Les membres peuvent utiliser toutes les fonctionnalités mais ne peuvent pas gérer les membres de l'équipe.", - viewer: - "Les spectateurs peuvent uniquement visualiser le contenu sans apporter de modifications.", - }, - cancel: "Annuler", - update: "Mettre à jour le rôle", - }, - toast: { - remove_success: "a été retiré de l'organisation", - remove_error: "Échec de la suppression du membre", - remove_unexpected: - "Une erreur inattendue s'est produite lors de la suppression du membre", - update_role_success: "a vu son rôle mis à jour en", - update_role_error: "Échec de la mise à jour du rôle du membre", - update_role_unexpected: - "Une erreur inattendue s'est produite lors de la mise à jour du rôle du membre", - }, - }, - }, + languages: { + es: "Espagnol", + fr: "Français", + no: "Norvégien", + pt: "Portugais", + en: "Anglais" + }, + language: { + title: "Langues", + description: "Changer la langue utilisée dans l'interface utilisateur.", + placeholder: "Sélectionner la langue" + }, + common: { + actions: { + save: "Enregistrer", + edit: "Modifier", + "delete": "Supprimer", + cancel: "Annuler", + clear: "Effacer", + create: "Créer", + send: "Envoyer", + "return": "Retourner", + success: "Succès", + error: "Erreur", + next: "Suivant", + complete: "Compléter", + addNew: "Ajouter nouveau" + }, + assignee: { + label: "Assigné", + placeholder: "Sélectionner l'assigné" + }, + date: { + pick: "Choisir une date", + due_date: "Date d'échéance" + }, + status: { + open: "Ouvert", + pending: "En attente", + closed: "Fermé", + archived: "Archivé", + compliant: "Conforme", + non_compliant: "Non conforme", + not_started: "Non commencé", + in_progress: "En cours", + published: "Publié", + needs_review: "Besoin de révision", + draft: "Brouillon", + not_assessed: "Non évalué", + assessed: "Évalué", + active: "Actif", + inactive: "Inactif", + title: "Statut" + }, + filters: { + clear: "Effacer les filtres", + search: "Recherche...", + status: "Statut", + department: "Département", + owner: { + label: "Assigné", + placeholder: "Filtrer par assigné" + } + }, + table: { + title: "Titre", + status: "Statut", + assigned_to: "Assigné à", + due_date: "Date d'échéance", + last_updated: "Dernière mise à jour", + no_results: "Aucun résultat trouvé" + }, + empty_states: { + no_results: { + title: "Aucun résultat trouvé", + title_tasks: "Aucune tâche trouvée", + title_risks: "Aucun risque trouvé", + description: "Essayez une autre recherche ou ajustez les filtres", + description_filters: "Essayez une autre recherche ou ajustez les filtres", + description_no_tasks: "Créez une tâche pour commencer", + description_no_risks: "Créez un risque pour commencer" + }, + no_items: { + title: "Aucun élément trouvé", + description: "Essayez d'ajuster votre recherche ou vos filtres" + } + }, + pagination: { + of: "de", + items_per_page: "Éléments par page", + rows_per_page: "Lignes par page", + page_x_of_y: "Page {{current}} sur {{total}}", + go_to_first_page: "Aller à la première page", + go_to_previous_page: "Aller à la page précédente", + go_to_next_page: "Aller à la page suivante", + go_to_last_page: "Aller à la dernière page" + }, + comments: { + title: "Commentaires", + description: "Ajoutez un commentaire en utilisant le formulaire ci-dessous.", + add: "Nouveau commentaire", + "new": "Nouveau commentaire", + save: "Enregistrer le commentaire", + success: "Commentaire ajouté avec succès", + error: "Échec de l'ajout du commentaire", + placeholder: "Écrivez votre commentaire ici...", + empty: { + title: "Aucun commentaire pour le moment", + description: "Soyez le premier à ajouter un commentaire" + } + }, + attachments: { + title: "Pièces jointes", + description: "Ajoutez un fichier en cliquant sur 'Ajouter une pièce jointe'.", + upload: "Télécharger la pièce jointe", + upload_description: "Téléchargez une pièce jointe ou ajoutez un lien vers une ressource externe.", + drop: "Déposez les fichiers ici", + drop_description: "Déposez des fichiers ici ou cliquez pour choisir des fichiers depuis votre appareil.", + drop_files_description: "Les fichiers peuvent faire jusqu'à ", + empty: { + title: "Aucune pièce jointe", + description: "Ajoutez un fichier en cliquant sur 'Ajouter une pièce jointe'." + }, + toasts: { + error: "Quelque chose a mal tourné, veuillez réessayer.", + error_uploading_files: "Impossible de télécharger plus d'un fichier à la fois", + error_uploading_files_multiple: "Impossible de télécharger plus de 10 fichiers", + error_no_files_selected: "Aucun fichier sélectionné", + error_file_rejected: "Le fichier {file} a été rejeté", + error_failed_to_upload_files: "Échec du téléchargement des fichiers", + error_failed_to_upload_files_multiple: "Échec du téléchargement des fichiers", + error_failed_to_upload_files_single: "Échec du téléchargement du fichier", + success_uploading_files: "Fichiers téléchargés avec succès", + success_uploading_files_multiple: "Fichiers téléchargés avec succès", + success_uploading_files_single: "Fichier téléchargé avec succès", + success_uploading_files_target: "Fichiers téléchargés", + uploading_files: "Téléchargement de {target}...", + remove_file: "Supprimer le fichier" + } + }, + notifications: { + inbox: "Boîte de réception", + archive: "Archive", + archive_all: "Archiver tout", + no_notifications: "Aucune nouvelle notification" + }, + edit: "Modifier", + errors: { + unexpected_error: "Une erreur inattendue est survenue" + }, + description: "Description", + last_updated: "Dernière mise à jour", + frequency: { + daily: "Quotidien", + weekly: "Hebdomadaire", + monthly: "Mensuel", + quarterly: "Trimestriel", + yearly: "Annuel" + }, + upload: { + fileUpload: { + uploadingText: "Téléchargement...", + uploadingFile: "Téléchargement du fichier...", + dropFileHere: "Déposez le fichier ici", + dropFileHereAlt: "Déposez le fichier ici", + releaseToUpload: "Relâchez pour télécharger", + addFiles: "Ajouter des fichiers", + uploadAdditionalEvidence: "Téléchargez un fichier ou un document", + dragDropOrClick: "Faites glisser et déposez ou cliquez pour parcourir", + dragDropOrClickToSelect: "Faites glisser et déposez ou cliquez pour sélectionner le fichier", + maxFileSize: "Taille maximale du fichier : {size} Mo" + }, + fileUrl: { + additionalLinks: "Liens supplémentaires", + add: "Ajouter", + linksAdded: "{count} lien{s} ajouté{s}", + enterUrl: "Entrez l'URL", + addAnotherLink: "Ajouter un autre lien", + saveLinks: "Enregistrer les liens", + urlBadge: "URL", + copyLink: "Copier le lien", + openLink: "Ouvrir le lien", + deleteLink: "Supprimer le lien" + }, + fileCard: { + preview: "Aperçu", + filePreview: "Aperçu du fichier : {fileName}", + previewNotAvailable: "Aperçu non disponible pour ce type de fichier", + openFile: "Ouvrir le fichier", + deleteFile: "Supprimer le fichier", + deleteFileConfirmTitle: "Supprimer le fichier", + deleteFileConfirmDescription: "Cette action ne peut pas être annulée. Le fichier sera définitivement supprimé." + }, + fileSection: { + filesUploaded: "{count} fichiers téléchargés" + } + } + }, + header: { + discord: { + button: "Rejoignez-nous sur Discord" + }, + feedback: { + button: "Retour d'information", + title: "Merci pour vos retours !", + description: "Nous reviendrons vers vous dès que possible", + placeholder: "Idées pour améliorer cette page ou problèmes que vous rencontrez.", + success: "Merci pour vos retours !", + error: "Erreur lors de l'envoi des retours - réessayer ?", + send: "Envoyer des retours" + } + }, + not_found: { + title: "404 - Page non trouvée", + description: "La page que vous recherchez n'existe pas.", + "return": "Retour au tableau de bord" + }, + theme: { + options: { + light: "Clair", + dark: "Sombre", + system: "Système" + } + }, + sidebar: { + overview: "Aperçu", + policies: "Politiques", + risk: "Gestion des risques", + vendors: "Fournisseurs", + integrations: "Intégrations", + settings: "Paramètres", + evidence: "Tâches de preuve", + people: "Personnes", + tests: "Tests Cloud" + }, + auth: { + title: "Automatisez la conformité SOC 2, ISO 27001 et RGPD avec l'IA.", + description: "Créez un compte gratuit ou connectez-vous avec un compte existant pour continuer.", + options: "Plus d'options", + google: "Continuer avec Google", + email: { + description: "Entrez votre adresse e-mail pour continuer.", + placeholder: "Entrer l'adresse e-mail", + button: "Continuer avec l'e-mail", + magic_link_sent: "Lien magique envoyé", + magic_link_description: "Vérifiez votre boîte de réception pour un lien magique.", + magic_link_try_again: "Réessayer.", + success: "E-mail envoyé - vérifiez votre boîte de réception !", + error: "Erreur lors de l'envoi de l'e-mail - réessayer ?" + }, + terms: "En cliquant sur continuer, vous reconnaissez avoir lu et accepté les Conditions d'utilisation et la Politique de confidentialité." + }, + onboarding: { + title: "Créer une organisation", + setup: "Bienvenue dans Comp AI", + description: "Parlez-nous un peu de votre organisation et des cadres avec lesquels vous souhaitez commencer.", + fields: { + name: { + label: "Nom de l'organisation", + placeholder: "Le nom de votre organisation" + }, + website: { + label: "Site web", + placeholder: "Le site web de votre organisation" + }, + subdomain: { + label: "Sous-domaine", + placeholder: "exemple" + }, + fullName: { + label: "Votre nom", + placeholder: "Votre nom complet" + } + }, + success: "Merci, vous êtes prêt !", + error: "Quelque chose s'est mal passé, veuillez réessayer.", + unavailable: "Indisponible", + check_availability: "Vérification de la disponibilité", + available: "Disponible", + submit: "Terminez la configuration", + trigger: { + title: "Patientez, nous créons votre organisation", + creating: "Cela peut prendre une minute ou deux...", + completed: "Organisation créée avec succès", + "continue": "Continuer vers le tableau de bord", + error: "Une erreur s'est produite, veuillez réessayer." + }, + creating: "Création de votre organisation..." + }, + overview: { + title: "Aperçu", + framework_chart: { + title: "Progrès du cadre" + }, + requirement_chart: { + title: "Statut de conformité" + } + }, + policies: { + dashboard: { + title: "Tableau de bord", + all: "Toutes les politiques", + policy_status: "Politique par statut", + policies_by_assignee: "Politiques par responsable", + policies_by_framework: "Politiques par cadre", + sub_pages: { + overview: "Aperçu", + edit_policy: "Modifier la politique" + } + }, + table: { + name: "Nom de la politique", + statuses: { + draft: "Brouillon", + published: "Publié", + archived: "Archivé" + }, + filters: { + owner: { + label: "Responsable", + placeholder: "Filtrer par responsable" + } + } + }, + filters: { + search: "Rechercher des politiques...", + all: "Toutes les politiques" + }, + status: { + draft: "Brouillon", + published: "Publié", + needs_review: "Besoin de révision", + archived: "Archivé" + }, + policies: "politiques", + title: "Politiques", + create_new: "Créer une nouvelle politique", + search_placeholder: "Rechercher des politiques...", + status_filter: "Filtrer par statut", + all_statuses: "Tous les statuts", + no_policies_title: "Aucune politique pour le moment", + no_policies_description: "Commencez par créer votre première politique", + create_first: "Créer la première politique", + no_description: "Aucune description fournie", + last_updated: "Dernière mise à jour : {{date}}", + save: "Enregistrer", + saving: "Enregistrement...", + saved_success: "Politique enregistrée avec succès", + saved_error: "Échec de l'enregistrement de la politique", + overview: { + title: "Aperçu de la politique", + form: { + update_policy: "Mettre à jour la politique", + update_policy_description: "Mettez à jour le titre ou la description de la politique.", + update_policy_success: "Politique mise à jour avec succès", + update_policy_error: "Échec de la mise à jour de la politique", + update_policy_title: "Nom de la politique", + review_frequency: "Fréquence de révision", + review_frequency_placeholder: "Sélectionnez une fréquence de révision", + review_date: "Date de révision", + review_date_placeholder: "Sélectionnez une date de révision", + required_to_sign: "Doit être signé par les employés", + signature_required: "Exiger la signature des employés", + signature_not_required: "Ne pas demander aux employés de signer", + signature_requirement: "Exigence de signature", + signature_requirement_placeholder: "Sélectionner l'exigence de signature" + } + }, + "new": { + success: "Politique créée avec succès", + error: "Échec de la création de la politique", + details: "Détails de la politique", + title: "Entrez un titre pour la politique", + description: "Entrez une description pour la politique" + } + }, + evidence_tasks: { + evidence_tasks: "Tâches de preuve", + overview: "Aperçu" + }, + risk: { + risks: "risques", + overview: "Aperçu", + create: "Créer un nouveau risque", + vendor: { + title: "Gestion des fournisseurs", + dashboard: { + title: "Tableau de bord des fournisseurs", + overview: "Aperçu des fournisseurs", + vendor_status: "Statut du fournisseur", + vendor_category: "Catégories de fournisseurs", + vendors_by_assignee: "Fournisseurs par responsable", + inherent_risk_description: "Niveau de risque initial avant l'application de tout contrôle", + residual_risk_description: "Niveau de risque restant après l'application des contrôles" + }, + register: { + title: "Registre des fournisseurs", + table: { + name: "Nom", + category: "Catégorie", + status: "Statut", + owner: "Propriétaire" + } + }, + assessment: { + title: "Évaluation des fournisseurs", + update_success: "Évaluation du risque fournisseur mise à jour avec succès", + update_error: "Échec de la mise à jour de l'évaluation du risque fournisseur", + inherent_risk: "Risque inhérent", + residual_risk: "Risque résiduel" + }, + form: { + vendor_details: "Détails du fournisseur", + vendor_name: "Nom", + vendor_name_placeholder: "Entrez le nom du fournisseur", + vendor_website: "Site web", + vendor_website_placeholder: "Entrez le site web du fournisseur", + vendor_description: "Description", + vendor_description_placeholder: "Entrez la description du fournisseur", + vendor_category: "Catégorie", + vendor_category_placeholder: "Sélectionner une catégorie", + vendor_status: "Statut", + vendor_status_placeholder: "Sélectionner un statut", + create_vendor_success: "Fournisseur créé avec succès", + create_vendor_error: "Échec de la création du fournisseur", + update_vendor: "Mettre à jour le fournisseur", + update_vendor_success: "Fournisseur mis à jour avec succès", + update_vendor_error: "Échec de la mise à jour du fournisseur", + add_comment: "Ajouter un commentaire" + }, + table: { + name: "Nom", + category: "Catégorie", + status: "Statut", + owner: "Propriétaire" + }, + filters: { + search_placeholder: "Rechercher des fournisseurs...", + status_placeholder: "Filtrer par statut", + category_placeholder: "Filtrer par catégorie", + owner_placeholder: "Filtrer par propriétaire" + }, + empty_states: { + no_vendors: { + title: "Aucun fournisseur pour le moment", + description: "Commencez par créer votre premier fournisseur" + }, + no_results: { + title: "Aucun résultat trouvé", + description: "Aucun fournisseur ne correspond à votre recherche", + description_with_filters: "Essayez d'ajuster vos filtres" + } + }, + actions: { + create: "Créer un fournisseur" + }, + status: { + not_assessed: "Non évalué", + in_progress: "En cours", + assessed: "Évalué" + }, + category: { + cloud: "Cloud", + infrastructure: "Infrastructure", + software_as_a_service: "Logiciel en tant que service", + finance: "Finance", + marketing: "Marketing", + sales: "Ventes", + hr: "Ressources humaines", + other: "Autre" + }, + risk_level: { + low: "Risque faible", + medium: "Risque moyen", + high: "Risque élevé", + unknown: "Risque inconnu" + } + }, + dashboard: { + title: "Tableau de bord", + overview: "Aperçu des risques", + risk_status: "Statut du risque", + risks_by_department: "Risques par département", + risks_by_assignee: "Risques par assigné", + inherent_risk_description: "Le risque inhérent est calculé comme probabilité * impact. Calculé avant l'application de tout contrôle.", + residual_risk_description: "Le risque résiduel est calculé comme probabilité * impact. C'est le niveau de risque après l'application des contrôles.", + risk_assessment_description: "Comparer les niveaux de risque inhérent et résiduel" + }, + register: { + title: "Registre des risques", + table: { + risk: "Risque" + }, + empty: { + no_risks: { + title: "Créez un risque pour commencer", + description: "Suivez et évaluez les risques, créez et assignez des tâches d'atténuation pour votre équipe, et gérez votre registre des risques dans une interface simple." + }, + create_risk: "Créer un risque" + } + }, + metrics: { + probability: "Probabilité", + impact: "Impact", + inherentRisk: "Risque inhérent", + residualRisk: "Risque résiduel" + }, + form: { + update_inherent_risk: "Enregistrer le risque inhérent", + update_inherent_risk_description: "Mettez à jour le risque inhérent du risque. C'est le niveau de risque avant l'application de tout contrôle.", + update_inherent_risk_success: "Risque inhérent mis à jour avec succès", + update_inherent_risk_error: "Échec de la mise à jour du risque inhérent", + update_residual_risk: "Enregistrer le risque résiduel", + update_residual_risk_description: "Mettez à jour le risque résiduel du risque. C'est le niveau de risque après l'application des contrôles.", + update_residual_risk_success: "Risque résiduel mis à jour avec succès", + update_residual_risk_error: "Échec de la mise à jour du risque résiduel", + update_risk: "Mettre à jour le risque", + update_risk_description: "Mettez à jour le titre ou la description du risque.", + update_risk_success: "Risque mis à jour avec succès", + update_risk_error: "Échec de la mise à jour du risque", + create_risk_success: "Risque créé avec succès", + create_risk_error: "Échec de la création du risque", + risk_details: "Détails du risque", + risk_title: "Titre du risque", + risk_title_description: "Entrez un nom pour le risque", + risk_description: "Description", + risk_description_description: "Entrez une description pour le risque", + risk_category: "Catégorie", + risk_category_placeholder: "Sélectionnez une catégorie", + risk_department: "Département", + risk_department_placeholder: "Sélectionnez un département", + risk_status: "Statut du risque", + risk_status_placeholder: "Sélectionnez un statut de risque" + }, + tasks: { + title: "Tâches", + attachments: "Pièces jointes", + overview: "Aperçu des tâches", + form: { + title: "Détails de la tâche", + task_title: "Titre de la tâche", + status: "Statut de la tâche", + status_placeholder: "Sélectionnez un statut de tâche", + task_title_description: "Entrez un nom pour la tâche", + description: "Description", + description_description: "Entrez une description pour la tâche", + due_date: "Date d'échéance", + due_date_description: "Sélectionnez la date d'échéance pour la tâche", + success: "Tâche créée avec succès", + error: "Échec de la création de la tâche" + }, + sheet: { + title: "Créer une tâche", + update: "Mettre à jour la tâche", + update_description: "Mettez à jour le titre ou la description de la tâche." + }, + empty: { + description_create: "Créez une tâche d'atténuation pour ce risque, ajoutez un plan de traitement et assignez-le à un membre de l'équipe." + } + } + }, + settings: { + general: { + title: "Général", + org_name: "Nom de l'organisation", + org_name_description: "C'est le nom visible de votre organisation. Vous devez utiliser le nom légal de votre organisation.", + org_name_tip: "Veuillez utiliser un maximum de 32 caractères.", + org_website: "Site Web de l'organisation", + org_website_description: "C'est l'URL du site Web officiel de votre organisation. Assurez-vous d'inclure l'URL complète avec https://.", + org_website_tip: "Veuillez entrer une URL valide incluant https://", + org_website_error: "Erreur lors de la mise à jour du site Web de l'organisation", + org_website_updated: "Site Web de l'organisation mis à jour", + org_delete: "Supprimer l'organisation", + org_delete_description: "Supprimez définitivement votre organisation et tout son contenu de la plateforme Comp AI. Cette action n'est pas réversible - veuillez continuer avec prudence.", + org_delete_alert_title: "Êtes-vous absolument sûr ?", + org_delete_alert_description: "Cette action ne peut pas être annulée. Cela supprimera définitivement votre organisation et retirera vos données de nos serveurs.", + org_delete_error: "Erreur lors de la suppression de l'organisation", + org_delete_success: "Organisation supprimée", + org_name_updated: "Nom de l'organisation mis à jour", + org_name_error: "Erreur lors de la mise à jour du nom de l'organisation", + save_button: "Enregistrer", + delete_button: "Supprimer", + delete_confirm: "SUPPRIMER", + delete_confirm_tip: "Tapez SUPPRIMER pour confirmer.", + cancel_button: "Annuler" + }, + members: { + title: "Membres" + }, + billing: { + title: "Facturation" + }, + api_keys: { + title: "Clés API", + description: "Gérez les clés API pour un accès programmatique aux données de votre organisation.", + list_title: "Clés API", + list_description: "Les clés API permettent un accès sécurisé aux données de votre organisation via notre API.", + create: "Nouvelle clé API", + create_title: "Nouvelle clé API", + create_description: "Créez une nouvelle clé API pour un accès programmatique aux données de votre organisation.", + created_title: "Clé API créée", + created_description: "Votre clé API a été créée. Assurez-vous de la copier maintenant car vous ne pourrez plus la voir.", + name: "Nom", + name_label: "Nom", + name_placeholder: "Entrez un nom pour cette clé API", + expiration: "Expiration", + expiration_placeholder: "Sélectionner l'expiration", + expires_label: "Expire", + expires_placeholder: "Sélectionner l'expiration", + expires_30days: "30 jours", + expires_90days: "90 jours", + expires_1year: "1 an", + expires_never: "Jamais", + thirty_days: "30 jours", + ninety_days: "90 jours", + one_year: "1 an", + your_key: "Votre clé API", + api_key: "Clé API", + save_warning: "Cette clé ne sera affichée qu'une seule fois. Assurez-vous de la copier maintenant.", + copied: "Clé API copiée dans le presse-papiers", + close_confirm: "Êtes-vous sûr de vouloir fermer ? Vous ne pourrez plus voir cette clé API.", + revoke_confirm: "Êtes-vous sûr de vouloir révoquer cette clé API ? Cette action ne peut pas être annulée.", + revoke_title: "Révoquer la clé API", + revoke: "Révoquer", + created: "Créée", + expires: "Expire", + last_used: "Dernière utilisation", + actions: "Actions", + never: "Jamais", + never_used: "Jamais utilisée", + no_keys: "Aucune clé API trouvée. Créez-en une pour commencer.", + security_note: "Les clés API fournissent un accès complet aux données de votre organisation. Gardez-les sécurisées et renouvelez-les régulièrement.", + fetch_error: "Échec de la récupération des clés API", + create_error: "Échec de la création de la clé API", + revoked_success: "Clé API révoquée avec succès", + revoked_error: "Échec de la révocation de la clé API", + done: "Terminé" + }, + team: { + tabs: { + members: "Membres de l'équipe", + invite: "Inviter des membres" + }, + members: { + title: "Membres de l'équipe", + empty: { + no_organization: { + title: "Aucune organisation", + description: "Vous ne faites partie d'aucune organisation" + }, + no_members: { + title: "Aucun membre", + description: "Il n'y a aucun membre actif dans votre organisation" + } + }, + role: { + owner: "Propriétaire", + admin: "Administrateur", + member: "Membre", + viewer: "Spectateur" + } + }, + invitations: { + title: "Invitations en attente", + description: "Utilisateurs qui ont été invités mais n'ont pas encore accepté", + empty: { + no_organization: { + title: "Aucune organisation", + description: "Vous ne faites partie d'aucune organisation" + }, + no_invitations: { + title: "Aucune invitation en attente", + description: "Il n'y a aucune invitation en attente" + } + }, + invitation_sent: "Invitation envoyée", + actions: { + resend: "Renvoyer l'invitation", + sending: "Envoi de l'invitation", + revoke: "Révoquer", + revoke_title: "Révoquer l'invitation", + revoke_description_prefix: "Êtes-vous sûr de vouloir révoquer l'invitation pour", + revoke_description_suffix: "Cette action ne peut pas être annulée." + }, + toast: { + resend_success_prefix: "Un e-mail d'invitation a été envoyé à", + resend_error: "Échec de l'envoi de l'invitation", + resend_unexpected: "Une erreur inattendue s'est produite lors de l'envoi de l'invitation", + revoke_success_prefix: "Invitation à", + revoke_success_suffix: "a été révoquée", + revoke_error: "Échec de la révocation de l'invitation", + revoke_unexpected: "Une erreur inattendue s'est produite lors de la révocation de l'invitation" + } + }, + invite: { + title: "Inviter un membre de l'équipe", + description: "Envoyer une invitation à un nouveau membre de l'équipe pour rejoindre votre organisation", + form: { + email: { + label: "E-mail", + placeholder: "membre@example.com", + error: "Veuillez entrer une adresse e-mail valide" + }, + role: { + label: "Rôle", + placeholder: "Sélectionner un rôle", + error: "Veuillez sélectionner un rôle" + }, + department: { + label: "Département", + placeholder: "Sélectionner un département", + error: "Veuillez sélectionner un département" + }, + departments: { + none: "Aucun", + it: "Informatique", + hr: "Ressources humaines", + admin: "Administrateur", + gov: "Gouvernement", + itsm: "ITSM", + qms: "QMS" + } + }, + button: { + send: "Envoyer l'invitation", + sending: "Envoi de l'invitation...", + sent: "Invitation envoyée" + }, + toast: { + error: "Échec de l'envoi de l'invitation", + unexpected: "Une erreur inattendue s'est produite lors de l'envoi de l'invitation" + } + }, + member_actions: { + actions: "Actions", + change_role: "Changer de rôle", + remove_member: "Supprimer un membre", + remove_confirm: { + title: "Supprimer un membre de l'équipe", + description_prefix: "Êtes-vous sûr de vouloir supprimer", + description_suffix: "Cette action ne peut pas être annulée." + }, + role_dialog: { + title: "Changer de rôle", + description_prefix: "Mettre à jour le rôle de", + role_label: "Rôle", + role_placeholder: "Sélectionner un rôle", + role_descriptions: { + admin: "Les administrateurs peuvent gérer les membres de l'équipe et les paramètres.", + member: "Les membres peuvent utiliser toutes les fonctionnalités mais ne peuvent pas gérer les membres de l'équipe.", + viewer: "Les spectateurs ne peuvent que visualiser le contenu sans apporter de modifications." + }, + cancel: "Annuler", + update: "Mettre à jour le rôle" + }, + toast: { + remove_success: "a été retiré de l'organisation", + remove_error: "Échec de la suppression du membre", + remove_unexpected: "Une erreur inattendue s'est produite lors de la suppression du membre", + update_role_success: "a eu son rôle mis à jour en", + update_role_error: "Échec de la mise à jour du rôle du membre", + update_role_unexpected: "Une erreur inattendue s'est produite lors de la mise à jour du rôle du membre" + } + } + } + }, + user_menu: { + theme: "Thème", + language: "Langue", + sign_out: "Se déconnecter", + account: "Compte", + support: "Assistance", + settings: "Paramètres", + teams: "Équipes" + }, + frameworks: { + title: "Cadres", + controls: { + title: "Contrôles", + description: "Examiner et gérer les contrôles de conformité", + table: { + status: "Statut", + control: "Contrôle", + artifacts: "Artifacts", + actions: "Actions" + }, + statuses: { + not_started: "Non commencé", + completed: "Terminé", + in_progress: "En cours" + } + }, + overview: { + error: "Échec du chargement des cadres", + loading: "Chargement des cadres...", + empty: { + title: "Aucun cadre sélectionné", + description: "Sélectionnez des cadres pour commencer votre parcours de conformité" + }, + progress: { + title: "Progression du cadre", + empty: { + title: "Pas encore de cadres", + description: "Commencez par ajouter un cadre de conformité pour suivre vos progrès", + action: "Ajouter un cadre" + } + }, + grid: { + welcome: { + title: "Bienvenue dans Comp AI", + description: "Commencez par sélectionner les cadres de conformité que vous souhaitez mettre en œuvre. Nous vous aiderons à gérer et à suivre votre parcours de conformité à travers plusieurs normes.", + action: "Commencer" + }, + title: "Sélectionner des cadres", + version: "Version", + actions: { + clear: "Effacer", + confirm: "Confirmer la sélection" + } + } + } + }, + vendor: { + title: "Tableau de bord", + register_title: "Gestion des fournisseurs", + dashboard: { + title: "Tableau de bord", + overview: "Aperçu des fournisseurs", + vendor_status: "Statut du fournisseur", + vendor_category: "Catégories de fournisseurs", + vendors_by_assignee: "Fournisseurs par responsable", + inherent_risk_description: "Niveau de risque initial avant l'application de tout contrôle", + residual_risk_description: "Niveau de risque restant après l'application des contrôles" + }, + register: { + title: "Registre des fournisseurs", + table: { + name: "Nom", + category: "Catégorie", + status: "Statut", + owner: "Propriétaire" + } + }, + category: { + cloud: "Cloud", + infrastructure: "Infrastructure", + software_as_a_service: "SaaS", + finance: "Finance", + marketing: "Marketing", + sales: "Ventes", + hr: "Ressources humaines", + other: "Autre" + }, + vendors: "fournisseurs", + form: { + vendor_details: "Détails du fournisseur", + vendor_name: "Nom", + vendor_name_placeholder: "Entrez le nom du fournisseur", + vendor_website: "Site web", + vendor_website_placeholder: "Entrez le site web du fournisseur", + vendor_description: "Description", + vendor_description_placeholder: "Entrez la description du fournisseur", + vendor_category: "Catégorie", + vendor_category_placeholder: "Sélectionner une catégorie", + vendor_status: "Statut", + vendor_status_placeholder: "Sélectionner un statut", + create_vendor_success: "Fournisseur créé avec succès", + create_vendor_error: "Échec de la création du fournisseur", + update_vendor_success: "Fournisseur mis à jour avec succès", + update_vendor_error: "Échec de la mise à jour du fournisseur", + contacts: "Contacts du fournisseur", + contact_name: "Nom du contact", + contact_email: "Email du contact", + contact_role: "Rôle du contact", + add_contact: "Ajouter un contact", + new_contact: "Nouveau contact", + min_one_contact_required: "Un fournisseur doit avoir au moins un contact" + }, + empty_states: { + no_vendors: { + title: "Pas encore de fournisseurs", + description: "Commencez par créer votre premier fournisseur" + }, + no_results: { + title: "Aucun résultat trouvé", + description: "Aucun fournisseur ne correspond à votre recherche", + description_with_filters: "Essayez d'ajuster vos filtres" + } + } + }, + people: { + title: "Personnes", + details: { + taskProgress: "Progression de la tâche", + tasks: "Tâches", + noTasks: "Aucune tâche assignée pour le moment" + }, + description: "Gérez vos membres d'équipe et leurs rôles.", + filters: { + search: "Rechercher des personnes...", + role: "Filtrer par rôle" + }, + actions: { + invite: "Ajouter un employé", + clear: "Effacer les filtres" + }, + table: { + name: "Nom", + email: "Email", + department: "Département", + externalId: "ID externe", + status: "Statut" + }, + empty: { + no_employees: { + title: "Aucun employé pour le moment", + description: "Commencez par inviter votre premier membre d'équipe." + }, + no_results: { + title: "Aucun résultat trouvé", + description: "Aucun employé ne correspond à votre recherche", + description_with_filters: "Essayez d'ajuster vos filtres" + } + }, + invite: { + title: "Ajouter un employé", + description: "Ajoutez un employé à votre organisation.", + email: { + label: "Adresse e-mail", + placeholder: "Entrez l'adresse e-mail" + }, + role: { + label: "Rôle", + placeholder: "Sélectionnez un rôle" + }, + name: { + label: "Nom", + placeholder: "Entrez le nom" + }, + department: { + label: "Département", + placeholder: "Sélectionnez un département" + }, + submit: "Ajouter un employé", + success: "Employé ajouté avec succès", + error: "Échec de l'ajout de l'employé" + } + }, + errors: { + unexpected: "Quelque chose s'est mal passé, veuillez réessayer" + }, + sub_pages: { + risk: { + overview: "Gestion des risques", + register: "Registre des risques", + risk_overview: "Aperçu des risques", + risk_comments: "Commentaires sur les risques", + tasks: { + task_overview: "Aperçu des tâches" + } + }, + policies: { + all: "Toutes les politiques", + editor: "Éditeur de politique", + policy_details: "Détails de la politique" + }, + people: { + all: "Personnes", + employee_details: "Détails de l'employé" + }, + settings: { + members: "Membres de l'équipe" + }, + frameworks: { + overview: "Cadres" + }, + evidence: { + title: "Preuve", + list: "Liste des preuves", + overview: "Aperçu des preuves" + }, + tests: { + overview: "Tests en nuage", + test_details: "Détails du test" + } + }, + editor: { + ai: { + thinking: "L'IA réfléchit", + thinking_spinner: "L'IA réfléchit", + edit_or_generate: "Modifier ou générer...", + tell_ai_what_to_do_next: "Dites à l'IA quoi faire ensuite", + request_limit_reached: "Vous avez atteint votre limite de demandes pour la journée." + }, + ai_selector: { + improve: "Améliorer l'écriture", + fix: "Corriger la grammaire", + shorter: "Raccourcir", + longer: "Allonger", + "continue": "Continuer l'écriture", + replace: "Remplacer la sélection", + insert: "Insérer ci-dessous", + discard: "Jeter" + } + }, + evidence: { + title: "Preuve", + list: "Toutes les preuves", + overview: "Aperçu des preuves", + edit: "Preuve Téléchargée", + dashboard: { + layout: "Tableau de bord", + layout_back_button: "Retour", + title: "Tableau de bord des preuves", + by_department: "Par département", + by_assignee: "Par responsable", + by_framework: "Par cadre" + }, + items: "articles", + status: { + up_to_date: "À jour", + needs_review: "Nécessite une révision", + draft: "Brouillon", + empty: "Vide" + }, + departments: { + none: "Non classé", + admin: "Administration", + gov: "Gouvernance", + hr: "Ressources humaines", + it: "Technologies de l'information", + itsm: "Gestion des services informatiques", + qms: "Gestion de la qualité" + }, + details: { + review_section: "Informations de révision", + content: "Contenu de la preuve" + } + }, + upload: { + fileSection: { + filesUploaded: "{count} fichier(s) téléchargé(s)", + upload: "{count} fichier(s) téléchargé(s)" + }, + fileUpload: { + uploadingText: "Téléchargement en cours...", + dropFileHere: "Déposez le fichier ici", + releaseToUpload: "Relâchez pour télécharger", + addFiles: "Ajouter des fichiers", + uploadAdditionalEvidence: "Télécharger un fichier", + dragDropOrClick: "Glissez-déposez ou cliquez pour télécharger", + dropFileHereAlt: "Déposez le fichier ici", + dragDropOrClickToSelect: "Glissez-déposez un fichier ici, ou cliquez pour sélectionner", + maxFileSize: "Taille maximale du fichier : {size} Mo", + uploadingFile: "Téléchargement du fichier..." + }, + fileCard: { + preview: "Aperçu", + previewNotAvailable: "Aperçu non disponible. Cliquez sur le bouton de téléchargement pour voir le fichier.", + filePreview: "Aperçu du fichier : {fileName}", + openFile: "Ouvrir le fichier", + deleteFile: "Supprimer le fichier", + deleteFileConfirmTitle: "Supprimer le fichier", + deleteFileConfirmDescription: "Êtes-vous sûr de vouloir supprimer ce fichier ? Cette action ne peut pas être annulée." + }, + fileUrl: { + additionalLinks: "Liens supplémentaires", + add: "Ajouter", + linksAdded: "{count} lien{s} ajouté{s}", + enterUrl: "Entrez l'URL", + addAnotherLink: "Ajouter un autre lien", + saveLinks: "Enregistrer les liens", + urlBadge: "URL", + copyLink: "Copier le lien", + openLink: "Ouvrir le lien", + deleteLink: "Supprimer le lien" + } + }, + tests: { + name: "Tests Cloud", + title: "Tests Cloud", + actions: { + create: "Ajouter un test Cloud", + clear: "Effacer les filtres", + refresh: "Rafraîchir", + refresh_success: "Tests actualisés avec succès", + refresh_error: "Échec de l'actualisation des tests" + }, + empty: { + no_tests: { + title: "Aucun test cloud pour le moment", + description: "Commencez par créer votre premier test dans le cloud." + }, + no_results: { + title: "Aucun résultat trouvé", + description: "Aucun test ne correspond à votre recherche", + description_with_filters: "Essayez d'ajuster vos filtres" + } + }, + filters: { + search: "Rechercher des tests...", + role: "Filtrer par fournisseur" + }, + register: { + title: "Ajouter un test Cloud", + description: "Configurer un nouveau test de conformité cloud.", + submit: "Créer un test", + success: "Test créé avec succès", + invalid_json: "Configuration JSON invalide fournie", + title_field: { + label: "Titre du test", + placeholder: "Entrez le titre du test" + }, + description_field: { + label: "Description", + placeholder: "Entrez la description du test" + }, + provider: { + label: "Fournisseur Cloud", + placeholder: "Sélectionnez le fournisseur cloud" + }, + config: { + label: "Configuration du test", + placeholder: "Entrez la configuration JSON pour le test" + }, + auth_config: { + label: "Configuration d'authentification", + placeholder: "Entrez la configuration d'authentification JSON" + } + }, + table: { + title: "Titre", + provider: "Fournisseur", + severity: "Gravité", + result: "Résultat", + createdAt: "Créé le", + assignedUser: "Utilisateur assigné", + assignedUserEmpty: "Non assigné", + no_results: "Aucun résultat trouvé", + status: "Statut" + }, + dashboard: { + overview: "Aperçu", + all: "Tous les tests", + tests_by_assignee: "Tests par le responsable", + passed: "Réussi", + failed: "Échoué", + severity_distribution: "Distribution de la gravité des tests" + }, + severity: { + low: "Faible", + medium: "Moyen", + high: "Élevé", + critical: "Critique" + } + }, + vendors: { + title: "Fournisseurs", + register: { + title: "Enregistrement des fournisseurs" + }, + dashboard: { + title: "Aperçu des fournisseurs" + } + }, + dashboard: { + risk_status: "Statut de risque", + risks_by_department: "Risques par département", + vendor_status: "Statut des fournisseurs", + vendors_by_category: "Fournisseurs par catégorie" + }, + team: { + tabs: { + members: "Membres de l'équipe", + invite: "Inviter des membres" + }, + members: { + title: "Membres de l'équipe", + empty: { + no_organization: { + title: "Aucune organisation", + description: "Vous ne faites partie d'aucune organisation" + }, + no_members: { + title: "Aucun membre", + description: "Il n'y a aucun membre actif dans votre organisation" + } + }, + role: { + owner: "Propriétaire", + admin: "Administrateur", + member: "Membre", + viewer: "Spectateur" + } + }, + invitations: { + title: "Invitations en attente", + description: "Utilisateurs qui ont été invités mais n'ont pas encore accepté", + empty: { + no_organization: { + title: "Aucune organisation", + description: "Vous ne faites partie d'aucune organisation" + }, + no_invitations: { + title: "Aucune invitation en attente", + description: "Il n'y a aucune invitation en attente" + } + }, + invitation_sent: "Invitation envoyée", + actions: { + resend: "Renvoyer l'invitation", + sending: "Envoi de l'invitation", + revoke: "Révoquer", + revoke_title: "Révoquer l'invitation", + revoke_description_prefix: "Êtes-vous sûr de vouloir révoquer l'invitation pour", + revoke_description_suffix: "Cette action ne peut pas être annulée." + }, + toast: { + resend_success_prefix: "Un e-mail d'invitation a été envoyé à", + resend_error: "Échec de l'envoi de l'invitation", + resend_unexpected: "Une erreur inattendue s'est produite lors de l'envoi de l'invitation", + revoke_success_prefix: "Invitation à", + revoke_success_suffix: "a été révoquée", + revoke_error: "Échec de la révocation de l'invitation", + revoke_unexpected: "Une erreur inattendue s'est produite lors de la révocation de l'invitation" + } + }, + invite: { + title: "Inviter un membre de l'équipe", + description: "Envoyer une invitation à un nouveau membre de l'équipe pour rejoindre votre organisation", + form: { + email: { + label: "E-mail", + placeholder: "membre@exemple.com", + error: "Veuillez entrer une adresse e-mail valide" + }, + role: { + label: "Rôle", + placeholder: "Sélectionner un rôle", + error: "Veuillez sélectionner un rôle" + }, + department: { + label: "Département", + placeholder: "Sélectionner un département", + error: "Veuillez sélectionner un département" + }, + departments: { + none: "Aucun", + it: "Informatique", + hr: "Ressources humaines", + admin: "Administrateur", + gov: "Gouvernement", + itsm: "ITSM", + qms: "QMS" + } + }, + button: { + send: "Envoyer l'invitation", + sending: "Envoi de l'invitation...", + sent: "Invitation envoyée" + }, + toast: { + error: "Échec de l'envoi de l'invitation", + unexpected: "Une erreur inattendue s'est produite lors de l'envoi de l'invitation" + } + }, + member_actions: { + actions: "Actions", + change_role: "Changer de rôle", + remove_member: "Supprimer un membre", + remove_confirm: { + title: "Supprimer un membre de l'équipe", + description_prefix: "Êtes-vous sûr de vouloir supprimer", + description_suffix: "Cette action ne peut pas être annulée." + }, + role_dialog: { + title: "Changer de rôle", + description_prefix: "Mettre à jour le rôle de", + role_label: "Rôle", + role_placeholder: "Sélectionner un rôle", + role_descriptions: { + admin: "Les administrateurs peuvent gérer les membres de l'équipe et les paramètres.", + member: "Les membres peuvent utiliser toutes les fonctionnalités mais ne peuvent pas gérer les membres de l'équipe.", + viewer: "Les spectateurs peuvent uniquement visualiser le contenu sans apporter de modifications." + }, + cancel: "Annuler", + update: "Mettre à jour le rôle" + }, + toast: { + remove_success: "a été retiré de l'organisation", + remove_error: "Échec de la suppression du membre", + remove_unexpected: "Une erreur inattendue s'est produite lors de la suppression du membre", + update_role_success: "a vu son rôle mis à jour en", + update_role_error: "Échec de la mise à jour du rôle du membre", + update_role_unexpected: "Une erreur inattendue s'est produite lors de la mise à jour du rôle du membre" + } + } + } } as const; diff --git a/apps/app/src/locales/no.ts b/apps/app/src/locales/no.ts index 20f310afb6..ac1e7a041f 100644 --- a/apps/app/src/locales/no.ts +++ b/apps/app/src/locales/no.ts @@ -253,8 +253,8 @@ export default { }, onboarding: { title: "Opprett en organisasjon", - setup: "Oppsett", - description: "Fortell oss litt om organisasjonen din.", + setup: "Velkommen til Comp AI", + description: "Fortell oss litt om organisasjonen din og hvilke rammeverk du ønsker å begynne med.", fields: { name: { label: "Organisasjonsnavn", @@ -277,7 +277,16 @@ export default { error: "Noe gikk galt, vennligst prøv igjen.", unavailable: "Ikke tilgjengelig", check_availability: "Sjekker tilgjengelighet", - available: "Tilgjengelig" + available: "Tilgjengelig", + submit: "Fullfør oppsett", + trigger: { + title: "Hold deg fast, vi oppretter organisasjonen din", + creating: "Dette kan ta et minutt eller to...", + completed: "Organisasjonen ble opprettet med suksess", + "continue": "Fortsett til dashbordet", + error: "Noe gikk galt, vennligst prøv igjen." + }, + creating: "Oppretter organisasjonen din..." }, overview: { title: "Oversikt", diff --git a/apps/app/src/locales/pt.ts b/apps/app/src/locales/pt.ts index f5285495b4..cdc69d5547 100644 --- a/apps/app/src/locales/pt.ts +++ b/apps/app/src/locales/pt.ts @@ -253,8 +253,8 @@ export default { }, onboarding: { title: "Criar uma organização", - setup: "Configuração", - description: "Conte-nos um pouco sobre sua organização.", + setup: "Bem-vindo ao Comp AI", + description: "Conte-nos um pouco sobre sua organização e quais framework(s) você deseja começar a usar.", fields: { name: { label: "Nome da Organização", @@ -277,7 +277,16 @@ export default { error: "Algo deu errado, por favor tente novamente.", unavailable: "Indisponível", check_availability: "Verificando disponibilidade", - available: "Disponível" + available: "Disponível", + submit: "Finalizar configuração", + trigger: { + title: "Aguarde, estamos criando sua organização", + creating: "Isso pode levar um ou dois minutos...", + completed: "Organização criada com sucesso", + "continue": "Continuar para o painel", + error: "Algo deu errado, por favor, tente novamente." + }, + creating: "Criando sua organização..." }, overview: { title: "Visão Geral",