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
+30 -26
View File
@@ -22,6 +22,7 @@ import {
DialogHeader,
DialogTitle,
} from "../ui/dialog";
import {
Select,
SelectContent,
@@ -31,6 +32,7 @@ import {
} from "../ui/select";
import { Shield, User } from "lucide-react";
import { LuCheck, LuX } from "react-icons/lu";
import { useTranslation } from "react-i18next";
type CreateUserOverlayProps = {
show: boolean;
@@ -43,22 +45,23 @@ export default function CreateUserDialog({
onCreate,
onCancel,
}: CreateUserOverlayProps) {
const { t } = useTranslation(["views/settings"]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const formSchema = z
.object({
user: z
.string()
.min(1, "Username is required")
.min(1, t("users.dialog.form.usernameIsRequired"))
.regex(/^[A-Za-z0-9._]+$/, {
message: "Username may only include letters, numbers, . or _",
message: t("users.dialog.createUser.usernameOnlyInclude"),
}),
password: z.string().min(1, "Password is required"),
confirmPassword: z.string().min(1, "Please confirm your password"),
role: z.enum(["admin", "viewer"]),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
message: t("users.dialog.form.password.notMatch"),
path: ["confirmPassword"],
});
@@ -109,10 +112,9 @@ export default function CreateUserDialog({
<Dialog open={show} onOpenChange={onCancel}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Create New User</DialogTitle>
<DialogTitle>{t("users.dialog.createUser.title")}</DialogTitle>
<DialogDescription>
Add a new user account and specify an role for access to areas of
the Frigate UI.
{t("users.dialog.createUser.desc")}
</DialogDescription>
</DialogHeader>
@@ -126,17 +128,17 @@ export default function CreateUserDialog({
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium">
Username
{t("users.dialog.form.user")}
</FormLabel>
<FormControl>
<Input
placeholder="Enter username"
placeholder={t("users.dialog.form.user.placeholder")}
className="h-10"
{...field}
/>
</FormControl>
<FormDescription className="text-xs text-muted-foreground">
Only letters, numbers, periods and underscores allowed.
{t("users.dialog.form.user.desc")}
</FormDescription>
<FormMessage />
</FormItem>
@@ -148,11 +150,11 @@ export default function CreateUserDialog({
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium">
Password
{t("users.dialog.form.password")}
</FormLabel>
<FormControl>
<Input
placeholder="Enter password"
placeholder={t("users.dialog.form.password.placeholder")}
type="password"
className="h-10"
{...field}
@@ -168,11 +170,13 @@ export default function CreateUserDialog({
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium">
Confirm Password
{t("users.dialog.form.password.confirm")}
</FormLabel>
<FormControl>
<Input
placeholder="Confirm password"
placeholder={t(
"users.dialog.form.password.confirm.placeholder",
)}
type="password"
className="h-10"
{...field}
@@ -184,14 +188,14 @@ export default function CreateUserDialog({
<>
<LuCheck className="size-3.5 text-green-500" />
<span className="text-green-600">
Passwords match
{t("users.dialog.form.password.match")}
</span>
</>
) : (
<>
<LuX className="size-3.5 text-red-500" />
<span className="text-red-600">
Passwords don't match
{t("users.dialog.form.password.notMatch")}
</span>
</>
)}
@@ -206,7 +210,9 @@ export default function CreateUserDialog({
name="role"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium">Role</FormLabel>
<FormLabel className="text-sm font-medium">
{t("role.title", { ns: "common" })}
</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
@@ -223,7 +229,7 @@ export default function CreateUserDialog({
>
<div className="flex items-center gap-2">
<Shield className="h-4 w-4 text-primary" />
<span>Admin</span>
<span>{t("role.admin", { ns: "common" })}</span>
</div>
</SelectItem>
<SelectItem
@@ -232,15 +238,13 @@ export default function CreateUserDialog({
>
<div className="flex items-center gap-2">
<User className="h-4 w-4 text-muted-foreground" />
<span>Viewer</span>
<span>{t("role.viewer", { ns: "common" })}</span>
</div>
</SelectItem>
</SelectContent>
</Select>
<FormDescription className="text-xs text-muted-foreground">
Admins have full access to all features in the Frigate UI.
Viewers are limited to viewing cameras, review items, and
historical footage in the UI.
{t("role.desc", { ns: "common" })}
</FormDescription>
<FormMessage />
</FormItem>
@@ -252,16 +256,16 @@ export default function CreateUserDialog({
<div className="flex flex-row gap-2 pt-5">
<Button
className="flex flex-1"
aria-label="Cancel"
aria-label={t("button.cancel", { ns: "common" })}
disabled={isLoading}
onClick={handleCancel}
type="button"
>
Cancel
{t("button.cancel", { ns: "common" })}
</Button>
<Button
variant="select"
aria-label="Save"
aria-label={t("button.save", { ns: "common" })}
disabled={isLoading || !form.formState.isValid}
className="flex flex-1"
type="submit"
@@ -269,10 +273,10 @@ export default function CreateUserDialog({
{isLoading ? (
<div className="flex flex-row items-center gap-2">
<ActivityIndicator />
<span>Saving...</span>
<span>{t("button.saving", { ns: "common" })}</span>
</div>
) : (
"Save"
t("button.save", { ns: "common" })
)}
</Button>
</div>