mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-02 17:12:16 +03:00
Refactor and clean up i18n (#17198)
* clean up i18n * fix key * fix key
This commit is contained in:
@@ -293,7 +293,9 @@ export default function ReviewCard({
|
||||
</AlertDialogTitle>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogDescription>
|
||||
<Trans ns="components/dialog">recording.confirmDelete.desc</Trans>
|
||||
<Trans ns="components/dialog">
|
||||
recording.confirmDelete.desc.selected
|
||||
</Trans>
|
||||
</AlertDialogDescription>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={() => setOptionsOpen(false)}>
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function SearchThumbnailFooter({
|
||||
searchResult.start_time,
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("time.formattedTimestampExcludeSeconds.24hour", { ns: "common" })
|
||||
: t("time.formattedTimestampExcludeSeconds", { ns: "common" }),
|
||||
: t("time.formattedTimestampExcludeSeconds.12hour", { ns: "common" }),
|
||||
config?.ui.timezone,
|
||||
);
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ const timeAgo = ({
|
||||
|
||||
const elapsed: number = elapsedTime / 1000;
|
||||
if (elapsed < 10) {
|
||||
return t("time.justNow");
|
||||
return t("time.justNow", { ns: "common" });
|
||||
}
|
||||
|
||||
for (let i = 0; i < timeUnits.length; i++) {
|
||||
@@ -66,6 +66,7 @@ const timeAgo = ({
|
||||
if (monthDiff > 0) {
|
||||
const unitAmount = monthDiff;
|
||||
return t("time.ago", {
|
||||
ns: "common",
|
||||
timeAgo: t(`time.${dense ? timeUnits[i].unit : timeUnits[i].full}`, {
|
||||
time: unitAmount,
|
||||
}),
|
||||
@@ -74,6 +75,7 @@ const timeAgo = ({
|
||||
} else if (elapsed >= timeUnits[i].value) {
|
||||
const unitAmount: number = Math.floor(elapsed / timeUnits[i].value);
|
||||
return t("time.ago", {
|
||||
ns: "common",
|
||||
timeAgo: t(`time.${dense ? timeUnits[i].unit : timeUnits[i].full}`, {
|
||||
time: unitAmount,
|
||||
}),
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function CalendarFilterButton({
|
||||
day,
|
||||
updateSelectedDay,
|
||||
}: CalendarFilterButtonProps) {
|
||||
const { t } = useTranslation(["components/filter"]);
|
||||
const { t } = useTranslation(["components/filter", "views/events"]);
|
||||
const [open, setOpen] = useState(false);
|
||||
const selectedDate = useFormattedTimestamp(
|
||||
day == undefined ? 0 : day?.getTime() / 1000 + 1,
|
||||
@@ -38,7 +38,7 @@ export default function CalendarFilterButton({
|
||||
const trigger = (
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
aria-label={t("date.selectDateBy.label")}
|
||||
aria-label={t("explore.date.selectDateBy.label")}
|
||||
variant={day == undefined ? "default" : "select"}
|
||||
size="sm"
|
||||
>
|
||||
@@ -109,7 +109,7 @@ export function CalendarRangeFilterButton({
|
||||
const trigger = (
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
aria-label={t("date.selectDateBy.label")}
|
||||
aria-label={t("explore.date.selectDateBy.label")}
|
||||
variant={range == undefined ? "default" : "select"}
|
||||
size="sm"
|
||||
>
|
||||
|
||||
@@ -279,7 +279,7 @@ function NewGroupDialog({
|
||||
setOpen(false);
|
||||
setEditState("none");
|
||||
toast.error(
|
||||
t("toast.save.error", {
|
||||
t("toast.save.error.title", {
|
||||
errorMessage: res.statusText,
|
||||
ns: "common",
|
||||
}),
|
||||
@@ -296,9 +296,12 @@ function NewGroupDialog({
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(t("toast.save.error", { errorMessage, ns: "common" }), {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("toast.save.error.title", { errorMessage, ns: "common" }),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
@@ -485,9 +488,7 @@ export function EditGroupDialog({
|
||||
<div className="scrollbar-container flex flex-col overflow-y-auto md:my-4">
|
||||
<Header className="mt-2" onClose={() => setOpen(false)}>
|
||||
<Title>{t("group.edit")}</Title>
|
||||
<Description className="sr-only">
|
||||
{t("group.edit.desc")}
|
||||
</Description>
|
||||
<Description className="sr-only">{t("group.edit")}</Description>
|
||||
</Header>
|
||||
|
||||
<CameraGroupEdit
|
||||
@@ -538,7 +539,9 @@ export function CameraGroupRow({
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("group.delete.confirm")}</AlertDialogTitle>
|
||||
<AlertDialogTitle>
|
||||
{t("group.delete.confirm.title")}
|
||||
</AlertDialogTitle>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogDescription>
|
||||
<Trans ns="components/camera" values={{ name: group[0] }}>
|
||||
@@ -734,7 +737,7 @@ export function CameraGroupEdit({
|
||||
.then(async (res) => {
|
||||
if (res.status === 200) {
|
||||
toast.success(
|
||||
t("group.toast.success", {
|
||||
t("group.success", {
|
||||
name: values.name,
|
||||
}),
|
||||
{
|
||||
@@ -748,7 +751,7 @@ export function CameraGroupEdit({
|
||||
setAllGroupsStreamingSettings(updatedSettings);
|
||||
} else {
|
||||
toast.error(
|
||||
t("toast.save.error", {
|
||||
t("toast.save.error.title", {
|
||||
errorMessage: res.statusText,
|
||||
ns: "common",
|
||||
}),
|
||||
@@ -764,7 +767,7 @@ export function CameraGroupEdit({
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(
|
||||
t("toast.save.error", {
|
||||
t("toast.save.error.title", {
|
||||
errorMessage,
|
||||
ns: "common",
|
||||
}),
|
||||
|
||||
@@ -38,17 +38,17 @@ export function CamerasFilterButton({
|
||||
|
||||
const buttonText = useMemo(() => {
|
||||
if (isMobile) {
|
||||
return t("menu.live.cameras", { ns: "common" });
|
||||
return t("menu.live.cameras.title", { ns: "common" });
|
||||
}
|
||||
|
||||
if (!selectedCameras || selectedCameras.length == 0) {
|
||||
return t("menu.live.allCameras", { ns: "common" });
|
||||
}
|
||||
return t("menu.live.cameras.count", {
|
||||
ns: "common",
|
||||
count: selectedCameras.includes("birdseye")
|
||||
? selectedCameras.length - 1
|
||||
: selectedCameras.length,
|
||||
ns: "common",
|
||||
});
|
||||
}, [selectedCameras, t]);
|
||||
|
||||
@@ -158,7 +158,7 @@ export function CamerasFilterContent({
|
||||
<div className="scrollbar-container flex h-auto max-h-[80dvh] flex-col gap-2 overflow-y-auto overflow-x-hidden p-4">
|
||||
<FilterSwitch
|
||||
isChecked={currentCameras == undefined}
|
||||
label={t("cameras.all")}
|
||||
label={t("cameras.all.title")}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
setCurrentCameras(undefined);
|
||||
|
||||
@@ -53,7 +53,7 @@ export function LogSettingsButton({
|
||||
<DropdownMenuSeparator />
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-md">{t("logSettings.loading")}</div>
|
||||
<div className="text-md">{t("logSettings.loading.title")}</div>
|
||||
<div className="mt-2.5 flex flex-col gap-2.5">
|
||||
<div className="space-y-1 text-xs text-muted-foreground">
|
||||
{t("logSettings.loading.desc")}
|
||||
|
||||
@@ -442,7 +442,7 @@ export function GeneralFilterContent({
|
||||
onReset,
|
||||
onClose,
|
||||
}: GeneralFilterContentProps) {
|
||||
const { t } = useTranslation(["components/filter"]);
|
||||
const { t } = useTranslation(["components/filter", "views/events"]);
|
||||
return (
|
||||
<>
|
||||
<div className="scrollbar-container h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden">
|
||||
@@ -476,7 +476,7 @@ export function GeneralFilterContent({
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allLabels"
|
||||
>
|
||||
{t("labels.all")}
|
||||
{t("labels.all.title")}
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
@@ -523,7 +523,7 @@ export function GeneralFilterContent({
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allZones"
|
||||
>
|
||||
{t("zones.all")}
|
||||
{t("zones.all.title")}
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
@@ -600,7 +600,7 @@ function ShowMotionOnlyButton({
|
||||
motionOnly,
|
||||
setMotionOnly,
|
||||
}: ShowMotionOnlyButtonProps) {
|
||||
const { t } = useTranslation(["views/events"]);
|
||||
const { t } = useTranslation(["views/events", "components/filter"]);
|
||||
const [motionOnlyButton, setMotionOnlyButton] = useOptimisticState(
|
||||
motionOnly,
|
||||
setMotionOnly,
|
||||
@@ -627,7 +627,7 @@ function ShowMotionOnlyButton({
|
||||
<Button
|
||||
size="sm"
|
||||
className="duration-0"
|
||||
aria-label={t("motion.showMotionOnly", { ns: "components/filter" })}
|
||||
aria-label={t("motion.only")}
|
||||
variant={motionOnlyButton ? "select" : "default"}
|
||||
onClick={() => setMotionOnlyButton(!motionOnlyButton)}
|
||||
>
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function SearchActionGroup({
|
||||
setSelectedObjects,
|
||||
pullLatestData,
|
||||
}: SearchActionGroupProps) {
|
||||
const { t } = useTranslation(["views/filter"]);
|
||||
const { t } = useTranslation(["components/filter"]);
|
||||
const onClearSelected = useCallback(() => {
|
||||
setSelectedObjects([]);
|
||||
}, [setSelectedObjects]);
|
||||
|
||||
@@ -198,7 +198,7 @@ export default function SearchFilterGroup({
|
||||
to: new Date(filter.before * 1000),
|
||||
}
|
||||
}
|
||||
defaultText={isMobile ? t("dates.all.short") : t("dates.all")}
|
||||
defaultText={isMobile ? t("dates.all.short") : t("dates.all.title")}
|
||||
updateSelectedRange={onUpdateSelectedRange}
|
||||
/>
|
||||
)}
|
||||
@@ -244,7 +244,7 @@ function GeneralFilterButton({
|
||||
}
|
||||
|
||||
if (!selectedLabels || selectedLabels.length == 0) {
|
||||
return t("labels.all");
|
||||
return t("labels.all.title");
|
||||
}
|
||||
|
||||
if (selectedLabels.length == 1) {
|
||||
@@ -338,7 +338,7 @@ export function GeneralFilterContent({
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allLabels"
|
||||
>
|
||||
{t("labels.all")}
|
||||
{t("labels.all.title")}
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
|
||||
@@ -78,7 +78,7 @@ export function GeneralFilterContent({
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allLabels"
|
||||
>
|
||||
{t("labels.all")}
|
||||
{t("labels.all.title")}
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
|
||||
@@ -198,7 +198,7 @@ export function CombinedStorageGraph({
|
||||
style={{ backgroundColor: item.color }}
|
||||
></div>
|
||||
{item.name === "Unused"
|
||||
? t("storage.cameraStorage.unused")
|
||||
? t("storage.cameraStorage.unused.title")
|
||||
: item.name.replaceAll("_", " ")}
|
||||
{item.name === "Unused" && (
|
||||
<Popover>
|
||||
|
||||
@@ -248,7 +248,7 @@ export default function InputWithTags({
|
||||
filters.before &&
|
||||
timestamp >= filters.before * 1000
|
||||
) {
|
||||
toast.error(t("afterDatebeEarlierBefore"), {
|
||||
toast.error(t("filter.toast.error.afterDatebeEarlierBefore"), {
|
||||
position: "top-center",
|
||||
});
|
||||
return;
|
||||
@@ -727,7 +727,7 @@ export default function InputWithTags({
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-medium">{t("filter.tips.title")}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("filter.tips.desc")}
|
||||
{t("filter.tips.desc.text")}
|
||||
</p>
|
||||
<Trans
|
||||
ns="views/search"
|
||||
|
||||
@@ -15,9 +15,13 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "../ui/dropdown-menu";
|
||||
import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
|
||||
import { DialogClose } from "../ui/dialog";
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerTrigger,
|
||||
DrawerClose,
|
||||
} from "@/components/ui/drawer";
|
||||
import { LuLogOut, LuSquarePen } from "react-icons/lu";
|
||||
import useSWR from "swr";
|
||||
|
||||
@@ -32,7 +36,7 @@ type AccountSettingsProps = {
|
||||
};
|
||||
|
||||
export default function AccountSettings({ className }: AccountSettingsProps) {
|
||||
const { t } = useTranslation(["views/settings"]);
|
||||
const { t } = useTranslation(["views/settings", "common"]);
|
||||
const { data: profile } = useSWR("profile");
|
||||
const { data: config } = useSWR("config");
|
||||
const logoutUrl = config?.proxy?.logout_url || `${baseUrl}api/logout`;
|
||||
@@ -42,7 +46,7 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
|
||||
const Container = isDesktop ? DropdownMenu : Drawer;
|
||||
const Trigger = isDesktop ? DropdownMenuTrigger : DrawerTrigger;
|
||||
const Content = isDesktop ? DropdownMenuContent : DrawerContent;
|
||||
const MenuItem = isDesktop ? DropdownMenuItem : DialogClose;
|
||||
const MenuItem = isDesktop ? DropdownMenuItem : DrawerClose;
|
||||
|
||||
const handlePasswordSave = async (password: string) => {
|
||||
if (!profile?.username || profile.username === "anonymous") return;
|
||||
@@ -74,8 +78,8 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
|
||||
|
||||
return (
|
||||
<Container modal={!isDesktop}>
|
||||
<Trigger>
|
||||
<Tooltip>
|
||||
<Tooltip>
|
||||
<Trigger asChild>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={cn(
|
||||
@@ -89,34 +93,41 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
|
||||
<VscAccount className="size-5 md:m-[6px]" />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent side="right">
|
||||
<p>{t("menu.user.account", { ns: "common" })}</p>
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</Tooltip>
|
||||
</Trigger>
|
||||
</Trigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent side="right" sideOffset={5}>
|
||||
<p>{t("menu.user.account", { ns: "common" })}</p>
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</Tooltip>
|
||||
|
||||
<Content
|
||||
className={
|
||||
isDesktop ? "mr-5 w-72" : "max-h-[75dvh] overflow-hidden p-2"
|
||||
}
|
||||
className={cn(
|
||||
isDesktop ? "mr-5 w-72" : "max-h-[75dvh] overflow-hidden p-4",
|
||||
)}
|
||||
>
|
||||
<div className="scrollbar-container w-full flex-col overflow-y-auto overflow-x-hidden">
|
||||
<DropdownMenuLabel>
|
||||
{t("menu.user.current", {
|
||||
ns: "common",
|
||||
user:
|
||||
profile?.username || t("menu.user.anonymous", { ns: "common" }),
|
||||
})}{" "}
|
||||
{t("role." + profile?.role) &&
|
||||
`(${t("role." + profile?.role, { ns: "common" })})`}
|
||||
<DropdownMenuLabel className="flex flex-col gap-1.5">
|
||||
<div>
|
||||
{t("menu.user.current", {
|
||||
ns: "common",
|
||||
user:
|
||||
profile?.username ||
|
||||
t("menu.user.anonymous", { ns: "common" }),
|
||||
})}{" "}
|
||||
{t("role." + profile?.role) &&
|
||||
`(${t("role." + profile?.role, { ns: "common" })})`}
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator className={isDesktop ? "mt-3" : "mt-1"} />
|
||||
|
||||
<DropdownMenuSeparator className={isDesktop ? "my-2" : "my-2"} />
|
||||
|
||||
{profile?.username && profile.username !== "anonymous" && (
|
||||
<MenuItem
|
||||
className={
|
||||
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
||||
}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2",
|
||||
isDesktop ? "cursor-pointer" : "p-2 text-sm",
|
||||
)}
|
||||
aria-label={t("menu.user.setPassword", { ns: "common" })}
|
||||
onClick={() => setPasswordDialogOpen(true)}
|
||||
>
|
||||
@@ -124,13 +135,16 @@ export default function AccountSettings({ className }: AccountSettingsProps) {
|
||||
<span>{t("menu.user.setPassword", { ns: "common" })}</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
<MenuItem
|
||||
className={
|
||||
isDesktop ? "cursor-pointer" : "flex items-center p-2 text-sm"
|
||||
}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2",
|
||||
isDesktop ? "cursor-pointer" : "p-2 text-sm",
|
||||
)}
|
||||
asChild
|
||||
aria-label={t("menu.user.logout", { ns: "common" })}
|
||||
>
|
||||
<a className="flex" href={logoutUrl}>
|
||||
<a href={logoutUrl} className="flex items-center gap-2">
|
||||
<LuLogOut className="mr-2 size-4" />
|
||||
<span>{t("menu.user.logout", { ns: "common" })}</span>
|
||||
</a>
|
||||
|
||||
@@ -68,7 +68,7 @@ type GeneralSettingsProps = {
|
||||
};
|
||||
|
||||
export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
const { t } = useTranslation(["common"]);
|
||||
const { t } = useTranslation(["common", "views/settings"]);
|
||||
const { data: profile } = useSWR("profile");
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
const logoutUrl = config?.proxy?.logout_url || "/api/logout";
|
||||
@@ -100,7 +100,9 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
if (response.status === 200) {
|
||||
setPasswordDialogOpen(false);
|
||||
toast.success(
|
||||
t("users.toast.success.updatePassword", { ns: "views/settings" }),
|
||||
t("users.toast.success.updatePassword", {
|
||||
ns: "views/settings",
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
@@ -184,11 +186,11 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
? "cursor-pointer"
|
||||
: "flex items-center p-2 text-sm"
|
||||
}
|
||||
aria-label={t("menu.user.setPassword")}
|
||||
aria-label={t("menu.user.setPassword", { ns: "common" })}
|
||||
onClick={() => setPasswordDialogOpen(true)}
|
||||
>
|
||||
<LuSquarePen className="mr-2 size-4" />
|
||||
<span>{t("menu.user.setPassword")}</span>
|
||||
<span>{t("menu.user.setPassword", { ns: "common" })}</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
@@ -501,7 +503,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
|
||||
aria-label={t("menu.documentation.label")}
|
||||
>
|
||||
<LuLifeBuoy className="mr-2 size-4" />
|
||||
<span>{t("menu.documentation")}</span>
|
||||
<span>{t("menu.documentation.title")}</span>
|
||||
</MenuItem>
|
||||
</a>
|
||||
<a
|
||||
|
||||
@@ -246,7 +246,7 @@ export default function LiveContextMenu({
|
||||
strftime_fmt:
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("time.formattedTimestampExcludeSeconds.24hour", { ns: "common" })
|
||||
: t("time.formattedTimestampExcludeSeconds", { ns: "common" }),
|
||||
: t("time.formattedTimestampExcludeSeconds.12hour", { ns: "common" }),
|
||||
});
|
||||
return t("time.untilForTime", { ns: "common", time });
|
||||
};
|
||||
@@ -343,7 +343,9 @@ export default function LiveContextMenu({
|
||||
}
|
||||
>
|
||||
<div className="text-primary">
|
||||
{t("streaming.debugView", { ns: "components/dialog" })}
|
||||
{t("streaming.debugView", {
|
||||
ns: "components/dialog",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuItem>
|
||||
|
||||
@@ -149,7 +149,7 @@ export default function SearchResultActions({
|
||||
onClick={showSnapshot}
|
||||
>
|
||||
<FrigatePlusIcon className="mr-2 size-4 cursor-pointer text-primary" />
|
||||
<span>{t("itemMenu.submitToPlus")}</span>
|
||||
<span>{t("itemMenu.submitToPlus.label")}</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
@@ -170,7 +170,9 @@ export default function SearchResultActions({
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("dialog.confirmDelete")}</AlertDialogTitle>
|
||||
<AlertDialogTitle>
|
||||
{t("dialog.confirmDelete.title")}
|
||||
</AlertDialogTitle>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogDescription>
|
||||
<Trans ns="views/explore">dialog.confirmDelete.desc</Trans>
|
||||
|
||||
@@ -128,7 +128,7 @@ export default function CreateUserDialog({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-sm font-medium">
|
||||
{t("users.dialog.form.user")}
|
||||
{t("users.dialog.form.user.title")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -150,7 +150,7 @@ export default function CreateUserDialog({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-sm font-medium">
|
||||
{t("users.dialog.form.password")}
|
||||
{t("users.dialog.form.password.title")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -170,7 +170,7 @@ export default function CreateUserDialog({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-sm font-medium">
|
||||
{t("users.dialog.form.password.confirm")}
|
||||
{t("users.dialog.form.password.confirm.title")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
|
||||
@@ -267,7 +267,7 @@ export function ExportContent({
|
||||
{isDesktop && (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("menu.export")}</DialogTitle>
|
||||
<DialogTitle>{t("menu.export", { ns: "common" })}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<SelectSeparator className="my-4 bg-secondary" />
|
||||
</>
|
||||
@@ -404,14 +404,14 @@ function CustomTimeSelector({
|
||||
const formattedStart = useFormattedTimestamp(
|
||||
startTime,
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("time.formattedTimestamp.24hour")
|
||||
: t("time.formattedTimestamp"),
|
||||
? t("time.formattedTimestamp.24hour", { ns: "common" })
|
||||
: t("time.formattedTimestamp.12hour", { ns: "common" }),
|
||||
);
|
||||
const formattedEnd = useFormattedTimestamp(
|
||||
endTime,
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("time.formattedTimestamp.24hour")
|
||||
: t("time.formattedTimestamp"),
|
||||
? t("time.formattedTimestamp.24hour", { ns: "common" })
|
||||
: t("time.formattedTimestamp.12hour", { ns: "common" }),
|
||||
);
|
||||
|
||||
const startClock = useMemo(() => {
|
||||
@@ -444,7 +444,7 @@ function CustomTimeSelector({
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
className={`text-primary ${isDesktop ? "" : "text-xs"}`}
|
||||
aria-label={t("export.time.start")}
|
||||
aria-label={t("export.time.start.title")}
|
||||
variant={startOpen ? "select" : "default"}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
@@ -510,7 +510,7 @@ function CustomTimeSelector({
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
className={`text-primary ${isDesktop ? "" : "text-xs"}`}
|
||||
aria-label={t("export.time.end")}
|
||||
aria-label={t("export.time.end.title")}
|
||||
variant={endOpen ? "select" : "default"}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function MobileCameraDrawer({
|
||||
<DrawerTrigger asChild>
|
||||
<Button
|
||||
className="rounded-lg capitalize"
|
||||
aria-label={t("menu.live.cameras")}
|
||||
aria-label={t("menu.live.cameras.title")}
|
||||
size="sm"
|
||||
>
|
||||
<FaVideo className="text-secondary-foreground" />
|
||||
|
||||
@@ -19,7 +19,6 @@ import { toast } from "sonner";
|
||||
import axios from "axios";
|
||||
import SaveExportOverlay from "./SaveExportOverlay";
|
||||
import { isIOS, isMobile } from "react-device-detect";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type DrawerMode = "none" | "select" | "export" | "calendar" | "filter";
|
||||
@@ -70,7 +69,7 @@ export default function MobileReviewSettingsDrawer({
|
||||
setMode,
|
||||
setShowExportPreview,
|
||||
}: MobileReviewSettingsDrawerProps) {
|
||||
const { t } = useTranslation(["views/recording"]);
|
||||
const { t } = useTranslation(["views/recording", "components/dialog"]);
|
||||
const [drawerMode, setDrawerMode] = useState<DrawerMode>("none");
|
||||
|
||||
// exports
|
||||
|
||||
@@ -68,13 +68,13 @@ export default function RoleChangeDialog({
|
||||
<SelectItem value="admin" className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<LuShield className="size-4 text-primary" />
|
||||
<span>{t("role.admin")}</span>
|
||||
<span>{t("role.admin", { ns: "common" })}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="viewer" className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<LuUser className="size-4 text-primary" />
|
||||
<span>{t("role.viewer")}</span>
|
||||
<span>{t("role.viewer", { ns: "common" })}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
|
||||
@@ -117,7 +117,7 @@ export default function SetPasswordDialog({
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">
|
||||
{t("users.dialog.form.newPassword")}
|
||||
{t("users.dialog.form.newPassword.title")}
|
||||
</Label>
|
||||
<Input
|
||||
id="password"
|
||||
@@ -142,7 +142,7 @@ export default function SetPasswordDialog({
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("users.dialog.form.password.strength")}
|
||||
{t("users.dialog.form.password.strength.title")}
|
||||
<span className="font-medium">{getStrengthLabel()}</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -151,7 +151,7 @@ export default function SetPasswordDialog({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="confirm-password">
|
||||
{t("users.dialog.form.password.confirm")}
|
||||
{t("users.dialog.form.password.confirm.title")}
|
||||
</Label>
|
||||
<Input
|
||||
id="confirm-password"
|
||||
|
||||
@@ -85,7 +85,7 @@ export function AnnotationSettingsPane({
|
||||
updateConfig();
|
||||
} else {
|
||||
toast.error(
|
||||
t("toast.save.error", {
|
||||
t("toast.save.error.title", {
|
||||
errorMessage: res.statusText,
|
||||
ns: "common",
|
||||
}),
|
||||
@@ -100,9 +100,12 @@ export function AnnotationSettingsPane({
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(t("toast.save.error", { errorMessage, ns: "common" }), {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("toast.save.error.title", { errorMessage, ns: "common" }),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
@@ -145,7 +148,7 @@ export function AnnotationSettingsPane({
|
||||
onCheckedChange={setShowZones}
|
||||
/>
|
||||
<Label className="cursor-pointer" htmlFor="show-zones">
|
||||
{t("objectLifecycle.annotationSettings.showAllZones")}
|
||||
{t("objectLifecycle.annotationSettings.showAllZones.title")}
|
||||
</Label>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
|
||||
@@ -573,7 +573,7 @@ export default function ObjectLifecycle({
|
||||
? t("time.formattedTimestamp2.24hour", {
|
||||
ns: "common",
|
||||
})
|
||||
: t("time.formattedTimestamp2", {
|
||||
: t("time.formattedTimestamp2.12hour", {
|
||||
ns: "common",
|
||||
}),
|
||||
time_style: "medium",
|
||||
|
||||
@@ -98,7 +98,7 @@ export default function ReviewDetailDialog({
|
||||
review?.start_time ?? 0,
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("time.formattedTimestampWithYear.24hour", { ns: "common" })
|
||||
: t("time.formattedTimestampWithYear", { ns: "common" }),
|
||||
: t("time.formattedTimestampWithYear.12hour", { ns: "common" }),
|
||||
config?.ui.timezone,
|
||||
);
|
||||
|
||||
|
||||
@@ -195,7 +195,9 @@ export default function SearchDetailDialog({
|
||||
>
|
||||
<Header>
|
||||
<Title>{t("trackedObjectDetails")}</Title>
|
||||
<Description className="sr-only">{t("details")}</Description>
|
||||
<Description className="sr-only">
|
||||
{t("trackedObjectDetails")}
|
||||
</Description>
|
||||
</Header>
|
||||
<ScrollArea
|
||||
className={cn("w-full whitespace-nowrap", isMobile && "my-2")}
|
||||
@@ -311,7 +313,7 @@ function ObjectDetailsTab({
|
||||
search?.start_time ?? 0,
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("time.formattedTimestampWithYear.24hour", { ns: "common" })
|
||||
: t("time.formattedTimestampWithYear", { ns: "common" }),
|
||||
: t("time.formattedTimestampWithYear.12hour", { ns: "common" }),
|
||||
config?.ui.timezone,
|
||||
);
|
||||
|
||||
@@ -575,7 +577,9 @@ function ObjectDetailsTab({
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent>{t("details.editSubLable")}</TooltipContent>
|
||||
<TooltipContent>
|
||||
{t("details.editSubLabel.title")}
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -597,7 +601,7 @@ function ObjectDetailsTab({
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-sm text-primary/40">
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
{t("details.topScore")}
|
||||
{t("details.topScore.label")}
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="cursor-pointer p-0">
|
||||
@@ -727,7 +731,7 @@ function ObjectDetailsTab({
|
||||
aria-label={t("details.button.regenerate.label")}
|
||||
onClick={() => regenerateDescription("thumbnails")}
|
||||
>
|
||||
{t("details.button.regenerate")}
|
||||
{t("details.button.regenerate.title")}
|
||||
</Button>
|
||||
{search.has_snapshot && (
|
||||
<DropdownMenu>
|
||||
@@ -772,13 +776,13 @@ function ObjectDetailsTab({
|
||||
<TextEntryDialog
|
||||
open={isSubLabelDialogOpen}
|
||||
setOpen={setIsSubLabelDialogOpen}
|
||||
title={t("details.editSubLable")}
|
||||
title={t("details.editSubLabel.title")}
|
||||
description={
|
||||
search.label
|
||||
? t("details.editSubLable.desc", {
|
||||
label: t(search.label, { ns: "objects" }),
|
||||
? t("details.editSubLabel.desc", {
|
||||
label: t(search.label, { an: "objects" }),
|
||||
})
|
||||
: t("details.editSubLable.desc.noLabel")
|
||||
: t("details.editSubLabel.descNoLabel")
|
||||
}
|
||||
onSave={handleSubLabelSave}
|
||||
defaultValue={search?.sub_label || ""}
|
||||
@@ -921,10 +925,10 @@ export function ObjectSnapshotTab({
|
||||
}}
|
||||
>
|
||||
{/^[aeiou]/i.test(search?.label || "")
|
||||
? t("explore.plus.review.true_other", {
|
||||
? t("explore.plus.review.true.true_other", {
|
||||
label: search?.label,
|
||||
})
|
||||
: t("explore.plus.review.true_one", {
|
||||
: t("explore.plus.review.true.true_one", {
|
||||
label: search?.label,
|
||||
})}
|
||||
</Button>
|
||||
@@ -938,10 +942,10 @@ export function ObjectSnapshotTab({
|
||||
}}
|
||||
>
|
||||
{/^[aeiou]/i.test(search?.label || "")
|
||||
? t("explore.plus.review.false_other", {
|
||||
? t("explore.plus.review.false.false_other", {
|
||||
label: search?.label,
|
||||
})
|
||||
: t("explore.plus.review.false_one", {
|
||||
: t("explore.plus.review.false.false_one", {
|
||||
label: search?.label,
|
||||
})}
|
||||
</Button>
|
||||
|
||||
@@ -251,7 +251,7 @@ function TimeRangeFilterContent({
|
||||
timeRange,
|
||||
updateTimeRange,
|
||||
}: TimeRangeFilterContentProps) {
|
||||
const { t } = useTranslation(["components/filter"]);
|
||||
const { t } = useTranslation(["components/filter", "components/dialog"]);
|
||||
const [startOpen, setStartOpen] = useState(false);
|
||||
const [endOpen, setEndOpen] = useState(false);
|
||||
|
||||
@@ -406,7 +406,7 @@ export function ZoneFilterContent({
|
||||
className="mx-2 cursor-pointer text-primary"
|
||||
htmlFor="allZones"
|
||||
>
|
||||
{t("zones.all")}
|
||||
{t("zones.all.title")}
|
||||
</Label>
|
||||
<Switch
|
||||
className="ml-1"
|
||||
@@ -581,6 +581,7 @@ export function SpeedFilterContent({
|
||||
<DropdownMenuSeparator className="mb-3" />
|
||||
<div className="mb-3 text-lg">
|
||||
{t("estimatedSpeed", {
|
||||
ns: "components/filter",
|
||||
unit:
|
||||
config?.ui.unit_system == "metric"
|
||||
? t("unit.speed.kph", { ns: "common" })
|
||||
|
||||
@@ -12,16 +12,16 @@ export function PlayerStats({ stats, minimal }: PlayerStatsProps) {
|
||||
const fullStatsContent = (
|
||||
<>
|
||||
<p>
|
||||
<span className="text-white/70">{t("stats.streamType")}</span>{" "}
|
||||
<span className="text-white/70">{t("stats.streamType.title")}</span>{" "}
|
||||
<span className="text-white">{stats.streamType}</span>
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-white/70">{t("stats.bandwidth")}</span>{" "}
|
||||
<span className="text-white/70">{t("stats.bandwidth.title")}</span>{" "}
|
||||
<span className="text-white">{stats.bandwidth.toFixed(2)} kbps</span>
|
||||
</p>
|
||||
{stats.latency != undefined && (
|
||||
<p>
|
||||
<span className="text-white/70">{t("stats.latency")}</span>{" "}
|
||||
<span className="text-white/70">{t("stats.latency.title")}</span>{" "}
|
||||
<span
|
||||
className={`text-white ${stats.latency > 2 ? "text-danger" : ""}`}
|
||||
>
|
||||
@@ -35,7 +35,9 @@ export function PlayerStats({ stats, minimal }: PlayerStatsProps) {
|
||||
</p>
|
||||
{stats.droppedFrames != undefined && (
|
||||
<p>
|
||||
<span className="text-white/70">{t("stats.droppedFrames")}</span>{" "}
|
||||
<span className="text-white/70">
|
||||
{t("stats.droppedFrames.title")}
|
||||
</span>{" "}
|
||||
<span className="text-white">{stats.droppedFrames}</span>
|
||||
</p>
|
||||
)}
|
||||
@@ -68,7 +70,9 @@ export function PlayerStats({ stats, minimal }: PlayerStatsProps) {
|
||||
</div>
|
||||
{stats.latency != undefined && (
|
||||
<div className="hidden flex-col items-center gap-1 md:flex">
|
||||
<span className="text-white/70">{t("stats.latency.short")}</span>
|
||||
<span className="text-white/70">
|
||||
{t("stats.latency.short.title")}
|
||||
</span>
|
||||
<span
|
||||
className={`text-white ${stats.latency >= 2 ? "text-danger" : ""}`}
|
||||
>
|
||||
@@ -81,7 +85,7 @@ export function PlayerStats({ stats, minimal }: PlayerStatsProps) {
|
||||
{stats.droppedFrames != undefined && (
|
||||
<div className="flex flex-col items-center justify-end gap-1">
|
||||
<span className="text-white/70">
|
||||
{t("stats.droppedFrames.short")}
|
||||
{t("stats.droppedFrames.short.title")}
|
||||
</span>
|
||||
<span className="text-white">
|
||||
{t("stats.droppedFrames.short.value", {
|
||||
|
||||
@@ -171,7 +171,7 @@ export default function PreviewThumbnailPlayer({
|
||||
review.start_time,
|
||||
config?.ui.time_format == "24hour"
|
||||
? t("time.formattedTimestampExcludeSeconds.24hour", { ns: "common" })
|
||||
: t("time.formattedTimestampExcludeSeconds", { ns: "common" }),
|
||||
: t("time.formattedTimestampExcludeSeconds.12hour", { ns: "common" }),
|
||||
config?.ui?.timezone,
|
||||
);
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export function CameraStreamingDialog({
|
||||
setIsDialogOpen,
|
||||
onSave,
|
||||
}: CameraStreamingDialogProps) {
|
||||
const { t } = useTranslation(["components/camera"]);
|
||||
const { t } = useTranslation(["components/camera", "components/dialog"]);
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -198,7 +198,9 @@ export function CameraStreamingDialog({
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 text-xs">
|
||||
{t("streaming.restreaming.desc", { ns: "components/dialog" })}
|
||||
{t("streaming.restreaming.desc.title", {
|
||||
ns: "components/dialog",
|
||||
})}
|
||||
<div className="mt-2 flex items-center text-primary">
|
||||
<Link
|
||||
to="https://docs.frigate.video/configuration/live"
|
||||
@@ -206,7 +208,7 @@ export function CameraStreamingDialog({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
{t("streaming.restreaming.readTheDocumentation", {
|
||||
{t("streaming.restreaming.desc.readTheDocumentation", {
|
||||
ns: "components/dialog",
|
||||
})}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
@@ -257,7 +259,7 @@ export function CameraStreamingDialog({
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 text-xs">
|
||||
{t("group.camera.setting.audio.desc")}
|
||||
{t("group.camera.setting.audio.tips.title")}
|
||||
<div className="mt-2 flex items-center text-primary">
|
||||
<Link
|
||||
to="https://docs.frigate.video/configuration/live"
|
||||
@@ -265,7 +267,7 @@ export function CameraStreamingDialog({
|
||||
rel="noopener noreferrer"
|
||||
className="inline"
|
||||
>
|
||||
{t("group.camera.setting.audio.desc.document")}
|
||||
{t("group.camera.setting.audio.tips.document")}
|
||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||
</Link>
|
||||
</div>
|
||||
@@ -322,7 +324,7 @@ export function CameraStreamingDialog({
|
||||
<>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(
|
||||
"group.camera.setting.streamMethod.method.continuousStreaming.desc",
|
||||
"group.camera.setting.streamMethod.method.continuousStreaming.desc.title",
|
||||
)}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -107,7 +107,7 @@ export default function MotionMaskEditPane({
|
||||
polygon: z.object({ name: z.string(), isFinished: z.boolean() }),
|
||||
})
|
||||
.refine(() => polygon?.isFinished === true, {
|
||||
message: t("masksAndZones.polygonDrawing.error.mustBeFinished"),
|
||||
message: t("masksAndZones.form.polygonDrawing.error.mustBeFinished"),
|
||||
path: ["polygon.isFinished"],
|
||||
});
|
||||
|
||||
@@ -166,7 +166,7 @@ export default function MotionMaskEditPane({
|
||||
if (res.status === 200) {
|
||||
toast.success(
|
||||
polygon.name
|
||||
? t("masksAndZones.motionMasks.toast.success", {
|
||||
? t("masksAndZones.motionMasks.toast.success.title", {
|
||||
polygonName: polygon.name,
|
||||
})
|
||||
: t("masksAndZones.motionMasks.toast.success.noName"),
|
||||
@@ -177,7 +177,7 @@ export default function MotionMaskEditPane({
|
||||
updateConfig();
|
||||
} else {
|
||||
toast.error(
|
||||
t("toast.save.error", {
|
||||
t("toast.save.error.title", {
|
||||
errorMessage: res.statusText,
|
||||
ns: "common",
|
||||
}),
|
||||
@@ -192,9 +192,12 @@ export default function MotionMaskEditPane({
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(t("toast.save.error", { errorMessage, ns: "common" }), {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("toast.save.error.title", { errorMessage, ns: "common" }),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
@@ -283,7 +286,7 @@ export default function MotionMaskEditPane({
|
||||
{polygonArea && polygonArea >= 0.35 && (
|
||||
<>
|
||||
<div className="mb-3 text-sm text-danger">
|
||||
{t("masksAndZones.motionMasks.polygonAreaTooLarge", {
|
||||
{t("masksAndZones.motionMasks.polygonAreaTooLarge.title", {
|
||||
polygonArea: Math.round(polygonArea * 100),
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -109,7 +109,7 @@ export default function ObjectMaskEditPane({
|
||||
polygon: z.object({ isFinished: z.boolean(), name: z.string() }),
|
||||
})
|
||||
.refine(() => polygon?.isFinished === true, {
|
||||
message: t("masksAndZones.polygonDrawing.error.mustBeFinished"),
|
||||
message: t("masksAndZones.form.polygonDrawing.error.mustBeFinished"),
|
||||
path: ["polygon.isFinished"],
|
||||
});
|
||||
|
||||
@@ -198,7 +198,7 @@ export default function ObjectMaskEditPane({
|
||||
if (res.status === 200) {
|
||||
toast.success(
|
||||
polygon.name
|
||||
? t("masksAndZones.objectMasks.toast.success", {
|
||||
? t("masksAndZones.objectMasks.toast.success.title", {
|
||||
polygonName: polygon.name,
|
||||
})
|
||||
: t("masksAndZones.objectMasks.toast.success.noName"),
|
||||
@@ -209,7 +209,7 @@ export default function ObjectMaskEditPane({
|
||||
updateConfig();
|
||||
} else {
|
||||
toast.error(
|
||||
t("toast.save.error", {
|
||||
t("toast.save.error.title", {
|
||||
errorMessage: res.statusText,
|
||||
ns: "common",
|
||||
}),
|
||||
@@ -225,7 +225,7 @@ export default function ObjectMaskEditPane({
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(
|
||||
t("toast.save.error", {
|
||||
t("toast.save.error.title", {
|
||||
errorMessage,
|
||||
ns: "common",
|
||||
}),
|
||||
@@ -327,7 +327,7 @@ export default function ObjectMaskEditPane({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("masksAndZones.objectMasks.objects")}
|
||||
{t("masksAndZones.objectMasks.objects.title")}
|
||||
</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
|
||||
@@ -188,9 +188,9 @@ export default function PolygonItem({
|
||||
updateConfig();
|
||||
} else {
|
||||
toast.error(
|
||||
t("toast.save.error", {
|
||||
errorMessage: res.statusText,
|
||||
t("toast.save.error.title", {
|
||||
ns: "common",
|
||||
errorMessage: res.statusText,
|
||||
}),
|
||||
{
|
||||
position: "top-center",
|
||||
@@ -203,9 +203,12 @@ export default function PolygonItem({
|
||||
error.response?.data?.message ||
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(t("toast.save.error", { errorMessage, ns: "common" }), {
|
||||
position: "top-center",
|
||||
});
|
||||
toast.error(
|
||||
t("toast.save.error.title", { errorMessage, ns: "common" }),
|
||||
{
|
||||
position: "top-center",
|
||||
},
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -59,7 +59,9 @@ export default function ExploreSettings({
|
||||
<div className={cn(className, "my-3 space-y-5 py-3 md:mt-0 md:py-0")}>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-md">{t("explore.settings.defaultView")}</div>
|
||||
<div className="text-md">
|
||||
{t("explore.settings.defaultView.title")}
|
||||
</div>
|
||||
<div className="space-y-1 text-xs text-muted-foreground">
|
||||
{t("explore.settings.defaultView.desc")}
|
||||
</div>
|
||||
@@ -95,7 +97,9 @@ export default function ExploreSettings({
|
||||
<DropdownMenuSeparator />
|
||||
<div className="flex w-full flex-col space-y-4">
|
||||
<div className="space-y-0.5">
|
||||
<div className="text-md">{t("explore.settings.gridColumns")}</div>
|
||||
<div className="text-md">
|
||||
{t("explore.settings.gridColumns.title")}
|
||||
</div>
|
||||
<div className="space-y-1 text-xs text-muted-foreground">
|
||||
{t("explore.settings.gridColumns.desc")}
|
||||
</div>
|
||||
|
||||
@@ -161,7 +161,7 @@ export default function ZoneEditPane({
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
isFinished: z.boolean().refine(() => polygon?.isFinished === true, {
|
||||
message: t("masksAndZones.polygonDrawing.error.mustBeFinished"),
|
||||
message: t("masksAndZones.form.polygonDrawing.error.mustBeFinished"),
|
||||
}),
|
||||
objects: z.array(z.string()).optional(),
|
||||
review_alerts: z.boolean().default(false).optional(),
|
||||
@@ -170,28 +170,28 @@ export default function ZoneEditPane({
|
||||
lineA: z.coerce
|
||||
.number()
|
||||
.min(0.1, {
|
||||
message: t("masksAndZones.form.distance.error"),
|
||||
message: t("masksAndZones.form.distance.error.text"),
|
||||
})
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
lineB: z.coerce
|
||||
.number()
|
||||
.min(0.1, {
|
||||
message: t("masksAndZones.form.distance.error"),
|
||||
message: t("masksAndZones.form.distance.error.text"),
|
||||
})
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
lineC: z.coerce
|
||||
.number()
|
||||
.min(0.1, {
|
||||
message: t("masksAndZones.form.distance.error"),
|
||||
message: t("masksAndZones.form.distance.error.text"),
|
||||
})
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
lineD: z.coerce
|
||||
.number()
|
||||
.min(0.1, {
|
||||
message: t("masksAndZones.form.distance.error"),
|
||||
message: t("masksAndZones.form.distance.error.text"),
|
||||
})
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
@@ -422,7 +422,7 @@ export default function ZoneEditPane({
|
||||
updateConfig();
|
||||
} else {
|
||||
toast.error(
|
||||
t("toast.save.error", {
|
||||
t("toast.save.error.title", {
|
||||
errorMessage: res.statusText,
|
||||
ns: "common",
|
||||
}),
|
||||
@@ -438,7 +438,7 @@ export default function ZoneEditPane({
|
||||
error.response?.data?.detail ||
|
||||
"Unknown error";
|
||||
toast.error(
|
||||
t("toast.save.error", {
|
||||
t("toast.save.error.title", {
|
||||
errorMessage,
|
||||
ns: "common",
|
||||
}),
|
||||
@@ -496,7 +496,7 @@ export default function ZoneEditPane({
|
||||
: t("masksAndZones.zones.add")}
|
||||
</Heading>
|
||||
<div className="my-2 text-sm text-muted-foreground">
|
||||
<p>{t("masksAndZones.zones.desc")}</p>
|
||||
<p>{t("masksAndZones.zones.desc.title")}</p>
|
||||
</div>
|
||||
<Separator className="my-3 bg-secondary" />
|
||||
{polygons && activePolygonIndex !== undefined && (
|
||||
@@ -532,7 +532,7 @@ export default function ZoneEditPane({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("masksAndZones.zones.name")}</FormLabel>
|
||||
<FormLabel>{t("masksAndZones.zones.name.title")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
|
||||
@@ -553,7 +553,7 @@ export default function ZoneEditPane({
|
||||
name="inertia"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("masksAndZones.zones.inertia")}</FormLabel>
|
||||
<FormLabel>{t("masksAndZones.zones.inertia.title")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
|
||||
@@ -576,7 +576,9 @@ export default function ZoneEditPane({
|
||||
name="loitering_time"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("masksAndZones.zones.loiteringTime")}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("masksAndZones.zones.loiteringTime.title")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="text-md w-full border border-input bg-background p-2 hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
|
||||
@@ -595,7 +597,7 @@ export default function ZoneEditPane({
|
||||
/>
|
||||
<Separator className="my-2 flex bg-secondary" />
|
||||
<FormItem>
|
||||
<FormLabel>{t("masksAndZones.zones.objects")}</FormLabel>
|
||||
<FormLabel>{t("masksAndZones.zones.objects.title")}</FormLabel>
|
||||
<FormDescription>
|
||||
{t("masksAndZones.zones.objects.desc")}
|
||||
</FormDescription>
|
||||
@@ -631,7 +633,7 @@ export default function ZoneEditPane({
|
||||
className="cursor-pointer text-primary"
|
||||
htmlFor="allLabels"
|
||||
>
|
||||
{t("masksAndZones.zones.speedEstimation")}
|
||||
{t("masksAndZones.zones.speedEstimation.title")}
|
||||
</FormLabel>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
@@ -644,7 +646,7 @@ export default function ZoneEditPane({
|
||||
) {
|
||||
toast.error(
|
||||
t(
|
||||
"masksAndZones.zones.speedEstimation.pointLengthError",
|
||||
"masksAndZones.zones.speedThreshold.toast.error.pointLengthError",
|
||||
),
|
||||
);
|
||||
return;
|
||||
@@ -655,7 +657,7 @@ export default function ZoneEditPane({
|
||||
if (checked && loiteringTime && loiteringTime > 0) {
|
||||
toast.error(
|
||||
t(
|
||||
"masksAndZones.zones.speedEstimation.loiteringTimeError",
|
||||
"masksAndZones.zones.speedThreshold.toast.error.loiteringTimeError",
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -778,11 +780,12 @@ export default function ZoneEditPane({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("masksAndZones.zones.speedThreshold", {
|
||||
{t("masksAndZones.zones.speedThreshold.title", {
|
||||
ns: "views/settings",
|
||||
unit:
|
||||
config?.ui.unit_system == "imperial"
|
||||
? t("unit.speed.mph")
|
||||
: t("unit.speed.kph"),
|
||||
? t("unit.speed.mph", { ns: "common" })
|
||||
: t("unit.speed.kph", { ns: "common" }),
|
||||
})}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
|
||||
@@ -60,15 +60,15 @@ interface Preset {
|
||||
|
||||
// Define presets
|
||||
const PRESETS: Preset[] = [
|
||||
{ name: "today", label: t("time.today") },
|
||||
{ name: "yesterday", label: t("time.yesterday") },
|
||||
{ name: "last7", label: t("time.last7") },
|
||||
{ name: "last14", label: t("time.last14") },
|
||||
{ name: "last30", label: t("time.last30") },
|
||||
{ name: "thisWeek", label: t("time.thisWeek") },
|
||||
{ name: "lastWeek", label: t("time.lastWeek") },
|
||||
{ name: "thisMonth", label: t("time.thisMonth") },
|
||||
{ name: "lastMonth", label: t("time.lastMonth") },
|
||||
{ name: "today", label: t("time.today", { ns: "common" }) },
|
||||
{ name: "yesterday", label: t("time.yesterday", { ns: "common" }) },
|
||||
{ name: "last7", label: t("time.last7", { ns: "common" }) },
|
||||
{ name: "last14", label: t("time.last14", { ns: "common" }) },
|
||||
{ name: "last30", label: t("time.last30", { ns: "common" }) },
|
||||
{ name: "thisWeek", label: t("time.thisWeek", { ns: "common" }) },
|
||||
{ name: "lastWeek", label: t("time.lastWeek", { ns: "common" }) },
|
||||
{ name: "thisMonth", label: t("time.thisMonth", { ns: "common" }) },
|
||||
{ name: "lastMonth", label: t("time.lastMonth", { ns: "common" }) },
|
||||
];
|
||||
|
||||
/** The DateRangePicker component allows a user to select a range of dates */
|
||||
|
||||
@@ -8,7 +8,7 @@ import { t } from "i18next";
|
||||
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||
<nav
|
||||
role="navigation"
|
||||
aria-label={t("pagination.label")}
|
||||
aria-label={t("pagination.label", { ns: "common" })}
|
||||
className={cn("mx-auto flex w-full justify-center", className)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -65,13 +65,13 @@ const PaginationPrevious = ({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||
<PaginationLink
|
||||
aria-label={t("pagination.previous.label")}
|
||||
aria-label={t("pagination.previous.label", { ns: "common" })}
|
||||
size="default"
|
||||
className={cn("gap-1 pl-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
<span>{t("pagination.previous")}</span>
|
||||
<span>{t("pagination.previous.title", { ns: "common" })}</span>
|
||||
</PaginationLink>
|
||||
);
|
||||
PaginationPrevious.displayName = "PaginationPrevious";
|
||||
@@ -81,12 +81,12 @@ const PaginationNext = ({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||
<PaginationLink
|
||||
aria-label={t("pagination.next.label")}
|
||||
aria-label={t("pagination.next.label", { ns: "common" })}
|
||||
size="default"
|
||||
className={cn("gap-1 pr-2.5", className)}
|
||||
{...props}
|
||||
>
|
||||
<span>{t("pagination.next")}</span>
|
||||
<span>{t("pagination.next.title", { ns: "common" })}</span>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</PaginationLink>
|
||||
);
|
||||
@@ -102,7 +102,7 @@ const PaginationEllipsis = ({
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">{t("pagination.more")}</span>
|
||||
<span className="sr-only">{t("pagination.more", { ns: "common" })}</span>
|
||||
</span>
|
||||
);
|
||||
PaginationEllipsis.displayName = "PaginationEllipsis";
|
||||
|
||||
Reference in New Issue
Block a user