Add react i18next to console
Signed-off-by: Jonathan <contact@grafikart.fr>
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
"cmdk": "^1.1.1",
|
||||
"mermaid": "^11.15.0",
|
||||
"react-dropzone": "^15.0.0",
|
||||
"react-i18next": "^17.0.10",
|
||||
"react-intersection-observer": "^10.0.3",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { useRefSync } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useCallback } from "react";
|
||||
import { type FileRejection, useDropzone } from "react-dropzone";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { tv } from "tailwind-variants";
|
||||
|
||||
import { IconPageCross, IconUpload } from "../Icons";
|
||||
@@ -79,7 +79,7 @@ export const dropzone = tv({
|
||||
const MB = 1024 * 1024;
|
||||
|
||||
export function Dropzone(props: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const onDropRef = useRefSync(props.onDrop);
|
||||
const onDrop = useCallback(
|
||||
(files: File[]) => {
|
||||
@@ -94,7 +94,7 @@ export function Dropzone(props: Props) {
|
||||
onDrop,
|
||||
maxSize: props.maxSize ? props.maxSize * MB : undefined,
|
||||
});
|
||||
const error = getDropzoneError(__, fileRejections);
|
||||
const error = getDropzoneError(t, fileRejections);
|
||||
const { wrapper, zone, title, description } = dropzone({
|
||||
...props,
|
||||
isDragActive,
|
||||
@@ -109,7 +109,7 @@ export function Dropzone(props: Props) {
|
||||
? (
|
||||
<div className={title({ isDragActive: true })}>
|
||||
<Spinner />
|
||||
{__("Uploading file")}
|
||||
{t("ui.dropzone.uploading")}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
@@ -122,7 +122,7 @@ export function Dropzone(props: Props) {
|
||||
: (
|
||||
<IconUpload size={20} />
|
||||
)}
|
||||
{error ?? __("Drag and drop or browse files")}
|
||||
{error ?? t("ui.dropzone.browse")}
|
||||
</div>
|
||||
<div className={description()}>{props.description}</div>
|
||||
</div>
|
||||
@@ -133,7 +133,7 @@ export function Dropzone(props: Props) {
|
||||
}
|
||||
|
||||
function getDropzoneError(
|
||||
__: (s: string) => string,
|
||||
t: (key: string) => string,
|
||||
fileRejections: readonly FileRejection[],
|
||||
) {
|
||||
if (fileRejections.length === 0) {
|
||||
@@ -143,10 +143,10 @@ function getDropzoneError(
|
||||
const code = fileRejections[0].errors[0].code;
|
||||
switch (code) {
|
||||
case "file-invalid-type":
|
||||
return __("This file is not supported");
|
||||
return t("ui.dropzone.errors.unsupported");
|
||||
case "file-too-large":
|
||||
return __("This file is too large");
|
||||
return t("ui.dropzone.errors.tooLarge");
|
||||
default:
|
||||
return __("Something went wrong");
|
||||
return t("ui.dropzone.errors.generic");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { useRefSync } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { type ReactNode, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useInView } from "react-intersection-observer";
|
||||
|
||||
import { Spinner } from "../Spinner/Spinner";
|
||||
@@ -32,7 +32,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export function InfiniteScrollTrigger({ children, onView, loading }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const { ref, inView } = useInView({
|
||||
threshold: 0,
|
||||
});
|
||||
@@ -49,7 +49,7 @@ export function InfiniteScrollTrigger({ children, onView, loading }: Props) {
|
||||
{children ?? (
|
||||
<>
|
||||
<Spinner size={16} />
|
||||
{__("Loading")}
|
||||
{t("ui.loading")}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { getDocumentClassificationLabel } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Badge } from "../../Atoms/Badge/Badge";
|
||||
|
||||
@@ -28,7 +27,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export function DocumentClassificationBadge({ classification }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Choose badge variant based on classification level
|
||||
const variant = (() => {
|
||||
@@ -48,7 +47,7 @@ export function DocumentClassificationBadge({ classification }: Props) {
|
||||
|
||||
return (
|
||||
<Badge variant={variant}>
|
||||
{getDocumentClassificationLabel(__, classification)}
|
||||
{t(`ui.documentClassification.${classification.toLowerCase()}`)}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { getDocumentTypeLabel } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Badge } from "../../Atoms/Badge/Badge";
|
||||
|
||||
@@ -28,6 +27,10 @@ type Props = {
|
||||
};
|
||||
|
||||
export function DocumentTypeBadge({ type }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
return <Badge variant="neutral">{getDocumentTypeLabel(__, type)}</Badge>;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Badge variant="neutral">
|
||||
{t(`ui.documentType.${type.toLowerCase()}`)}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Badge } from "../../Atoms/Badge/Badge";
|
||||
|
||||
@@ -27,7 +27,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export function DocumentVersionBadge(props: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const variant = {
|
||||
DRAFT: "neutral",
|
||||
@@ -36,9 +36,9 @@ export function DocumentVersionBadge(props: Props) {
|
||||
} as const;
|
||||
|
||||
const label = {
|
||||
DRAFT: __("Draft"),
|
||||
PENDING_APPROVAL: __("Pending approval"),
|
||||
PUBLISHED: __("Published"),
|
||||
DRAFT: t("ui.documentVersionBadge.draft"),
|
||||
PENDING_APPROVAL: t("ui.documentVersionBadge.pendingApproval"),
|
||||
PUBLISHED: t("ui.documentVersionBadge.published"),
|
||||
} as const;
|
||||
|
||||
return <Badge variant={variant[props.state]}>{label[props.state]}</Badge>;
|
||||
|
||||
@@ -19,9 +19,8 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import type { measureStates } from "@probo/helpers";
|
||||
import { getMeasureStateLabel } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import type { ComponentProps } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Badge } from "../../Atoms/Badge/Badge";
|
||||
|
||||
@@ -42,10 +41,10 @@ const stateToVariant: Record<
|
||||
};
|
||||
|
||||
export function MeasureBadge({ state }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Badge variant={stateToVariant[state]}>
|
||||
{getMeasureStateLabel(__, state)}
|
||||
{t(`ui.measureState.${state.toLowerCase()}`)}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Badge } from "../../Atoms/Badge/Badge";
|
||||
|
||||
@@ -51,30 +51,30 @@ const badgeVariant = (level: string | number) => {
|
||||
};
|
||||
|
||||
export function RiskBadge({ level }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const label = () => {
|
||||
if (typeof level === "number") {
|
||||
if (level >= 15) {
|
||||
return __("High");
|
||||
return t("ui.risk.severity.high");
|
||||
}
|
||||
if (level >= 8) {
|
||||
return __("Medium");
|
||||
return t("ui.risk.severity.medium");
|
||||
}
|
||||
return __("Low");
|
||||
return t("ui.risk.severity.low");
|
||||
}
|
||||
switch (level) {
|
||||
case "CRITICAL":
|
||||
return __("Critical");
|
||||
return t("ui.risk.severity.critical");
|
||||
case "HIGH":
|
||||
return __("High");
|
||||
return t("ui.risk.severity.high");
|
||||
case "LOW":
|
||||
return __("Low");
|
||||
return t("ui.risk.severity.low");
|
||||
case "MEDIUM":
|
||||
return __("Medium");
|
||||
return t("ui.risk.severity.medium");
|
||||
case "NONE":
|
||||
return __("None");
|
||||
return t("ui.risk.severity.none");
|
||||
default:
|
||||
return __("Low");
|
||||
return t("ui.risk.severity.low");
|
||||
}
|
||||
};
|
||||
return <Badge variant={badgeVariant(level)}>{label()}</Badge>;
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { getSeverity } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Badge } from "../../Atoms/Badge/Badge";
|
||||
|
||||
@@ -28,7 +27,15 @@ type Props = {
|
||||
};
|
||||
|
||||
export function SeverityBadge({ score }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const severity = getSeverity(__, score);
|
||||
return <Badge variant={severity?.variant}>{severity?.label}</Badge>;
|
||||
const { t } = useTranslation();
|
||||
const severity = score >= 15
|
||||
? { variant: "danger" as const, key: "critical" }
|
||||
: score >= 5
|
||||
? { variant: "warning" as const, key: "high" }
|
||||
: { variant: "neutral" as const, key: "low" };
|
||||
return (
|
||||
<Badge variant={severity.variant}>
|
||||
{t(`ui.risk.severity.${severity.key}`)}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import {
|
||||
Cancel,
|
||||
Content,
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
} from "@radix-ui/react-alert-dialog";
|
||||
import { Root as Portal } from "@radix-ui/react-portal";
|
||||
import { type ComponentProps, useCallback, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { create } from "zustand";
|
||||
import { combine } from "zustand/middleware";
|
||||
|
||||
@@ -70,7 +70,7 @@ const useConfirmStore = create(
|
||||
*/
|
||||
export function useConfirm() {
|
||||
const open = useConfirmStore(state => state.open);
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useCallback(
|
||||
(cb: State["onConfirm"], props: Omit<State, "onConfirm">) => {
|
||||
@@ -78,12 +78,12 @@ export function useConfirm() {
|
||||
onConfirm: cb,
|
||||
...props,
|
||||
message: props.message,
|
||||
title: props.title ?? __("Are you sure ?"),
|
||||
title: props.title ?? t("ui.confirmDialog.defaultTitle"),
|
||||
variant: props.variant ?? "danger",
|
||||
label: props.label ?? __("Delete"),
|
||||
label: props.label ?? t("ui.actions.delete"),
|
||||
});
|
||||
},
|
||||
[open, __],
|
||||
[open, t],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ function ConfirmDialogContent() {
|
||||
const onConfirm = useConfirmStore(state => state.onConfirm);
|
||||
const close = useConfirmStore(state => state.close);
|
||||
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const isOpen = !!message;
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -156,7 +156,7 @@ function ConfirmDialogContent() {
|
||||
<footer className={dialogStyles.footer}>
|
||||
<Cancel asChild>
|
||||
<Button disabled={loading} variant="tertiary">
|
||||
{__("Cancel")}
|
||||
{t("ui.actions.cancel")}
|
||||
</Button>
|
||||
</Cancel>
|
||||
<Button
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import {
|
||||
Close,
|
||||
Content,
|
||||
@@ -42,6 +41,7 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { tv } from "tailwind-variants";
|
||||
|
||||
import { Button } from "../../Atoms/Button/Button";
|
||||
@@ -210,14 +210,14 @@ export function DialogFooter({
|
||||
// switches to justify-between; otherwise it keeps the default right alignment.
|
||||
start?: ReactNode;
|
||||
}) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const { footer } = dialog();
|
||||
return (
|
||||
<footer className={footer({ className: clsx(start != null && "justify-between", className) })}>
|
||||
{start != null && <div className="flex items-center gap-2">{start}</div>}
|
||||
<div className="flex items-center gap-2">
|
||||
<Close asChild>
|
||||
<Button variant="secondary">{exitLabel ?? __("Cancel")}</Button>
|
||||
<Button variant="secondary">{exitLabel ?? t("ui.actions.cancel")}</Button>
|
||||
</Close>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import type { HTMLAttributes } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "../../Atoms/Button/Button";
|
||||
import { IconPlusLarge } from "../../Atoms/Icons";
|
||||
@@ -60,7 +60,7 @@ const parse = (value: string): { amount: number; unit: string } => {
|
||||
};
|
||||
|
||||
export function DurationPicker({ value, onValueChange, ...props }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
if (!value) {
|
||||
return (
|
||||
<div>
|
||||
@@ -92,10 +92,10 @@ export function DurationPicker({ value, onValueChange, ...props }: Props) {
|
||||
onValueChange={(v: string) =>
|
||||
onValueChange(stringify(amount, v))}
|
||||
>
|
||||
<Option value="M">{__("Minutes")}</Option>
|
||||
<Option value="H">{__("Hours")}</Option>
|
||||
<Option value="D">{__("Days")}</Option>
|
||||
<Option value="W">{__("Weeks")}</Option>
|
||||
<Option value="M">{t("ui.durationPicker.minutes")}</Option>
|
||||
<Option value="H">{t("ui.durationPicker.hours")}</Option>
|
||||
<Option value="D">{t("ui.durationPicker.days")}</Option>
|
||||
<Option value="W">{t("ui.durationPicker.weeks")}</Option>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "../../Atoms/Button/Button";
|
||||
import { Dropdown, DropdownItem } from "../../Atoms/Dropdown/Dropdown";
|
||||
@@ -128,7 +128,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export function FrameworkSelector({ disabled, onSelect }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Dropdown
|
||||
toggle={(
|
||||
@@ -137,7 +137,7 @@ export function FrameworkSelector({ disabled, onSelect }: Props) {
|
||||
iconAfter={IconChevronDown}
|
||||
disabled={disabled}
|
||||
>
|
||||
{__("New framework")}
|
||||
{t("ui.frameworkSelector.new")}
|
||||
</Button>
|
||||
)}
|
||||
>
|
||||
@@ -154,7 +154,7 @@ export function FrameworkSelector({ disabled, onSelect }: Props) {
|
||||
}
|
||||
|
||||
function FrameworkItem(props: { framework?: Framework; onClick: () => void }) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
if (!props.framework) {
|
||||
return (
|
||||
<DropdownItem onClick={props.onClick} className="">
|
||||
@@ -163,10 +163,10 @@ function FrameworkItem(props: { framework?: Framework; onClick: () => void }) {
|
||||
</div>
|
||||
<div className="space-y-[2px]">
|
||||
<div className="text-sm font-medium">
|
||||
{__("Custom framework")}
|
||||
{t("ui.frameworkSelector.custom.title")}
|
||||
</div>
|
||||
<div className="text-xs text-txt-secondary">
|
||||
{__("Start from scratch")}
|
||||
{t("ui.frameworkSelector.custom.description")}
|
||||
</div>
|
||||
</div>
|
||||
</DropdownItem>
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { getMeasureStateLabel, measureStates } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { measureStates } from "@probo/helpers";
|
||||
import { clsx } from "clsx";
|
||||
import type { ComponentProps } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { MeasureBadge } from "../Badge/MeasureBadge";
|
||||
|
||||
@@ -42,7 +42,7 @@ const stateToColor: Record<MeasureState, string> = {
|
||||
};
|
||||
|
||||
export function MeasureImplementation({ measures, className }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const counts = measures.reduce(
|
||||
(acc, measure) => {
|
||||
acc[measure.state] = (acc[measure.state] ?? 0) + 1;
|
||||
@@ -58,7 +58,7 @@ export function MeasureImplementation({ measures, className }: Props) {
|
||||
return (
|
||||
<div className={clsx("space-y-3", className)}>
|
||||
<h2 className="text-base font-medium">
|
||||
{__("Measure implementation")}
|
||||
{t("ui.measureImplementation.title")}
|
||||
</h2>
|
||||
<div className="h-2 rounded overflow-hidden bg-highlight flex justify-stretch item-stretch">
|
||||
{measureStates.map(state => (
|
||||
@@ -76,7 +76,7 @@ export function MeasureImplementation({ measures, className }: Props) {
|
||||
<div className="mr-auto">
|
||||
{percent}
|
||||
%
|
||||
{__("Complete")}
|
||||
{t("ui.measureImplementation.complete")}
|
||||
</div>
|
||||
)}
|
||||
{measureStates.map(state => (
|
||||
@@ -91,7 +91,7 @@ export function MeasureImplementation({ measures, className }: Props) {
|
||||
)}
|
||||
>
|
||||
</div>
|
||||
{getMeasureStateLabel(__, state)}
|
||||
{t(`ui.measureState.${state.toLowerCase()}`)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -23,8 +23,8 @@ import {
|
||||
getRiskLikelihoods,
|
||||
getSeverity,
|
||||
} from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { clsx } from "clsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Card } from "../../Atoms/Card/Card";
|
||||
|
||||
@@ -48,29 +48,31 @@ const getColor = (score: number): string => {
|
||||
};
|
||||
|
||||
export function RiskOverview({ type, risk }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const impact = risk?.[`${type}Impact`] ?? 0;
|
||||
const likelihood = risk?.[`${type}Likelihood`] ?? 0;
|
||||
const severity = getSeverity(__, impact * likelihood);
|
||||
const severity = getSeverity(t, impact * likelihood);
|
||||
return (
|
||||
<Card padded>
|
||||
<h2 className="font-semibold text-base mb-6">
|
||||
{type === "inherent" ? __("Initial Risk") : __("Residual Risk")}
|
||||
{type === "inherent"
|
||||
? t("ui.risk.initial")
|
||||
: t("ui.risk.residual")}
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||
<RiskOverviewBadge
|
||||
label={__("Impact")}
|
||||
label={t("ui.risk.impact.label")}
|
||||
textCb={getRiskImpacts}
|
||||
score={impact}
|
||||
/>
|
||||
<RiskOverviewBadge
|
||||
label={__("Likelihood")}
|
||||
label={t("ui.risk.likelihood.label")}
|
||||
textCb={getRiskLikelihoods}
|
||||
score={likelihood}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="font-medium text-xs">{__("Score")}</div>
|
||||
<div className="font-medium text-xs">{t("ui.risk.score")}</div>
|
||||
<div
|
||||
className={clsx(
|
||||
severity?.bg,
|
||||
@@ -94,17 +96,17 @@ function RiskOverviewBadge({
|
||||
label: string;
|
||||
textCb: (t: (s: string) => string) => { value: number; label: string }[];
|
||||
}) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="font-medium text-xs">{__(label)}</div>
|
||||
<div className="font-medium text-xs">{label}</div>
|
||||
<div
|
||||
className={clsx(
|
||||
getColor(score),
|
||||
"py-2 text-sm font-semibold rounded-lg text-txt-invert text-center",
|
||||
)}
|
||||
>
|
||||
{textCb(__).find(i => i.value === score)?.label}
|
||||
{textCb(t).find(i => i.value === score)?.label}
|
||||
{" "}
|
||||
(
|
||||
{score}
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { getRiskImpacts, getRiskLikelihoods, groupBy } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { clsx } from "clsx";
|
||||
import { Fragment, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router";
|
||||
|
||||
import { Card } from "../../Atoms/Card/Card";
|
||||
@@ -66,12 +66,16 @@ const cellKey = (impact: number, likelihood: number) =>
|
||||
* Displays a grid of risk grouped by impact & likelihood
|
||||
*/
|
||||
export function RisksChart({ organizationId, type, risks }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const legend = [__("Low"), __("High"), __("Critical")];
|
||||
const legend = [
|
||||
t("ui.risk.severity.low"),
|
||||
t("ui.risk.severity.high"),
|
||||
t("ui.risk.severity.critical"),
|
||||
];
|
||||
|
||||
const impacts = getRiskImpacts(__).reverse();
|
||||
const likelihoods = getRiskLikelihoods(__);
|
||||
const impacts = getRiskImpacts(t).reverse();
|
||||
const likelihoods = getRiskLikelihoods(t);
|
||||
const impactField
|
||||
= type === "inherent" ? "inherentImpact" : "residualImpact";
|
||||
const likelihoodField
|
||||
@@ -88,8 +92,8 @@ export function RisksChart({ organizationId, type, risks }: Props) {
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="font-semibold text-lg">
|
||||
{type === "inherent"
|
||||
? __("Initial Risk")
|
||||
: __("Residual Risk")}
|
||||
? t("ui.risk.initial")
|
||||
: t("ui.risk.residual")}
|
||||
</h2>
|
||||
<div className="flex gap-3">
|
||||
{legend.map((label, i) => (
|
||||
@@ -114,7 +118,7 @@ export function RisksChart({ organizationId, type, risks }: Props) {
|
||||
className="text-xs font-medium flex-none text-center"
|
||||
style={{ writingMode: "sideways-lr" }}
|
||||
>
|
||||
{__("Impact")}
|
||||
{t("ui.risk.impact.label")}
|
||||
</div>
|
||||
<div className="grid grid-cols-[90px_1fr_1fr_1fr_1fr_1fr] gap-1 w-full">
|
||||
{impacts.map(impact => (
|
||||
@@ -158,7 +162,7 @@ export function RisksChart({ organizationId, type, risks }: Props) {
|
||||
)
|
||||
{likelihood.value === 3 && (
|
||||
<div className="text-xs text-txt-primary font-medium flex-none text-center mt-3">
|
||||
{__("Likelihood")}
|
||||
{t("ui.risk.likelihood.label")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -180,7 +184,7 @@ function RisksChartCell({
|
||||
likelihood: number;
|
||||
organizationId: string;
|
||||
}) {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const level = getLevel(impact * likelihood);
|
||||
const baseClass
|
||||
= "flex items-center justify-center aspect-square rounded-xl text-txt-invert text-sm font-semibold";
|
||||
@@ -189,9 +193,9 @@ function RisksChartCell({
|
||||
}
|
||||
|
||||
const infos = [
|
||||
{ label: __("Number of risks"), value: risks.length },
|
||||
{ label: __("Impact"), value: impact },
|
||||
{ label: __("Likelihood"), value: likelihood },
|
||||
{ label: t("ui.risk.numberOfRisks"), value: risks.length },
|
||||
{ label: t("ui.risk.impact.label"), value: impact },
|
||||
{ label: t("ui.risk.likelihood.label"), value: likelihood },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -224,7 +228,9 @@ function RisksChartCell({
|
||||
<div className="text-txt-primary">{impact * likelihood}</div>
|
||||
</div>
|
||||
<DropdownSeparator className="my-3" />
|
||||
<div className="text-txt-secondary mb-1">{__("Linked Risks")}</div>
|
||||
<div className="text-txt-secondary mb-1">
|
||||
{t("ui.risk.linkedRisks")}
|
||||
</div>
|
||||
{risks.map(risk => (
|
||||
<DropdownItem key={risk.id} asChild>
|
||||
<Link
|
||||
|
||||
@@ -18,29 +18,29 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Option } from "../../Atoms/Select/Select";
|
||||
|
||||
export function ImpactOptions() {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const descriptions = {
|
||||
LOW: {
|
||||
label: __("Low"),
|
||||
description: __("Minimal impact on business"),
|
||||
label: t("ui.risk.severity.low"),
|
||||
description: t("ui.impactOptions.low"),
|
||||
},
|
||||
MEDIUM: {
|
||||
label: __("Medium"),
|
||||
description: __("Moderate impact on business"),
|
||||
label: t("ui.risk.severity.medium"),
|
||||
description: t("ui.impactOptions.medium"),
|
||||
},
|
||||
HIGH: {
|
||||
label: __("High"),
|
||||
description: __("Significant business impact"),
|
||||
label: t("ui.risk.severity.high"),
|
||||
description: t("ui.impactOptions.high"),
|
||||
},
|
||||
CRITICAL: {
|
||||
label: __("Critical"),
|
||||
description: __("Critical to business operations"),
|
||||
label: t("ui.risk.severity.critical"),
|
||||
description: t("ui.impactOptions.critical"),
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -18,33 +18,33 @@
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Option } from "../../Atoms/Select/Select";
|
||||
|
||||
export function SentitivityOptions() {
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const descriptions = {
|
||||
NONE: {
|
||||
label: __("None"),
|
||||
description: __("No sensitive data"),
|
||||
label: t("ui.risk.severity.none"),
|
||||
description: t("ui.sensitivityOptions.none"),
|
||||
},
|
||||
LOW: {
|
||||
label: __("Low"),
|
||||
description: __("Public or non-sensitive data"),
|
||||
label: t("ui.risk.severity.low"),
|
||||
description: t("ui.sensitivityOptions.low"),
|
||||
},
|
||||
MEDIUM: {
|
||||
label: __("Medium"),
|
||||
description: __("Internal/restricted data"),
|
||||
label: t("ui.risk.severity.medium"),
|
||||
description: t("ui.sensitivityOptions.medium"),
|
||||
},
|
||||
HIGH: {
|
||||
label: __("High"),
|
||||
description: __("Confidential data"),
|
||||
label: t("ui.risk.severity.high"),
|
||||
description: t("ui.sensitivityOptions.high"),
|
||||
},
|
||||
CRITICAL: {
|
||||
label: __("Critical"),
|
||||
description: __("Regulated/PII/financial data"),
|
||||
label: t("ui.risk.severity.critical"),
|
||||
description: t("ui.sensitivityOptions.critical"),
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { useStateWithRef } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Command } from "cmdk";
|
||||
import { Fragment, type ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { tv } from "tailwind-variants";
|
||||
|
||||
import { Badge } from "../../Atoms/Badge/Badge";
|
||||
@@ -54,7 +54,7 @@ export function SelectCell<T extends NonNullable<unknown>>(props: Props<T>) {
|
||||
props.defaultValue,
|
||||
);
|
||||
const cellRef = useEditableCellRef();
|
||||
const { __ } = useTranslate();
|
||||
const { t } = useTranslation();
|
||||
const filteredValue = Array.isArray(value) ? value.filter(v => v !== undefined) : value ? [value] : [];
|
||||
const usedKeys = new Set<string>(filteredValue.map(getKey).filter(Boolean) as string[]);
|
||||
const { onUpdate } = useEditableRowContext();
|
||||
@@ -105,7 +105,7 @@ export function SelectCell<T extends NonNullable<unknown>>(props: Props<T>) {
|
||||
{props.multiple && (
|
||||
<Command.Input
|
||||
className={classNames.input()}
|
||||
placeholder={__("Search")}
|
||||
placeholder={t("ui.actions.search")}
|
||||
/>
|
||||
)}
|
||||
<Command.List>
|
||||
|
||||
Reference in New Issue
Block a user