mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-19 14:48:22 +03:00
Update i18n
This commit is contained in:
parent
8429655761
commit
07a9612e9b
@ -52,6 +52,33 @@
|
|||||||
"categorizeImage": "Classify Image",
|
"categorizeImage": "Classify Image",
|
||||||
"wizard": {
|
"wizard": {
|
||||||
"title": "Create New Classification",
|
"title": "Create New Classification",
|
||||||
"description": "Create a new state or object classification model."
|
"description": "Create a new state or object classification model.",
|
||||||
|
"steps": {
|
||||||
|
"nameAndDefine": "Name & Define",
|
||||||
|
"stateArea": "State Area",
|
||||||
|
"chooseExamples": "Choose Examples",
|
||||||
|
"train": "Train"
|
||||||
|
},
|
||||||
|
"step1": {
|
||||||
|
"name": "Name",
|
||||||
|
"namePlaceholder": "Enter model name...",
|
||||||
|
"type": "Type",
|
||||||
|
"typeState": "State",
|
||||||
|
"typeObject": "Object",
|
||||||
|
"classificationType": "Classification Type",
|
||||||
|
"classificationSubLabel": "Sub Label",
|
||||||
|
"classificationAttribute": "Attribute",
|
||||||
|
"classes": "Classes",
|
||||||
|
"classPlaceholder": "Enter class name...",
|
||||||
|
"errors": {
|
||||||
|
"nameRequired": "Model name is required",
|
||||||
|
"nameLength": "Model name must be 64 characters or less",
|
||||||
|
"nameOnlyNumbers": "Model name cannot contain only numbers",
|
||||||
|
"classRequired": "At least 1 class is required",
|
||||||
|
"classesUnique": "Class names must be unique",
|
||||||
|
"stateRequiresTwoClasses": "State models require at least 2 classes",
|
||||||
|
"objectTypeRequired": "Please select a classification type"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,14 +7,20 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "../ui/dialog";
|
} from "../ui/dialog";
|
||||||
import { useReducer } from "react";
|
import { useReducer, useMemo } from "react";
|
||||||
import Step1NameAndDefine, { Step1FormData } from "./wizard/Step1NameAndDefine";
|
import Step1NameAndDefine, { Step1FormData } from "./wizard/Step1NameAndDefine";
|
||||||
|
|
||||||
const STEPS = [
|
const OBJECT_STEPS = [
|
||||||
"classificationWizard.steps.nameAndDefine",
|
"wizard.steps.nameAndDefine",
|
||||||
"classificationWizard.steps.stateArea",
|
"wizard.steps.chooseExamples",
|
||||||
"classificationWizard.steps.chooseExamples",
|
"wizard.steps.train",
|
||||||
"classificationWizard.steps.train",
|
];
|
||||||
|
|
||||||
|
const STATE_STEPS = [
|
||||||
|
"wizard.steps.nameAndDefine",
|
||||||
|
"wizard.steps.stateArea",
|
||||||
|
"wizard.steps.chooseExamples",
|
||||||
|
"wizard.steps.train",
|
||||||
];
|
];
|
||||||
|
|
||||||
type ClassificationModelWizardDialogProps = {
|
type ClassificationModelWizardDialogProps = {
|
||||||
@ -74,6 +80,15 @@ export default function ClassificationModelWizardDialog({
|
|||||||
|
|
||||||
const [wizardState, dispatch] = useReducer(wizardReducer, initialState);
|
const [wizardState, dispatch] = useReducer(wizardReducer, initialState);
|
||||||
|
|
||||||
|
const steps = useMemo(() => {
|
||||||
|
if (!wizardState.step1Data) {
|
||||||
|
return OBJECT_STEPS;
|
||||||
|
}
|
||||||
|
return wizardState.step1Data.modelType === "state"
|
||||||
|
? STATE_STEPS
|
||||||
|
: OBJECT_STEPS;
|
||||||
|
}, [wizardState.step1Data]);
|
||||||
|
|
||||||
const handleStep1Next = (data: Step1FormData) => {
|
const handleStep1Next = (data: Step1FormData) => {
|
||||||
dispatch({ type: "SET_STEP_1", payload: data });
|
dispatch({ type: "SET_STEP_1", payload: data });
|
||||||
};
|
};
|
||||||
@ -99,7 +114,7 @@ export default function ClassificationModelWizardDialog({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StepIndicator
|
<StepIndicator
|
||||||
steps={STEPS}
|
steps={steps}
|
||||||
currentStep={wizardState.currentStep}
|
currentStep={wizardState.currentStep}
|
||||||
variant="dots"
|
variant="dots"
|
||||||
className="mb-4 justify-start"
|
className="mb-4 justify-start"
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { LuX } from "react-icons/lu";
|
import { LuX } from "react-icons/lu";
|
||||||
import { MdAddBox } from "react-icons/md";
|
import { MdAddBox } from "react-icons/md";
|
||||||
|
|
||||||
@ -37,26 +38,28 @@ export default function Step1NameAndDefine({
|
|||||||
onNext,
|
onNext,
|
||||||
onCancel,
|
onCancel,
|
||||||
}: Step1NameAndDefineProps) {
|
}: Step1NameAndDefineProps) {
|
||||||
|
const { t } = useTranslation(["views/classificationModel"]);
|
||||||
|
|
||||||
const step1FormData = z
|
const step1FormData = z
|
||||||
.object({
|
.object({
|
||||||
modelName: z
|
modelName: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, "Model name is required")
|
.min(1, t("wizard.step1.errors.nameRequired"))
|
||||||
.max(64, "Model name must be 64 characters or less")
|
.max(64, t("wizard.step1.errors.nameLength"))
|
||||||
.refine((value) => !/^\d+$/.test(value), {
|
.refine((value) => !/^\d+$/.test(value), {
|
||||||
message: "Model name cannot contain only numbers",
|
message: t("wizard.step1.errors.nameOnlyNumbers"),
|
||||||
}),
|
}),
|
||||||
modelType: z.enum(["state", "object"]),
|
modelType: z.enum(["state", "object"]),
|
||||||
objectType: z.enum(["sub_label", "attribute"]).optional(),
|
objectType: z.enum(["sub_label", "attribute"]).optional(),
|
||||||
classes: z
|
classes: z
|
||||||
.array(z.string())
|
.array(z.string())
|
||||||
.min(1, "At least one class field is required")
|
.min(1, t("wizard.step1.errors.classRequired"))
|
||||||
.refine(
|
.refine(
|
||||||
(classes) => {
|
(classes) => {
|
||||||
const nonEmpty = classes.filter((c) => c.trim().length > 0);
|
const nonEmpty = classes.filter((c) => c.trim().length > 0);
|
||||||
return nonEmpty.length >= 1;
|
return nonEmpty.length >= 1;
|
||||||
},
|
},
|
||||||
{ message: "At least 1 class is required" },
|
{ message: t("wizard.step1.errors.classRequired") },
|
||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
(classes) => {
|
(classes) => {
|
||||||
@ -64,7 +67,7 @@ export default function Step1NameAndDefine({
|
|||||||
const unique = new Set(nonEmpty.map((c) => c.toLowerCase()));
|
const unique = new Set(nonEmpty.map((c) => c.toLowerCase()));
|
||||||
return unique.size === nonEmpty.length;
|
return unique.size === nonEmpty.length;
|
||||||
},
|
},
|
||||||
{ message: "Class names must be unique" },
|
{ message: t("wizard.step1.errors.classesUnique") },
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
@ -77,7 +80,7 @@ export default function Step1NameAndDefine({
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: "State models require at least 2 classes",
|
message: t("wizard.step1.errors.stateRequiresTwoClasses"),
|
||||||
path: ["classes"],
|
path: ["classes"],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -90,7 +93,7 @@ export default function Step1NameAndDefine({
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: "Please select a classification type",
|
message: t("wizard.step1.errors.objectTypeRequired"),
|
||||||
path: ["objectType"],
|
path: ["objectType"],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -145,11 +148,11 @@ export default function Step1NameAndDefine({
|
|||||||
name="modelName"
|
name="modelName"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>{t("wizard.step1.name")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
className="h-8"
|
className="h-8"
|
||||||
placeholder="Enter model name..."
|
placeholder={t("wizard.step1.namePlaceholder")}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -163,7 +166,7 @@ export default function Step1NameAndDefine({
|
|||||||
name="modelType"
|
name="modelType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Type</FormLabel>
|
<FormLabel>{t("wizard.step1.type")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
@ -181,7 +184,7 @@ export default function Step1NameAndDefine({
|
|||||||
value="state"
|
value="state"
|
||||||
/>
|
/>
|
||||||
<Label className="cursor-pointer" htmlFor="state">
|
<Label className="cursor-pointer" htmlFor="state">
|
||||||
State
|
{t("wizard.step1.typeState")}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -195,7 +198,7 @@ export default function Step1NameAndDefine({
|
|||||||
value="object"
|
value="object"
|
||||||
/>
|
/>
|
||||||
<Label className="cursor-pointer" htmlFor="object">
|
<Label className="cursor-pointer" htmlFor="object">
|
||||||
Object
|
{t("wizard.step1.typeObject")}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
@ -211,7 +214,7 @@ export default function Step1NameAndDefine({
|
|||||||
name="objectType"
|
name="objectType"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Classification Type</FormLabel>
|
<FormLabel>{t("wizard.step1.classificationType")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
@ -229,7 +232,7 @@ export default function Step1NameAndDefine({
|
|||||||
value="sub_label"
|
value="sub_label"
|
||||||
/>
|
/>
|
||||||
<Label className="cursor-pointer" htmlFor="sub_label">
|
<Label className="cursor-pointer" htmlFor="sub_label">
|
||||||
Sub Label
|
{t("wizard.step1.classificationSubLabel")}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -243,7 +246,7 @@ export default function Step1NameAndDefine({
|
|||||||
value="attribute"
|
value="attribute"
|
||||||
/>
|
/>
|
||||||
<Label className="cursor-pointer" htmlFor="attribute">
|
<Label className="cursor-pointer" htmlFor="attribute">
|
||||||
Attribute
|
{t("wizard.step1.classificationAttribute")}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
@ -256,7 +259,7 @@ export default function Step1NameAndDefine({
|
|||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<FormLabel>Classes</FormLabel>
|
<FormLabel>{t("wizard.step1.classes")}</FormLabel>
|
||||||
<MdAddBox
|
<MdAddBox
|
||||||
className="size-7 cursor-pointer text-primary hover:text-primary/80"
|
className="size-7 cursor-pointer text-primary hover:text-primary/80"
|
||||||
onClick={handleAddClass}
|
onClick={handleAddClass}
|
||||||
@ -274,7 +277,7 @@ export default function Step1NameAndDefine({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Input
|
<Input
|
||||||
className="h-8"
|
className="h-8"
|
||||||
placeholder="Enter class name..."
|
placeholder={t("wizard.step1.classPlaceholder")}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
{watchedClasses.length > 1 && (
|
{watchedClasses.length > 1 && (
|
||||||
@ -306,7 +309,7 @@ export default function Step1NameAndDefine({
|
|||||||
|
|
||||||
<div className="flex flex-col gap-3 pt-3 sm:flex-row sm:justify-end sm:gap-4">
|
<div className="flex flex-col gap-3 pt-3 sm:flex-row sm:justify-end sm:gap-4">
|
||||||
<Button type="button" onClick={onCancel} className="sm:flex-1">
|
<Button type="button" onClick={onCancel} className="sm:flex-1">
|
||||||
Cancel
|
{t("button.cancel", { ns: "common" })}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@ -315,7 +318,7 @@ export default function Step1NameAndDefine({
|
|||||||
className="flex items-center justify-center gap-2 sm:flex-1"
|
className="flex items-center justify-center gap-2 sm:flex-1"
|
||||||
disabled={!form.formState.isValid}
|
disabled={!form.formState.isValid}
|
||||||
>
|
>
|
||||||
Continue
|
{t("button.continue", { ns: "common" })}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user