feat(i18n): add internationalization support for 2FA setup and error messages

This commit is contained in:
JiPai 2025-03-07 23:32:15 +08:00
parent 2bced3e9b6
commit 6df680e9da
3 changed files with 83 additions and 27 deletions

View File

@ -26,6 +26,7 @@ import { authClient } from "@/lib/auth-client";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Fingerprint, QrCode } from "lucide-react"; import { Fingerprint, QrCode } from "lucide-react";
import { useTranslation } from "next-i18next";
import QRCode from "qrcode"; import QRCode from "qrcode";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
@ -55,6 +56,7 @@ type PinForm = z.infer<typeof PinSchema>;
export const Enable2FA = () => { export const Enable2FA = () => {
const utils = api.useUtils(); const utils = api.useUtils();
const { t } = useTranslation("settings");
const [data, setData] = useState<TwoFactorSetupData | null>(null); const [data, setData] = useState<TwoFactorSetupData | null>(null);
const [backupCodes, setBackupCodes] = useState<string[]>([]); const [backupCodes, setBackupCodes] = useState<string[]>([]);
const [isDialogOpen, setIsDialogOpen] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false);
@ -86,16 +88,18 @@ export const Enable2FA = () => {
}); });
setStep("verify"); setStep("verify");
toast.success("Scan the QR code with your authenticator app"); toast.success(t("settings.2fa.scanQrCode"));
} else { } else {
throw new Error("No TOTP URI received from server"); throw new Error("No TOTP URI received from server");
} }
} catch (error) { } catch (error) {
toast.error( toast.error(
error instanceof Error ? error.message : "Error setting up 2FA", error instanceof Error
? error.message
: t("settings.2fa.errorSettingUp")
); );
passwordForm.setError("password", { passwordForm.setError("password", {
message: "Error verifying password", message: t("settings.2fa.errorVerifyingPassword"),
}); });
} finally { } finally {
setIsPasswordLoading(false); setIsPasswordLoading(false);
@ -111,9 +115,9 @@ export const Enable2FA = () => {
if (result.error) { if (result.error) {
if (result.error.code === "INVALID_TWO_FACTOR_AUTHENTICATION") { if (result.error.code === "INVALID_TWO_FACTOR_AUTHENTICATION") {
pinForm.setError("pin", { pinForm.setError("pin", {
message: "Invalid code. Please try again.", message: t("settings.2fa.invalidCode"),
}); });
toast.error("Invalid verification code"); toast.error(t("settings.2fa.invalidVerificationCode"));
return; return;
} }
@ -124,14 +128,14 @@ export const Enable2FA = () => {
throw new Error("No response received from server"); throw new Error("No response received from server");
} }
toast.success("2FA configured successfully"); toast.success(t("settings.2fa.success"));
utils.user.get.invalidate(); utils.user.get.invalidate();
setIsDialogOpen(false); setIsDialogOpen(false);
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
const errorMessage = const errorMessage =
error.message === "Failed to fetch" error.message === "Failed to fetch"
? "Connection error. Please check your internet connection." ? t("settings.2fa.connectionError")
: error.message; : error.message;
pinForm.setError("pin", { pinForm.setError("pin", {
@ -140,9 +144,9 @@ export const Enable2FA = () => {
toast.error(errorMessage); toast.error(errorMessage);
} else { } else {
pinForm.setError("pin", { pinForm.setError("pin", {
message: "Error verifying code", message: t("settings.2fa.errorVerifyingCode"),
}); });
toast.error("Error verifying 2FA code"); toast.error(t("settings.2fa.errorVerifying2faCode"));
} }
} }
}; };
@ -176,16 +180,16 @@ export const Enable2FA = () => {
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="ghost"> <Button variant="ghost">
<Fingerprint className="size-4 text-muted-foreground" /> <Fingerprint className="size-4 text-muted-foreground" />
Enable 2FA {t("settings.2fa.enable2fa")}
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-xl"> <DialogContent className="max-h-screen overflow-y-auto sm:max-w-xl">
<DialogHeader> <DialogHeader>
<DialogTitle>2FA Setup</DialogTitle> <DialogTitle>{t("settings.2fa.title")}</DialogTitle>
<DialogDescription> <DialogDescription>
{step === "password" {step === "password"
? "Enter your password to begin 2FA setup" ? t("settings.2fa.enterPassword")
: "Scan the QR code and verify with your authenticator app"} : t("settings.2fa.scanQrCodeAndVerify")}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
@ -201,16 +205,18 @@ export const Enable2FA = () => {
name="password" name="password"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Password</FormLabel> <FormLabel>
{t("settings.2fa.password")}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="password" type="password"
placeholder="Enter your password" placeholder={t("settings.2fa.enterPasswordPlaceholder")}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
Enter your password to enable 2FA {t("settings.2fa.enterPasswordDescription")}
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -221,7 +227,7 @@ export const Enable2FA = () => {
className="w-full" className="w-full"
isLoading={isPasswordLoading} isLoading={isPasswordLoading}
> >
Continue {t("settings.2fa.continue")}
</Button> </Button>
</form> </form>
</Form> </Form>
@ -238,16 +244,16 @@ export const Enable2FA = () => {
<div className="flex flex-col items-center gap-4 p-6 border rounded-lg"> <div className="flex flex-col items-center gap-4 p-6 border rounded-lg">
<QrCode className="size-5 text-muted-foreground" /> <QrCode className="size-5 text-muted-foreground" />
<span className="text-sm font-medium"> <span className="text-sm font-medium">
Scan this QR code with your authenticator app {t("settings.2fa.scanQrCode")}
</span> </span>
<img <img
src={data.qrCodeUrl} src={data.qrCodeUrl}
alt="2FA QR Code" alt={t("settings.2fa.qrCodeAlt")}
className="rounded-lg w-48 h-48" className="rounded-lg w-48 h-48"
/> />
<div className="flex flex-col gap-2 text-center"> <div className="flex flex-col gap-2 text-center">
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">
Can't scan the QR code? {t("settings.2fa.cantScanQrCode")}
</span> </span>
<span className="text-xs font-mono bg-muted p-2 rounded"> <span className="text-xs font-mono bg-muted p-2 rounded">
{data.secret} {data.secret}
@ -257,7 +263,9 @@ export const Enable2FA = () => {
{backupCodes && backupCodes.length > 0 && ( {backupCodes && backupCodes.length > 0 && (
<div className="w-full space-y-3 border rounded-lg p-4"> <div className="w-full space-y-3 border rounded-lg p-4">
<h4 className="font-medium">Backup Codes</h4> <h4 className="font-medium">
{t("settings.2fa.backupCodes")}
</h4>
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
{backupCodes.map((code, index) => ( {backupCodes.map((code, index) => (
<code <code
@ -269,9 +277,7 @@ export const Enable2FA = () => {
))} ))}
</div> </div>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Save these backup codes in a secure place. You can use {t("settings.2fa.saveBackupCodes")}
them to access your account if you lose access to your
authenticator device.
</p> </p>
</div> </div>
)} )}
@ -288,7 +294,9 @@ export const Enable2FA = () => {
name="pin" name="pin"
render={({ field }) => ( render={({ field }) => (
<FormItem className="flex flex-col justify-center items-center"> <FormItem className="flex flex-col justify-center items-center">
<FormLabel>Verification Code</FormLabel> <FormLabel>
{t("settings.2fa.verificationCode")}
</FormLabel>
<FormControl> <FormControl>
<InputOTP maxLength={6} {...field}> <InputOTP maxLength={6} {...field}>
<InputOTPGroup> <InputOTPGroup>
@ -302,7 +310,7 @@ export const Enable2FA = () => {
</InputOTP> </InputOTP>
</FormControl> </FormControl>
<FormDescription> <FormDescription>
Enter the 6-digit code from your authenticator app {t("settings.2fa.enterVerificationCode")}
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -314,7 +322,7 @@ export const Enable2FA = () => {
className="w-full" className="w-full"
isLoading={isPasswordLoading} isLoading={isPasswordLoading}
> >
Enable 2FA {t("settings.2fa.enable2fa")}
</Button> </Button>
</form> </form>
</Form> </Form>

View File

@ -43,6 +43,30 @@
"settings.profile.password": "Password", "settings.profile.password": "Password",
"settings.profile.avatar": "Avatar", "settings.profile.avatar": "Avatar",
"settings.2fa.enable2fa": "Enable 2FA",
"settings.2fa.title": "2FA Setup",
"settings.2fa.enterPassword": "Enter your password to begin 2FA setup",
"settings.2fa.scanQrCodeAndVerify": "Scan the QR code and verify with your authenticator app",
"settings.2fa.password": "Password",
"settings.2fa.enterPasswordPlaceholder": "Enter your password",
"settings.2fa.enterPasswordDescription": "Enter your password to enable 2FA",
"settings.2fa.continue": "Continue",
"settings.2fa.scanQrCode": "Scan this QR code with your authenticator app",
"settings.2fa.qrCodeAlt": "2FA QR Code",
"settings.2fa.cantScanQrCode": "Can't scan the QR code?",
"settings.2fa.backupCodes": "Backup Codes",
"settings.2fa.saveBackupCodes": "Save these backup codes in a secure place. You can use them to access your account if you lose access to your authenticator device.",
"settings.2fa.verificationCode": "Verification Code",
"settings.2fa.enterVerificationCode": "Enter the 6-digit code from your authenticator app",
"settings.2fa.errorSettingUp": "Error setting up 2FA",
"settings.2fa.errorVerifyingPassword": "Error verifying password",
"settings.2fa.invalidCode": "Invalid code. Please try again.",
"settings.2fa.invalidVerificationCode": "Invalid verification code",
"settings.2fa.success": "2FA configured successfully",
"settings.2fa.connectionError": "Connection error. Please check your internet connection.",
"settings.2fa.errorVerifyingCode": "Error verifying code",
"settings.2fa.errorVerifying2faCode": "Error verifying 2FA code",
"settings.appearance.title": "Appearance", "settings.appearance.title": "Appearance",
"settings.appearance.description": "Customize the theme of your dashboard.", "settings.appearance.description": "Customize the theme of your dashboard.",
"settings.appearance.theme": "Theme", "settings.appearance.theme": "Theme",

View File

@ -43,6 +43,30 @@
"settings.profile.password": "密码", "settings.profile.password": "密码",
"settings.profile.avatar": "头像", "settings.profile.avatar": "头像",
"settings.2fa.enable2fa": "启用 2FA",
"settings.2fa.title": "2FA 设置",
"settings.2fa.enterPassword": "输入您的密码以开始 2FA 设置",
"settings.2fa.scanQrCodeAndVerify": "扫描二维码并使用您的身份验证器应用程序进行验证",
"settings.2fa.password": "密码",
"settings.2fa.enterPasswordPlaceholder": "输入您的密码",
"settings.2fa.enterPasswordDescription": "输入您的密码以启用 2FA",
"settings.2fa.continue": "继续",
"settings.2fa.scanQrCode": "使用您的身份验证器应用程序扫描此二维码",
"settings.2fa.qrCodeAlt": "2FA 二维码",
"settings.2fa.cantScanQrCode": "无法扫描二维码?",
"settings.2fa.backupCodes": "备份代码",
"settings.2fa.saveBackupCodes": "将这些备份代码保存在安全的地方。如果您丢失了身份验证设备,可以使用它们访问您的帐户。",
"settings.2fa.verificationCode": "验证码",
"settings.2fa.enterVerificationCode": "输入您的身份验证器应用程序中的 6 位数代码",
"settings.2fa.errorSettingUp": "设置 2FA 时出错",
"settings.2fa.errorVerifyingPassword": "验证密码时出错",
"settings.2fa.invalidCode": "无效的代码。请再试一次。",
"settings.2fa.invalidVerificationCode": "无效的验证码",
"settings.2fa.success": "2FA 配置成功",
"settings.2fa.connectionError": "连接错误。请检查您的互联网连接。",
"settings.2fa.errorVerifyingCode": "验证代码时出错",
"settings.2fa.errorVerifying2faCode": "验证 2FA 代码时出错",
"settings.appearance.title": "外观", "settings.appearance.title": "外观",
"settings.appearance.description": "自定义面板主题", "settings.appearance.description": "自定义面板主题",
"settings.appearance.theme": "主题", "settings.appearance.theme": "主题",