Explorar el Código

feat: add i18n translations to Signup and OTP pages #69

- Import and use useTranslation hook in both components
- Replace all hardcoded strings with translation keys
- Signup page: labels, placeholders, buttons, error messages
- OTP page: all UI text, error messages, actions
- Fully supports English, Hungarian, and German languages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude hace 5 meses
padre
commit
a16f98260f
Se han modificado 2 ficheros con 48 adiciones y 44 borrados
  1. 21 19
      shopcall.ai-main/src/pages/OTP.tsx
  2. 27 25
      shopcall.ai-main/src/pages/Signup.tsx

+ 21 - 19
shopcall.ai-main/src/pages/OTP.tsx

@@ -5,8 +5,10 @@ import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp
 import { Mail, ArrowLeft, Loader2, Edit2 } from "lucide-react";
 import { useNavigate, useLocation } from "react-router-dom";
 import { API_URL } from "@/lib/config";
+import { useTranslation } from "react-i18next";
 
 const OTP = () => {
+  const { t } = useTranslation();
   const [otp, setOtp] = useState("");
   const navigate = useNavigate();
   const location = useLocation();
@@ -59,16 +61,16 @@ const OTP = () => {
       }
       // data.error == "Invalid OTP" show error message
       if (data.error == "Invalid OTP code") {
-        setError("Invalid OTP code");
+        setError(t('otp.errors.invalidOTP'));
       }
       else {
-        setError("Something went wrong. Please try again.");
+        setError(t('otp.errors.somethingWrong'));
       }
 
       console.log("OTP verification:", { otp });
     } catch (error) {
       console.error("OTP verification error:", error);
-      setError("Something went wrong. Please try again.");
+      setError(t('otp.errors.somethingWrong'));
     } finally {
       setIsVerifying(false);
     }
@@ -93,11 +95,11 @@ const OTP = () => {
         console.log("OTP resent successfully");
         // Optionally show a success message to user
       } else {
-        setError(data.error || "Failed to resend OTP. Please try again.");
+        setError(data.error || t('otp.errors.somethingWrong'));
       }
     } catch (error) {
       console.error("Resend OTP error:", error);
-      setError("Something went wrong. Please try again.");
+      setError(t('otp.errors.somethingWrong'));
     } finally {
       setIsResending(false);
     }
@@ -119,12 +121,12 @@ const OTP = () => {
             <img src="/uploads/e0ddbf09-622c-426a-851f-149776e300c0.png" alt="ShopCall.ai" className="w-10 h-10" />
             <span className="text-2xl font-bold text-white">ShopCall.ai</span>
           </div>
-          <p className="text-slate-400">Enter the verification code sent to your email</p>
+          <p className="text-slate-400">{t('otp.subtitle')}</p>
         </div>
 
         <Card className="bg-slate-800 border-slate-700">
           <CardHeader>
-            <CardTitle className="text-white text-center">Verify Your Email</CardTitle>
+            <CardTitle className="text-white text-center">{t('otp.title')}</CardTitle>
           </CardHeader>
           <CardContent>
             <form onSubmit={handleVerify} className="space-y-6">
@@ -137,7 +139,7 @@ const OTP = () => {
 
                 <div className="text-center">
                   <p className="text-slate-300 text-sm mb-2">
-                    We've sent a 6-digit code to
+                    {t('otp.sentTo')}
                   </p>
                   <div className="flex items-center justify-center gap-2">
                     <p className="text-white font-medium">
@@ -147,11 +149,11 @@ const OTP = () => {
                       }
                     </p>
                     <button
-                      onClick={() => navigate("/signup", { 
-                        state: { 
+                      onClick={() => navigate("/signup", {
+                        state: {
                           ...location.state,
-                          editingEmail: true 
-                        } 
+                          editingEmail: true
+                        }
                       })}
                       className="ml-2 text-cyan-400 hover:text-cyan-300 transition-colors"
                       title="Edit email address"
@@ -160,7 +162,7 @@ const OTP = () => {
                     </button>
                   </div>
                   <p className="text-slate-400 text-xs mt-1">
-                    Wrong email? Click the edit icon to change it
+                    {t('otp.wrongEmail')}
                   </p>
                 </div>
 
@@ -201,10 +203,10 @@ const OTP = () => {
                 {isVerifying ? (
                   <div className="flex items-center gap-2">
                     <Loader2 className="h-4 w-4 animate-spin" />
-                    Verifying...
+                    {t('otp.verifying')}
                   </div>
                 ) : (
-                  "Verify Code"
+                  t('otp.verifyCode')
                 )}
               </Button>
             </form>
@@ -212,7 +214,7 @@ const OTP = () => {
             <div className="mt-6 space-y-4">
               <div className="text-center">
                 <p className="text-slate-400 text-sm">
-                  Didn't receive the code?{" "}
+                  {t('otp.didntReceive')}{" "}
                   <button
                     onClick={handleResend}
                     disabled={isResending}
@@ -221,10 +223,10 @@ const OTP = () => {
                     {isResending ? (
                       <span className="inline-flex items-center gap-1">
                         <Loader2 className="h-3 w-3 animate-spin" />
-                        Resending...
+                        {t('otp.resending')}
                       </span>
                     ) : (
-                      "Resend"
+                      t('otp.resend')
                     )}
                   </button>
                 </p>
@@ -236,7 +238,7 @@ const OTP = () => {
                   className="inline-flex items-center gap-2 text-slate-400 hover:text-slate-300 text-sm"
                 >
                   <ArrowLeft className="w-4 h-4" />
-                  Back to Sign In
+                  {t('otp.backToSignIn')}
                 </button>
               </div>
             </div>

+ 27 - 25
shopcall.ai-main/src/pages/Signup.tsx

@@ -5,8 +5,10 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
 import { Phone, Mail, Lock, User, Loader2, AlertCircle } from "lucide-react";
 import { useLocation, useNavigate } from "react-router-dom";
 import { API_URL } from "@/lib/config";
+import { useTranslation } from "react-i18next";
 
 const Signup = () => {
+  const { t } = useTranslation();
   //if we have state and editingEmail: true we will use the state to fill the form
   const state = useLocation().state;
   const [email, setEmail] = useState(state?.form_data?.email || "");
@@ -37,30 +39,30 @@ const Signup = () => {
 
     // Name validation
     if (!name.trim()) {
-      newErrors.name = "Full name is required";
+      newErrors.name = t('signup.errors.nameRequired');
     } else if (name.trim().length < 2) {
-      newErrors.name = "Name must be at least 2 characters";
+      newErrors.name = t('signup.errors.nameMinLength');
     }
 
     // Company validation
     if (!company.trim()) {
-      newErrors.company = "Company name is required";
+      newErrors.company = t('signup.errors.companyRequired');
     } else if (company.trim().length < 2) {
-      newErrors.company = "Company name must be at least 2 characters";
+      newErrors.company = t('signup.errors.companyMinLength');
     }
 
     // Email validation
     if (!email.trim()) {
-      newErrors.email = "Email is required";
+      newErrors.email = t('signup.errors.emailRequired');
     } else if (!validateEmail(email)) {
-      newErrors.email = "Please enter a valid email address";
+      newErrors.email = t('signup.errors.emailInvalid');
     }
 
     // Password validation
     if (!password) {
-      newErrors.password = "Password is required";
+      newErrors.password = t('signup.errors.passwordRequired');
     } else if (!validatePassword(password)) {
-      newErrors.password = "Password must be at least 8 characters";
+      newErrors.password = t('signup.errors.passwordMinLength');
     }
 
     setErrors(newErrors);
@@ -121,8 +123,8 @@ const Signup = () => {
         
       } catch (error) {
         console.error("Signup error:", error);
-        setErrors({ 
-          general: error instanceof Error ? error.message : "An error occurred during signup. Please try again." 
+        setErrors({
+          general: error instanceof Error ? error.message : t('signup.errors.signupFailed')
         });
       } finally {
         setIsLoading(false);
@@ -141,12 +143,12 @@ const Signup = () => {
             <img src="/uploads/e0ddbf09-622c-426a-851f-149776e300c0.png" alt="ShopCall.ai" className="w-10 h-10" />
             <span className="text-2xl font-bold text-white">ShopCall.ai</span>
           </div>
-          <p className="text-slate-400">Create your account to get started</p>
+          <p className="text-slate-400">{t('signup.subtitle')}</p>
         </div>
 
         <Card className="bg-slate-800 border-slate-700">
           <CardHeader>
-            <CardTitle className="text-white text-center">Sign Up</CardTitle>
+            <CardTitle className="text-white text-center">{t('signup.title')}</CardTitle>
           </CardHeader>
           <CardContent>
             <form onSubmit={handleSignup} className="space-y-4">
@@ -158,12 +160,12 @@ const Signup = () => {
               )}
 
               <div className="space-y-2">
-                <label className="text-sm font-medium text-slate-300">Full Name</label>
+                <label className="text-sm font-medium text-slate-300">{t('signup.fullName')}</label>
                 <div className="relative">
                   <User className="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400 w-4 h-4" />
                   <Input
                     type="text"
-                    placeholder="Enter your full name"
+                    placeholder={t('signup.fullNamePlaceholder')}
                     value={name}
                     onChange={(e) => {
                       setName(e.target.value);
@@ -187,12 +189,12 @@ const Signup = () => {
               </div>
 
               <div className="space-y-2">
-                <label className="text-sm font-medium text-slate-300">Company Name</label>
+                <label className="text-sm font-medium text-slate-300">{t('signup.companyName')}</label>
                 <div className="relative">
                   <User className="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400 w-4 h-4" />
                   <Input
                     type="text"
-                    placeholder="Enter your company name"
+                    placeholder={t('signup.companyNamePlaceholder')}
                     value={company}
                     onChange={(e) => {
                       setCompany(e.target.value);
@@ -216,12 +218,12 @@ const Signup = () => {
               </div>
 
               <div className="space-y-2">
-                <label className="text-sm font-medium text-slate-300">Email</label>
+                <label className="text-sm font-medium text-slate-300">{t('signup.email')}</label>
                 <div className="relative">
                   <Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400 w-4 h-4" />
                   <Input
                     type="email"
-                    placeholder="Enter your email"
+                    placeholder={t('signup.emailPlaceholder')}
                     value={email}
                     onChange={(e) => {
                       setEmail(e.target.value);
@@ -245,12 +247,12 @@ const Signup = () => {
               </div>
 
               <div className="space-y-2">
-                <label className="text-sm font-medium text-slate-300">Password</label>
+                <label className="text-sm font-medium text-slate-300">{t('signup.password')}</label>
                 <div className="relative">
                   <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400 w-4 h-4" />
                   <Input
                     type="password"
-                    placeholder="Create a password"
+                    placeholder={t('signup.passwordPlaceholder')}
                     value={password}
                     onChange={(e) => {
                       setPassword(e.target.value);
@@ -272,7 +274,7 @@ const Signup = () => {
                   </div>
                 )}
                 <div className="text-xs text-slate-400">
-                  Password must be at least 8 characters long
+                  {t('signup.passwordRequirement')}
                 </div>
               </div>
 
@@ -284,19 +286,19 @@ const Signup = () => {
                 {isLoading ? (
                   <>
                     <Loader2 className="mr-2 h-4 w-4 animate-spin" />
-                    Creating Account...
+                    {t('signup.creatingAccount')}
                   </>
                 ) : (
-                  "Create Account"
+                  t('signup.createAccount')
                 )}
               </Button>
             </form>
 
             <div className="mt-6 text-center">
               <p className="text-slate-400 text-sm">
-                Already have an account?{" "}
+                {t('signup.alreadyHaveAccount')}{" "}
                 <a href="/login" className="text-cyan-400 hover:text-cyan-300">
-                  Sign in
+                  {t('signup.signIn')}
                 </a>
               </p>
             </div>