mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-01 16:42:18 +03:00
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:
co-authored by
Josh Hawkins
parent
db541abed4
commit
d34533981f
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user