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
+81 -49
View File
@@ -13,6 +13,7 @@ import { toast } from "sonner";
import DeleteUserDialog from "@/components/overlay/DeleteUserDialog";
import { HiTrash } from "react-icons/hi";
import { FaUserEdit } from "react-icons/fa";
import { LuPlus, LuShield, LuUserCog } from "react-icons/lu";
import {
Table,
@@ -30,8 +31,10 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import RoleChangeDialog from "@/components/overlay/RoleChangeDialog";
import { useTranslation } from "react-i18next";
export default function AuthenticationView() {
const { t } = useTranslation("views/settings");
const { data: config } = useSWR<FrigateConfig>("config");
const { data: users, mutate: mutateUsers } = useSWR<User[]>("users");
@@ -46,30 +49,38 @@ export default function AuthenticationView() {
>();
useEffect(() => {
document.title = "Authentication Settings - Frigate";
}, []);
document.title = t("documentTitle.authentication");
}, [t]);
const onSavePassword = useCallback((user: string, password: string) => {
axios
.put(`users/${user}/password`, { password })
.then((response) => {
if (response.status === 200) {
setShowSetPassword(false);
toast.success("Password updated successfully", {
position: "top-center",
});
}
})
.catch((error) => {
const errorMessage =
error.response?.data?.message ||
error.response?.data?.detail ||
"Unknown error";
toast.error(`Failed to save password: ${errorMessage}`, {
position: "top-center",
const onSavePassword = useCallback(
(user: string, password: string) => {
axios
.put(`users/${user}/password`, { password })
.then((response) => {
if (response.status === 200) {
setShowSetPassword(false);
toast.success(t("users.toast.success.updatePassword"), {
position: "top-center",
});
}
})
.catch((error) => {
const errorMessage =
error.response?.data?.message ||
error.response?.data?.detail ||
"Unknown error";
toast.error(
t("users.toast.error.setPasswordFailed", {
errorMessage,
}),
{
position: "top-center",
},
);
});
});
}, []);
},
[t],
);
const onCreate = (
user: string,
@@ -85,7 +96,7 @@ export default function AuthenticationView() {
users?.push({ username: user, role: role });
return users;
}, false);
toast.success(`User ${user} created successfully`, {
toast.success(t("users.toast.success.createUser", { user }), {
position: "top-center",
});
}
@@ -95,9 +106,14 @@ export default function AuthenticationView() {
error.response?.data?.message ||
error.response?.data?.detail ||
"Unknown error";
toast.error(`Failed to create user: ${errorMessage}`, {
position: "top-center",
});
toast.error(
t("users.toast.error.createUserFailed", {
errorMessage,
}),
{
position: "top-center",
},
);
});
};
@@ -111,7 +127,7 @@ export default function AuthenticationView() {
(users) => users?.filter((u) => u.username !== user),
false,
);
toast.success(`User ${user} deleted successfully`, {
toast.success(t("users.toast.success.deleteUser", { user }), {
position: "top-center",
});
}
@@ -121,9 +137,14 @@ export default function AuthenticationView() {
error.response?.data?.message ||
error.response?.data?.detail ||
"Unknown error";
toast.error(`Failed to delete user: ${errorMessage}`, {
position: "top-center",
});
toast.error(
t("users.toast.error.deleteUserFailed", {
errorMessage,
}),
{
position: "top-center",
},
);
});
};
@@ -142,7 +163,7 @@ export default function AuthenticationView() {
),
false,
);
toast.success(`Role updated for ${user}`, {
toast.success(t("users.toast.success.roleUpdated", { user }), {
position: "top-center",
});
}
@@ -152,9 +173,14 @@ export default function AuthenticationView() {
error.response?.data?.message ||
error.response?.data?.detail ||
"Unknown error";
toast.error(`Failed to update role: ${errorMessage}`, {
position: "top-center",
});
toast.error(
t("users.toast.error.roleUpdateFailed", {
errorMessage,
}),
{
position: "top-center",
},
);
});
};
@@ -173,20 +199,20 @@ export default function AuthenticationView() {
<div className="mb-5 flex flex-row items-center justify-between gap-2">
<div className="flex flex-col items-start">
<Heading as="h3" className="my-2">
User Management
{t("users.management")}
</Heading>
<p className="text-sm text-muted-foreground">
Manage this Frigate instance's user accounts.
{t("users.management.desc")}
</p>
</div>
<Button
className="flex items-center gap-2 self-start sm:self-auto"
aria-label="Add a new user"
aria-label={t("users.addUser")}
variant="default"
onClick={() => setShowCreate(true)}
>
<LuPlus className="size-4" />
Add User
{t("users.addUser")}
</Button>
</div>
<div className="mb-6 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
@@ -195,16 +221,20 @@ export default function AuthenticationView() {
<Table>
<TableHeader className="sticky top-0 bg-muted/50">
<TableRow>
<TableHead className="w-[250px]">Username</TableHead>
<TableHead>Role</TableHead>
<TableHead className="text-right">Actions</TableHead>
<TableHead className="w-[250px]">
{t("users.table.username")}
</TableHead>
<TableHead>{t("users.table.role")}</TableHead>
<TableHead className="text-right">
{t("users.table.actions")}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.length === 0 ? (
<TableRow>
<TableCell colSpan={3} className="h-24 text-center">
No users found.
{t("users.table.noUsers")}
</TableCell>
</TableRow>
) : (
@@ -231,7 +261,9 @@ export default function AuthenticationView() {
: ""
}
>
{user.role || "viewer"}
{t("role." + (user.role || "viewer"), {
ns: "common",
})}
</Badge>
</TableCell>
<TableCell className="text-right">
@@ -255,12 +287,12 @@ export default function AuthenticationView() {
>
<LuUserCog className="size-3.5" />
<span className="ml-1.5 hidden sm:inline-block">
Role
{t("role.title", { ns: "common" })}
</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Change user role</p>
<p>{t("users.table.changeRole")}</p>
</TooltipContent>
</Tooltip>
)}
@@ -278,12 +310,12 @@ export default function AuthenticationView() {
>
<FaUserEdit className="size-3.5" />
<span className="ml-1.5 hidden sm:inline-block">
Password
{t("users.table.password")}
</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Update password</p>
<p>{t("users.updatePassword")}</p>
</TooltipContent>
</Tooltip>
@@ -301,12 +333,12 @@ export default function AuthenticationView() {
>
<HiTrash className="size-3.5" />
<span className="ml-1.5 hidden sm:inline-block">
Delete
{t("button.delete", { ns: "common" })}
</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Delete user</p>
<p>{t("users.table.deleteUser")}</p>
</TooltipContent>
</Tooltip>
)}