feat: add i18n (translation/localization) (#16877)

* Translation module init

* Add more i18n keys

* fix: fix string wrong

* refactor: use namespace translation file

* chore: add more translation key

* fix: fix some page name error

* refactor: change Trans tag for t function

* chore: fix some key not work

* chore: fix SearchFilterDialog i18n key error

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* chore: fix en i18n file filter missing some keys

* chore: add some i18n keys

* chore: add more i18n keys again

* feat: add search page i18n

* feat: add explore model i18n keys

* Update web/src/components/menu/GeneralSettings.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Update web/src/components/menu/GeneralSettings.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Update web/src/components/menu/GeneralSettings.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* feat: add more live i18n keys

* feat: add more search setting i18n keys

* fix: remove some comment

* fix: fix some setting page url error

* Update web/src/views/settings/SearchSettingsView.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* fix: add system missing keys

* fix: update password update i18n keys

* chore: remove outdate translation.json file

* fix: fix exploreSettings error

* chore: add object setting i18n keys

* Update web/src/views/recording/RecordingView.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Update web/public/locales/en/components/filter.json

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Update web/src/components/overlay/ExportDialog.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* feat: add more i18n keys

* fix: fix motionDetectionTuner html node

* feat: add more page i18n keys

* fix: cameraStream i18n keys error

* feat: add Player i18n keys

* feat: add more toast i18n keys

* feat: change explore setting name

* feat: add more document title i18n keys

* feat: add more search i18n keys

* fix: fix accessDenied i18n keys error

* chore: add objectType i18n

* chore: add  inputWithTags i18n

* chore: add SearchFilterDialog i18n

* Update web/src/views/settings/ObjectSettingsView.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Update web/src/views/settings/ObjectSettingsView.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Update web/src/views/settings/ObjectSettingsView.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Update web/src/views/settings/ObjectSettingsView.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* Update web/src/views/settings/ObjectSettingsView.tsx

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>

* chore: add some missing i18n keys

* chore: remove most import { t } from "i18next";

---------

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
This commit is contained in:
GuoQing Liu
2025-03-16 10:36:20 -05:00
committed by GitHub
co-authored by Josh Hawkins
parent db541abed4
commit d34533981f
150 changed files with 6809 additions and 1926 deletions
+40 -36
View File
@@ -37,7 +37,9 @@ import PolygonItem from "@/components/settings/PolygonItem";
import { Link } from "react-router-dom";
import { isDesktop } from "react-device-detect";
import { StatusBarMessagesContext } from "@/context/statusbar-provider";
import { useSearchEffect } from "@/hooks/use-overlay-state";
import { useTranslation } from "react-i18next";
type MasksAndZoneViewProps = {
selectedCamera: string;
@@ -50,6 +52,7 @@ export default function MasksAndZonesView({
selectedZoneMask,
setUnsavedChanges,
}: MasksAndZoneViewProps) {
const { t } = useTranslation(["views/settings"]);
const { data: config } = useSWR<FrigateConfig>("config");
const [allPolygons, setAllPolygons] = useState<Polygon[]>([]);
const [editingPolygons, setEditingPolygons] = useState<Polygon[]>([]);
@@ -182,8 +185,8 @@ export default function MasksAndZonesView({
setActivePolygonIndex(undefined);
setHoveredPolygonIndex(null);
setUnsavedChanges(false);
document.title = "Mask and Zone Editor - Frigate";
}, [allPolygons, setUnsavedChanges]);
document.title = t("documentTitle.masksAndZones");
}, [allPolygons, setUnsavedChanges, t]);
const handleSave = useCallback(() => {
setAllPolygons([...(editingPolygons ?? [])]);
@@ -218,12 +221,16 @@ export default function MasksAndZonesView({
.map((point) => `${point[0]},${point[1]}`)
.join(","),
);
toast.success(`Copied coordinates for ${poly.name} to clipboard.`);
toast.success(
t("masksAndZones.toast.success.copyCoordinates", {
polyName: poly.name,
}),
);
} else {
toast.error("Could not copy coordinates to clipboard.");
toast.error(t("masksAndZones.toast.error.copyCoordinatesFailed"));
}
},
[allPolygons, scaledHeight, scaledWidth],
[allPolygons, scaledHeight, scaledWidth, t],
);
useEffect(() => {
@@ -418,8 +425,8 @@ export default function MasksAndZonesView({
});
useEffect(() => {
document.title = "Mask and Zone Editor - Frigate";
}, []);
document.title = t("documentTitle.masksAndZones");
}, [t]);
if (!cameraConfig && !selectedCamera) {
return <ActivityIndicator />;
@@ -480,7 +487,7 @@ export default function MasksAndZonesView({
{editPane === undefined && (
<>
<Heading as="h3" className="my-2">
Masks / Zones
{t("menu.masksAndZones")}
</Heading>
<div className="flex w-full flex-col">
{(selectedZoneMask === undefined ||
@@ -489,15 +496,13 @@ export default function MasksAndZonesView({
<div className="my-3 flex flex-row items-center justify-between">
<HoverCard>
<HoverCardTrigger asChild>
<div className="text-md cursor-default">Zones</div>
<div className="text-md cursor-default">
{t("masksAndZones.zones.label")}
</div>
</HoverCardTrigger>
<HoverCardContent>
<div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
<p>
Zones allow you to define a specific area of the
frame so you can determine whether or not an
object is within a particular area.
</p>
<p>{t("masksAndZones.zones.desc")}</p>
<div className="flex items-center text-primary">
<Link
to="https://docs.frigate.video/configuration/zones"
@@ -505,7 +510,7 @@ export default function MasksAndZonesView({
rel="noopener noreferrer"
className="inline"
>
Documentation{" "}
{t("masksAndZones.zones.desc.documentation")}{" "}
<LuExternalLink className="ml-2 inline-flex size-3" />
</Link>
</div>
@@ -517,7 +522,7 @@ export default function MasksAndZonesView({
<Button
variant="secondary"
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
aria-label="Add a new zone"
aria-label={t("masksAndZones.zones.add")}
onClick={() => {
setEditPane("zone");
handleNewPolygon("zone");
@@ -526,7 +531,9 @@ export default function MasksAndZonesView({
<LuPlus />
</Button>
</TooltipTrigger>
<TooltipContent>Add Zone</TooltipContent>
<TooltipContent>
{t("masksAndZones.zones.add")}
</TooltipContent>
</Tooltip>
</div>
{allPolygons
@@ -556,17 +563,12 @@ export default function MasksAndZonesView({
<HoverCard>
<HoverCardTrigger asChild>
<div className="text-md cursor-default">
Motion Masks
{t("masksAndZones.motionMasks.label")}
</div>
</HoverCardTrigger>
<HoverCardContent>
<div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
<p>
Motion masks are used to prevent unwanted types
of motion from triggering detection. Over
masking will make it more difficult for objects
to be tracked.
</p>
<p>{t("masksAndZones.motionMasks.desc")}</p>
<div className="flex items-center text-primary">
<Link
to="https://docs.frigate.video/configuration/masks#motion-masks"
@@ -574,7 +576,9 @@ export default function MasksAndZonesView({
rel="noopener noreferrer"
className="inline"
>
Documentation{" "}
{t(
"masksAndZones.motionMasks.desc.documentation",
)}{" "}
<LuExternalLink className="ml-2 inline-flex size-3" />
</Link>
</div>
@@ -586,7 +590,7 @@ export default function MasksAndZonesView({
<Button
variant="secondary"
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
aria-label="Add a new motion mask"
aria-label={t("masksAndZones.motionMasks.add")}
onClick={() => {
setEditPane("motion_mask");
handleNewPolygon("motion_mask");
@@ -595,7 +599,9 @@ export default function MasksAndZonesView({
<LuPlus />
</Button>
</TooltipTrigger>
<TooltipContent>Add Motion Mask</TooltipContent>
<TooltipContent>
{t("masksAndZones.motionMasks.add")}
</TooltipContent>
</Tooltip>
</div>
{allPolygons
@@ -627,16 +633,12 @@ export default function MasksAndZonesView({
<HoverCard>
<HoverCardTrigger asChild>
<div className="text-md cursor-default">
Object Masks
{t("masksAndZones.objectMasks.label")}
</div>
</HoverCardTrigger>
<HoverCardContent>
<div className="my-2 flex flex-col gap-2 text-sm text-primary-variant">
<p>
Object filter masks are used to filter out false
positives for a given object type based on
location.
</p>
<p>{t("masksAndZones.objectMasks.desc")}</p>
<div className="flex items-center text-primary">
<Link
to="https://docs.frigate.video/configuration/masks#object-filter-masks"
@@ -644,7 +646,7 @@ export default function MasksAndZonesView({
rel="noopener noreferrer"
className="inline"
>
Documentation{" "}
{t("masksAndZones.objectMasks.documentation")}{" "}
<LuExternalLink className="ml-2 inline-flex size-3" />
</Link>
</div>
@@ -656,7 +658,7 @@ export default function MasksAndZonesView({
<Button
variant="secondary"
className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
aria-label="Add a new object mask"
aria-label={t("masksAndZones.objectMasks.add")}
onClick={() => {
setEditPane("object_mask");
handleNewPolygon("object_mask");
@@ -665,7 +667,9 @@ export default function MasksAndZonesView({
<LuPlus />
</Button>
</TooltipTrigger>
<TooltipContent>Add Object Mask</TooltipContent>
<TooltipContent>
{t("masksAndZones.objectMasks.add")}
</TooltipContent>
</Tooltip>
</div>
{allPolygons