i18n fixes (#17725)

* fix key

* add french

* Fix random 0

* fix i18n in role change dialog

* fix delete user dialog

* fix filter tips steps

* remove classes from i18n files

* combine disjointed norweigan localized files

* change submit to plus to use a question with yes/no

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
Josh Hawkins
2025-04-17 07:34:24 -06:00
committed by GitHub
co-authored by Nicolas Mowen
parent 1315a28252
commit 84c1ad59a2
17 changed files with 134 additions and 72 deletions
+25 -14
View File
@@ -51,7 +51,7 @@ import { toast } from "sonner";
import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
import { MdImageSearch } from "react-icons/md";
import { Trans, useTranslation } from "react-i18next";
import { useTranslation } from "react-i18next";
type InputWithTagsProps = {
inputFocused: boolean;
@@ -729,20 +729,31 @@ export default function InputWithTags({
<p className="text-sm text-muted-foreground">
{t("filter.tips.desc.text")}
</p>
<Trans
ns="views/search"
values={{
DateFormat: getIntlDateFormat(),
exampleTime:
config?.ui.time_format == "24hour"
? "15:00-16:00"
: "3:00PM-4:00PM",
}}
>
filter.tips.desc.step
</Trans>
<ul className="list-disc pl-5 text-sm text-primary-variant">
<li>{t("filter.tips.desc.step1")}</li>
<li>{t("filter.tips.desc.step2")}</li>
<li>{t("filter.tips.desc.step3")}</li>
<li>
{t("filter.tips.desc.step4", {
DateFormat: getIntlDateFormat(),
})}
</li>
<li>
{t("filter.tips.desc.step5", {
exampleTime:
config?.ui.time_format == "24hour"
? "15:00-16:00"
: "3:00PM-4:00PM",
})}
</li>
<li>{t("filter.tips.desc.step6")}</li>
</ul>
<p className="text-sm text-muted-foreground">
<Trans ns="views/search">filter.tips.desc.example</Trans>
{t("filter.tips.desc.exampleLabel")}{" "}
<code className="text-primary">
cameras:front_door label:person before:01012024
time_range:3:00PM-4:00PM
</code>
</p>
</div>
</PopoverContent>
@@ -78,6 +78,7 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) {
const languages = [
{ code: "en", label: t("menu.language.en") },
{ code: "es", label: t("menu.language.es") },
{ code: "fr", label: t("menu.language.fr") },
{ code: "zh-CN", label: t("menu.language.zhCN") },
{ code: "tr", label: t("menu.language.tr") },
{ code: "nl", label: t("menu.language.nl") },
@@ -35,7 +35,8 @@ export default function DeleteUserDialog({
<div className="my-4 rounded-md border border-destructive/20 bg-destructive/5 p-4 text-center text-sm">
<p className="font-medium text-destructive">
{t("users.dialog.deleteUser.warn", { username })}
{t("users.dialog.deleteUser.warn")}
<span className="font-medium"> {username}</span>?
</p>
</div>
@@ -1,4 +1,4 @@
import { Trans, useTranslation } from "react-i18next";
import { useTranslation } from "react-i18next";
import { Button } from "../ui/button";
import {
Dialog,
@@ -46,13 +46,28 @@ export default function RoleChangeDialog({
{t("users.dialog.changeRole.title")}
</DialogTitle>
<DialogDescription>
{t("users.dialog.changeRole.desc", { username })}
{t("users.dialog.changeRole.desc")}
<span className="font-medium"> {username}</span>
</DialogDescription>
</DialogHeader>
<div className="py-6">
<div className="py-3">
<div className="mb-4 text-sm text-muted-foreground">
<Trans ns="views/settings">users.dialog.changeRole.roleInfo</Trans>
<p>{t("users.dialog.changeRole.roleInfo.intro")}</p>
<ul className="mt-2 list-disc space-y-1 pl-5">
<li>
<span className="font-medium">
{t("users.dialog.changeRole.roleInfo.admin")}
</span>
: {t("users.dialog.changeRole.roleInfo.adminDesc")}
</li>
<li>
<span className="font-medium">
{t("users.dialog.changeRole.roleInfo.viewer")}
</span>
: {t("users.dialog.changeRole.roleInfo.viewerDesc")}
</li>
</ul>
</div>
<Select
@@ -73,7 +73,7 @@ import { LuInfo, LuSearch } from "react-icons/lu";
import { TooltipPortal } from "@radix-ui/react-tooltip";
import { FaPencilAlt } from "react-icons/fa";
import TextEntryDialog from "@/components/overlay/dialog/TextEntryDialog";
import { useTranslation } from "react-i18next";
import { Trans, useTranslation } from "react-i18next";
import { TbFaceId } from "react-icons/tb";
import { useIsAdmin } from "@/hooks/use-is-admin";
import FaceSelectionDialog from "../FaceSelectionDialog";
@@ -366,7 +366,7 @@ function ObjectDetailsTab({
const snapScore = useMemo(() => {
if (!search?.has_snapshot) {
return 0;
return undefined;
}
const value = search.data.score ?? search.score ?? 0;
@@ -1017,7 +1017,7 @@ export function ObjectSnapshotTab({
search,
onEventUploaded,
}: ObjectSnapshotTabProps) {
const { t } = useTranslation(["components/dialog"]);
const { t, i18n } = useTranslation(["components/dialog"]);
type SubmissionState = "reviewing" | "uploading" | "submitted";
const [imgRef, imgLoaded, onImgLoad] = useImageLoaded();
@@ -1128,42 +1128,67 @@ export function ObjectSnapshotTab({
</div>
</div>
<div className="flex flex-row justify-center gap-2 md:justify-end">
<div className="flex w-full flex-1 flex-col justify-center gap-2 md:ml-8 md:w-auto md:justify-end">
{state == "reviewing" && (
<>
<Button
className="bg-success"
aria-label={t("explore.plus.review.true.label")}
onClick={() => {
setState("uploading");
onSubmitToPlus(false);
}}
>
{/^[aeiou]/i.test(search?.label || "")
? t("explore.plus.review.true.true_other", {
label: search?.label,
})
: t("explore.plus.review.true.true_one", {
label: search?.label,
})}
</Button>
<Button
className="text-white"
aria-label={t("explore.plus.review.false.label")}
variant="destructive"
onClick={() => {
setState("uploading");
onSubmitToPlus(true);
}}
>
{/^[aeiou]/i.test(search?.label || "")
? t("explore.plus.review.false.false_other", {
label: search?.label,
})
: t("explore.plus.review.false.false_one", {
label: search?.label,
})}
</Button>
<div>
{i18n.language === "en" ? (
// English with a/an logic plus label
<>
{/^[aeiou]/i.test(search?.label || "") ? (
<Trans
ns="components/dialog"
values={{ label: search?.label }}
>
explore.plus.review.question.ask_an
</Trans>
) : (
<Trans
ns="components/dialog"
values={{ label: search?.label }}
>
explore.plus.review.question.ask_a
</Trans>
)}
</>
) : (
// For other languages
<Trans
ns="components/dialog"
values={{
untranslatedLabel: search?.label,
translatedLabel: t(
"filter.label." + search?.label,
),
}}
>
explore.plus.review.question.ask_full
</Trans>
)}
</div>
<div className="flex w-full flex-row gap-2">
<Button
className="flex-1 bg-success"
aria-label={t("button.yes", { ns: "common" })}
onClick={() => {
setState("uploading");
onSubmitToPlus(false);
}}
>
{t("button.yes", { ns: "common" })}
</Button>
<Button
className="flex-1 text-white"
aria-label={t("button.no", { ns: "common" })}
variant="destructive"
onClick={() => {
setState("uploading");
onSubmitToPlus(true);
}}
>
{t("button.no", { ns: "common" })}
</Button>
</div>
</>
)}
{state == "uploading" && <ActivityIndicator />}
@@ -242,7 +242,9 @@ export default function MotionMaskEditPane({
</Heading>
<div className="my-3 space-y-3 text-sm text-muted-foreground">
<p>
<Trans ns="views/settings">masksAndZones.motionMasks.context</Trans>
<Trans ns="views/settings">
masksAndZones.motionMasks.context.title
</Trans>
</p>
<div className="flex items-center text-primary">