* Object labels with spaces should use correct i18n keys

* Add Hungarian

* Ensure onvif move request has a valid speed before removing

When autotracking zooming is set to `disabled` (or is left out of the config), move_request["Speed"] may not exist, depending on the camera

* Add another frame cache debug log
This commit is contained in:
Josh Hawkins 2025-06-25 16:45:24 -05:00 committed by GitHub
parent 623bc72633
commit f97629433d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 45 additions and 30 deletions

View File

@ -445,7 +445,7 @@ class CameraState:
obj.thumbnail_data["frame_time"] if obj.thumbnail_data else None
)
logger.debug(
f"{self.name}: Tracked object {obj_id} thumbnail frame_time: {thumb_time}"
f"{self.name}: Tracked object {obj_id} thumbnail frame_time: {thumb_time}, false positive: {obj.false_positive}"
)
for t in thumb_frames_to_delete:
object_id = self.frame_cache[t].get("object_id", "unknown")

View File

@ -265,9 +265,15 @@ class OnvifController:
"RelativeZoomTranslationSpace"
][zoom_space_id]["URI"]
else:
if "Zoom" in move_request["Translation"]:
if (
move_request["Translation"] is not None
and "Zoom" in move_request["Translation"]
):
del move_request["Translation"]["Zoom"]
if "Zoom" in move_request["Speed"]:
if (
move_request["Speed"] is not None
and "Zoom" in move_request["Speed"]
):
del move_request["Speed"]["Zoom"]
logger.debug(
f"{camera_name}: Relative move request after deleting zoom: {move_request}"

View File

@ -12,7 +12,7 @@ import { SearchResult } from "@/types/search";
import { cn } from "@/lib/utils";
import { TooltipPortal } from "@radix-ui/react-tooltip";
import useContextMenu from "@/hooks/use-contextmenu";
import { useTranslation } from "react-i18next";
import { getTranslatedLabel } from "@/utils/i18n";
type SearchThumbnailProps = {
searchResult: SearchResult;
@ -23,7 +23,6 @@ export default function SearchThumbnail({
searchResult,
onClick,
}: SearchThumbnailProps) {
const { t } = useTranslation(["views/search"]);
const apiHost = useApiHost();
const { data: config } = useSWR<FrigateConfig>("config");
const [imgRef, imgLoaded, onImgLoad] = useImageLoaded();
@ -151,7 +150,7 @@ export default function SearchThumbnail({
.filter(
(item) => item !== undefined && !item.includes("-verified"),
)
.map((text) => t(text, { ns: "objects" }))
.map((text) => getTranslatedLabel(text))
.sort()
.join(", ")
.replaceAll("-verified", "")}

View File

@ -24,6 +24,7 @@ import CalendarFilterButton from "./CalendarFilterButton";
import { CamerasFilterButton } from "./CamerasFilterButton";
import PlatformAwareDialog from "../overlay/dialog/PlatformAwareDialog";
import { useTranslation } from "react-i18next";
import { getTranslatedLabel } from "@/utils/i18n";
const REVIEW_FILTERS = [
"cameras",
@ -498,7 +499,7 @@ export function GeneralFilterContent({
{allLabels.map((item) => (
<FilterSwitch
key={item}
label={t(item, { ns: "objects" })}
label={getTranslatedLabel(item)}
isChecked={filter.labels?.includes(item) ?? false}
onCheckedChange={(isChecked) => {
if (isChecked) {

View File

@ -26,6 +26,7 @@ import { CalendarRangeFilterButton } from "./CalendarFilterButton";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { useTranslation } from "react-i18next";
import { getTranslatedLabel } from "@/utils/i18n";
type SearchFilterGroupProps = {
className: string;
@ -372,7 +373,7 @@ export function GeneralFilterContent({
{allLabels.map((item) => (
<FilterSwitch
key={item}
label={t(item, { ns: "objects" })}
label={getTranslatedLabel(item)}
isChecked={currentLabels?.includes(item) ?? false}
onCheckedChange={(isChecked) => {
if (isChecked) {

View File

@ -38,6 +38,7 @@ import { toast } from "sonner";
import { Toaster } from "../ui/sonner";
import ActivityIndicator from "../indicators/activity-indicator";
import { useTranslation } from "react-i18next";
import { getTranslatedLabel } from "@/utils/i18n";
type ObjectMaskEditPaneProps = {
polygons?: Polygon[];
@ -101,7 +102,7 @@ export default function ObjectMaskEditPane({
return t("masksAndZones.objectMaskLabel", {
number: count + 1,
label: t(objectType, { ns: "objects" }),
label: getTranslatedLabel(objectType),
});
}, [polygons, polygon, t]);
@ -438,7 +439,7 @@ export function ZoneObjectSelector({ camera }: ZoneObjectSelectorProps) {
<SelectSeparator className="bg-secondary" />
{allLabels.map((item) => (
<SelectItem key={item} value={item}>
{t(item, { ns: "objects" })}
{getTranslatedLabel(item)}
</SelectItem>
))}
</SelectGroup>

View File

@ -33,6 +33,7 @@ import { Trans, useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { LuExternalLink } from "react-icons/lu";
import { useDocDomain } from "@/hooks/use-doc-domain";
import { getTranslatedLabel } from "@/utils/i18n";
type ZoneEditPaneProps = {
polygons?: Polygon[];
@ -969,7 +970,7 @@ export function ZoneObjectSelector({
className="w-full cursor-pointer text-primary smart-capitalize"
htmlFor={item}
>
{t(item, { ns: "objects" })}
{getTranslatedLabel(item)}
</Label>
<Switch
key={item}

View File

@ -19,4 +19,5 @@ export const supportedLanguageKeys = [
"pl",
"uk",
"cs",
"hu",
];

View File

@ -1,7 +1,13 @@
import i18n from "i18next";
import i18n, { t } from "i18next";
import { initReactI18next } from "react-i18next";
import HttpBackend from "i18next-http-backend";
export const getTranslatedLabel = (label: string) => {
if (!label) return "";
return t(`${label.replace(/\s+/g, "_").toLowerCase()}`, { ns: "objects" });
};
i18n
.use(initReactI18next)
.use(HttpBackend)

View File

@ -1,20 +1,15 @@
import { ObjectLifecycleSequence } from "@/types/timeline";
import { t } from "i18next";
import { getTranslatedLabel } from "./i18n";
export function getLifecycleItemDescription(
lifecycleItem: ObjectLifecycleSequence,
) {
// can't use useTranslation here
const label = t(
(
(Array.isArray(lifecycleItem.data.sub_label)
? lifecycleItem.data.sub_label[0]
: lifecycleItem.data.sub_label) || lifecycleItem.data.label
)
.replace(" ", "_")
.toLowerCase(),
{ ns: "objects" },
);
const rawLabel = Array.isArray(lifecycleItem.data.sub_label)
? lifecycleItem.data.sub_label[0]
: lifecycleItem.data.sub_label || lifecycleItem.data.label;
const label = getTranslatedLabel(rawLabel);
switch (lifecycleItem.class_type) {
case "visible":

View File

@ -22,6 +22,7 @@ import SearchResultActions from "@/components/menu/SearchResultActions";
import { SearchTab } from "@/components/overlay/detail/SearchDetailDialog";
import { FrigateConfig } from "@/types/frigateConfig";
import { useTranslation } from "react-i18next";
import { getTranslatedLabel } from "@/utils/i18n";
type ExploreViewProps = {
searchDetail: SearchResult | undefined;
@ -152,7 +153,7 @@ function ThumbnailRow({
return (
<div className="rounded-lg bg-background_alt p-2 md:px-4">
<div className="flex flex-row items-center text-lg smart-capitalize">
{t(objectType, { ns: "objects" })}
{getTranslatedLabel(objectType)}
{searchResults && (
<span className="ml-3 text-sm text-secondary-foreground">
{t("trackedObjectsCount", {

View File

@ -32,6 +32,7 @@ import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { useAlertsState, useDetectionsState, useEnabledState } from "@/api/ws";
import { useDocDomain } from "@/hooks/use-doc-domain";
import { getTranslatedLabel } from "@/utils/i18n";
type CameraSettingsViewProps = {
selectedCamera: string;
@ -81,18 +82,18 @@ export default function CameraSettingsView({
const alertsLabels = useMemo(() => {
return cameraConfig?.review.alerts.labels
? cameraConfig.review.alerts.labels
.map((label) => t(label, { ns: "objects" }))
.map((label) => getTranslatedLabel(label))
.join(", ")
: "";
}, [cameraConfig, t]);
}, [cameraConfig]);
const detectionsLabels = useMemo(() => {
return cameraConfig?.review.detections.labels
? cameraConfig.review.detections.labels
.map((label) => t(label, { ns: "objects" }))
.map((label) => getTranslatedLabel(label))
.join(", ")
: "";
}, [cameraConfig, t]);
}, [cameraConfig]);
// form

View File

@ -42,6 +42,7 @@ import { useSearchEffect } from "@/hooks/use-overlay-state";
import { useTranslation } from "react-i18next";
import { useDocDomain } from "@/hooks/use-doc-domain";
import { getTranslatedLabel } from "@/utils/i18n";
type MasksAndZoneViewProps = {
selectedCamera: string;
@ -332,7 +333,7 @@ export default function MasksAndZonesView({
camera: cameraConfig.name,
name: t("masksAndZones.objectMaskLabel", {
number: globalObjectMasksCount + index + 1,
label: t(objectName, { ns: "objects" }),
label: getTranslatedLabel(objectName),
}),
objects: [objectName],
points: interpolatePoints(

View File

@ -29,6 +29,7 @@ import { Separator } from "@/components/ui/separator";
import { isDesktop } from "react-device-detect";
import { Trans, useTranslation } from "react-i18next";
import { useDocDomain } from "@/hooks/use-doc-domain";
import { getTranslatedLabel } from "@/utils/i18n";
type ObjectSettingsViewProps = {
selectedCamera?: string;
@ -371,7 +372,7 @@ function ObjectList({ cameraConfig, objects }: ObjectListProps) {
{getIconForLabel(obj.label, "size-5 text-white")}
</div>
<div className="ml-3 text-lg">
{t(obj.label, { ns: "objects" })}
{getTranslatedLabel(obj.label)}
</div>
</div>
<div className="flex w-8/12 flex-row items-center justify-end">