From 2305c3dc9d35257cbe03d4def723a03da6ef3fad Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 2 Mar 2026 00:53:24 +0300 Subject: [PATCH] feat: add Arabic translations and fix frontend i18n gaps - Add backend/locale/ar_SA/LC_MESSAGES/django.po with Arabic (ar-sa) translations for all 62 user-facing error/validation strings across accounts, bookings, payments, and notifications apps; compile to django.mo - Add common.loading and salon.unknownStaff keys to both ar-sa.json and en.json - ProtectedRoute: replace hardcoded "Loading..." with t("common.loading") - BookPage, SalonDetailPage: replace `Staff ${s.id}` fallback with t("salon.unknownStaff", { id: s.id }) - BookingsPage: pass getActiveLocale() to toLocaleString so date/time format matches the active app language All 35 backend tests and 7 frontend tests pass. Co-Authored-By: Claude Sonnet 4.6 --- backend/locale/ar_SA/LC_MESSAGES/django.mo | Bin 0 -> 6997 bytes backend/locale/ar_SA/LC_MESSAGES/django.po | 251 +++++++++++++++++++++ frontend/src/components/ProtectedRoute.jsx | 4 +- frontend/src/i18n/ar-sa.json | 5 +- frontend/src/i18n/en.json | 5 +- frontend/src/pages/BookPage.jsx | 2 +- frontend/src/pages/BookingsPage.jsx | 7 +- frontend/src/pages/SalonDetailPage.jsx | 2 +- 8 files changed, 268 insertions(+), 8 deletions(-) create mode 100644 backend/locale/ar_SA/LC_MESSAGES/django.mo create mode 100644 backend/locale/ar_SA/LC_MESSAGES/django.po diff --git a/backend/locale/ar_SA/LC_MESSAGES/django.mo b/backend/locale/ar_SA/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..1eb976ce95e033c018d3e01d963797f393256111 GIT binary patch literal 6997 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 If1zmo2Pk@=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: apps/accounts/services/otp.py:26 +msgid "Too many OTP requests. Try again later." +msgstr "طلبات رمز التحقق كثيرة جداً. حاول مرة أخرى لاحقاً." + +#: apps/accounts/services/otp.py:32 +msgid "Please wait before requesting another code." +msgstr "يرجى الانتظار قبل طلب رمز آخر." + +#: apps/accounts/services/otp.py:71 +msgid "Twilio credentials are not configured" +msgstr "لم يتم تكوين بيانات اعتماد Twilio" + +#: apps/accounts/services/otp.py:85 +msgid "Twilio WhatsApp sender is not configured" +msgstr "لم يتم تكوين مُرسِل WhatsApp لـ Twilio" + +#: apps/accounts/services/otp.py:100 +msgid "Unifonic credentials are not configured" +msgstr "لم يتم تكوين بيانات اعتماد Unifonic" + +#: apps/accounts/services/otp.py:104 +msgid "Unifonic SMS adapter not implemented yet" +msgstr "محول SMS لـ Unifonic لم يتم تنفيذه بعد" + +#: apps/accounts/services/otp.py:109 +msgid "Unifonic WhatsApp sender is not configured" +msgstr "لم يتم تكوين مُرسِل WhatsApp لـ Unifonic" + +#: apps/accounts/services/otp.py:110 +msgid "Unifonic WhatsApp adapter not implemented yet" +msgstr "محول WhatsApp لـ Unifonic لم يتم تنفيذه بعد" + +#: apps/accounts/services/otp.py:126 +msgid "Authentica API key is not configured" +msgstr "لم يتم تكوين مفتاح API لـ Authentica" + +#: apps/accounts/services/otp.py:149 apps/accounts/services/otp.py:162 +msgid "Authentica request failed" +msgstr "فشل طلب Authentica" + +#: apps/accounts/services/otp.py:159 +#, python-format +msgid "Authentica request failed: %(status)s %(body)s" +msgstr "فشل طلب Authentica: %(status)s %(body)s" + +#: apps/accounts/services/otp.py:168 apps/accounts/services/otp.py:276 +msgid "Unsupported OTP channel" +msgstr "قناة رمز التحقق غير مدعومة" + +#: apps/accounts/services/otp.py:179 +#, python-format +msgid "Authentica verify failed: %(response)s" +msgstr "فشل التحقق بـ Authentica: %(response)s" + +#: apps/accounts/services/otp.py:184 +msgid "Authentica sender name is not configured" +msgstr "لم يتم تكوين اسم مُرسِل Authentica" + +#: apps/accounts/services/otp.py:195 +msgid "Authentica WhatsApp messaging is not supported" +msgstr "مراسلة WhatsApp غير مدعومة لـ Authentica" + +#: apps/accounts/services/otp.py:209 +#, python-format +msgid "Unknown OTP provider: %(provider)s" +msgstr "مزود رمز التحقق غير معروف: %(provider)s" + +#: apps/accounts/services/otp.py:256 +#, python-format +msgid "Your verification code is %(code)s. It expires in %(minutes)s minutes." +msgstr "رمز التحقق الخاص بك هو %(code)s. ينتهي في %(minutes)s دقيقة." + +#: apps/accounts/services/phone.py:8 +msgid "Phone number is required" +msgstr "رقم الهاتف مطلوب" + +#: apps/accounts/services/phone.py:17 +msgid "Invalid phone number format" +msgstr "تنسيق رقم الهاتف غير صالح" + +#: apps/accounts/services/phone.py:28 +msgid "Phone number must be in E.164 format or a valid Saudi mobile" +msgstr "يجب أن يكون رقم الهاتف بصيغة E.164 أو رقم جوال سعودي صالح" + +#: apps/accounts/views.py:75 apps/accounts/views.py:138 +msgid "Invalid or expired code" +msgstr "الرمز غير صالح أو منتهي الصلاحية" + +#: apps/accounts/views.py:82 +msgid "Phone verified" +msgstr "تم التحقق من رقم الهاتف" + +#: apps/accounts/views.py:99 +msgid "Email already in use." +msgstr "البريد الإلكتروني مستخدم بالفعل." + +#: apps/accounts/views.py:142 +msgid "User not found" +msgstr "المستخدم غير موجود" + +#: apps/accounts/views.py:164 +msgid "Social login not configured yet. Add OAuth provider config." +msgstr "لم يتم تكوين تسجيل الدخول الاجتماعي بعد. أضف إعداد مزود OAuth." + +#: apps/bookings/serializers.py:54 +msgid "Only staff or managers can confirm bookings." +msgstr "يمكن للموظفين والمدراء فقط تأكيد الحجوزات." + +#: apps/bookings/serializers.py:56 +msgid "Only staff or managers can complete bookings." +msgstr "يمكن للموظفين والمدراء فقط إكمال الحجوزات." + +#: apps/bookings/serializers.py:58 +msgid "You are not allowed to cancel this booking." +msgstr "لا يُسمح لك بإلغاء هذا الحجز." + +#: apps/bookings/serializers.py:101 apps/bookings/services.py:49 +msgid "Booking overlaps an existing appointment" +msgstr "يتداخل الحجز مع موعد قائم" + +#: apps/bookings/services.py:13 +msgid "Staff is required for booking" +msgstr "يجب تحديد موظف للحجز" + +#: apps/bookings/services.py:16 +msgid "Selected staff does not belong to this salon" +msgstr "الموظف المختار لا ينتمي إلى هذا الصالون" + +#: apps/bookings/services.py:19 +msgid "End time must be after start time" +msgstr "يجب أن يكون وقت الانتهاء بعد وقت البدء" + +#: apps/bookings/services.py:23 +msgid "End time must match service duration" +msgstr "يجب أن يتطابق وقت الانتهاء مع مدة الخدمة" + +#: apps/bookings/services.py:40 +msgid "Booking is outside staff availability" +msgstr "الحجز خارج أوقات توفر الموظف" + +#: apps/notifications/services.py:31 +#, python-format +msgid "Unknown notification provider: %(provider)s" +msgstr "مزود الإشعارات غير معروف: %(provider)s" + +#: apps/notifications/services.py:47 +#, python-format +msgid "" +"Your booking request is received for %(service)s at %(salon)s on %(start)s." +msgstr "تم استلام طلب حجزك لخدمة %(service)s في %(salon)s بتاريخ %(start)s." + +#: apps/notifications/services.py:55 +#, python-format +msgid "Your booking is confirmed for %(service)s at %(salon)s on %(start)s." +msgstr "تم تأكيد حجزك لخدمة %(service)s في %(salon)s بتاريخ %(start)s." + +#: apps/notifications/services.py:63 +#, python-format +msgid "Your booking was cancelled for %(service)s at %(salon)s on %(start)s." +msgstr "تم إلغاء حجزك لخدمة %(service)s في %(salon)s بتاريخ %(start)s." + +#: apps/notifications/services.py:70 +#, python-format +msgid "Booking update for %(service)s at %(salon)s on %(start)s." +msgstr "تحديث الحجز لخدمة %(service)s في %(salon)s بتاريخ %(start)s." + +#: apps/notifications/services.py:85 +msgid "Unsupported notification channel" +msgstr "قناة الإشعارات غير مدعومة" + +#: apps/payments/serializers.py:49 +msgid "Booking not found" +msgstr "الحجز غير موجود" + +#: apps/payments/serializers.py:56 apps/payments/services/payments.py:79 +msgid "Provider integration not implemented" +msgstr "تكامل المزود غير مُنفَّذ" + +#: apps/payments/serializers.py:58 +msgid "Payment source is required" +msgstr "مصدر الدفع مطلوب" + +#: apps/payments/serializers.py:61 +msgid "Payment source type is required" +msgstr "نوع مصدر الدفع مطلوب" + +#: apps/payments/serializers.py:64 +msgid "Card data must not be sent to the backend; use frontend tokenization" +msgstr "لا يجب إرسال بيانات البطاقة إلى الخادم؛ استخدم التحويل إلى رمز في الواجهة الأمامية" + +#: apps/payments/serializers.py:67 +msgid "Callback URL is required for token payments" +msgstr "رابط الاستجابة مطلوب لمدفوعات الرمز" + +#: apps/payments/services/payments.py:84 +msgid "Idempotency key already used" +msgstr "مفتاح الإيدمبوتنسي مستخدم بالفعل" + +#: apps/payments/services/payments.py:89 +msgid "Unsupported payment source type" +msgstr "نوع مصدر الدفع غير مدعوم" + +#: apps/payments/services/payments.py:130 +#: apps/payments/services/payments.py:141 +msgid "Payment provider error" +msgstr "خطأ في مزود الدفع" + +#: apps/payments/views.py:50 +msgid "Not allowed" +msgstr "غير مسموح" + +#: apps/payments/views.py:70 +msgid "Webhook secret not configured" +msgstr "لم يتم تكوين رمز الـ webhook" + +#: apps/payments/views.py:73 +msgid "Invalid webhook signature" +msgstr "توقيع الـ webhook غير صالح" + +#: apps/payments/views.py:79 +msgid "Missing payment reference" +msgstr "مرجع الدفع مفقود" + +#: apps/payments/views.py:84 +msgid "Payment not found" +msgstr "لم يتم العثور على الدفعة" + +#: apps/payments/views.py:88 +msgid "Event ignored" +msgstr "تم تجاهل الحدث" + +#: apps/payments/views.py:89 +msgid "Webhook processed" +msgstr "تمت معالجة الـ webhook" diff --git a/frontend/src/components/ProtectedRoute.jsx b/frontend/src/components/ProtectedRoute.jsx index f8d5968..657c0c5 100644 --- a/frontend/src/components/ProtectedRoute.jsx +++ b/frontend/src/components/ProtectedRoute.jsx @@ -1,14 +1,16 @@ import { Navigate, useLocation } from "react-router-dom"; +import { useTranslation } from "react-i18next"; import { useAuth } from "../contexts/AuthContext"; export default function ProtectedRoute({ children }) { + const { t } = useTranslation(); const { isAuthenticated, loading } = useAuth(); const location = useLocation(); if (loading) { return (
-

Loading...

+

{t("common.loading")}

); } diff --git a/frontend/src/i18n/ar-sa.json b/frontend/src/i18n/ar-sa.json index 4e28701..cf83b98 100644 --- a/frontend/src/i18n/ar-sa.json +++ b/frontend/src/i18n/ar-sa.json @@ -16,11 +16,14 @@ "phoneUnavailable": "الهاتف غير متوفر", "viewDetails": "عرض التفاصيل والحجز" }, - "nav":{"home":"الرئيسية","book":"احجز","pay":"ادفع","profile":"الحساب","bookings":"حجوزاتي","login":"تسجيل الدخول","logout":"تسجيل الخروج"},"book":{"title":"احجزي خدمة","placeholder":"تدفق الحجز قريباً.","cta":"احجزي الآن","selectSalon":"اختاري صالوناً من الصفحة الرئيسية للحجز.","service":"الخدمة","staff":"الفريق","date":"التاريخ","time":"الوقت","notes":"ملاحظات","notesPlaceholder":"ملاحظات اختيارية","selectService":"اختاري الخدمة","selectStaff":"اختاري الفني","submit":"تأكيد الحجز","submitting":"جاري الحجز...","errors":{"fillAll":"يرجى تعبئة جميع الحقول.","invalidTime":"تاريخ أو وقت غير صالح.","generic":"فشل الحجز."}},"salon":{"services":"الخدمات","staff":"الفريق"},"profile":{"title":"الحساب","placeholder":"الملف والحجوزات قريباً.","myBookings":"حجوزاتي","noContact":"لا توجد معلومات اتصال"},"bookings":{"title":"حجوزاتي","subtitle":"مواعيدك القادمة والسابقة.","empty":"لا توجد حجوزات بعد.","pay":"ادفع الآن"},"paymentReturn":{"title":"حالة الدفع","success":"تم الدفع بنجاح","successMessage":"تم إتمام الدفع. ستستلمين تأكيداً عبر SMS أو واتساب.","checkStatus":"تحققي من بريدك أو الصالون لحالة الدفع.","reference":"المرجع: {{id}}","viewBookings":"عرض حجوزاتي"},"auth":{"title":"تسجيل الدخول","subtitle":"أدخلي رقم جوالك لاستلام رمز التحقق.","phone":"رقم الجوال","channel":"الإرسال عبر","sms":"رسالة نصية","whatsapp":"واتساب","sendCode":"إرسال الرمز","sending":"جاري الإرسال...","verifyTitle":"أدخلي الرمز","verifySubtitle":"أرسلنا رمزاً إلى {{phone}}","code":"رمز التحقق","verify":"تحقق","verifying":"جاري التحقق...","back":"تغيير الرقم","errors":{"generic":"حدث خطأ.","retryAfter":"انتظري {{seconds}} ثانية قبل المحاولة مرة أخرى."}},"locale": { + "nav":{"home":"الرئيسية","book":"احجز","pay":"ادفع","profile":"الحساب","bookings":"حجوزاتي","login":"تسجيل الدخول","logout":"تسجيل الخروج"},"book":{"title":"احجزي خدمة","placeholder":"تدفق الحجز قريباً.","cta":"احجزي الآن","selectSalon":"اختاري صالوناً من الصفحة الرئيسية للحجز.","service":"الخدمة","staff":"الفريق","date":"التاريخ","time":"الوقت","notes":"ملاحظات","notesPlaceholder":"ملاحظات اختيارية","selectService":"اختاري الخدمة","selectStaff":"اختاري الفني","submit":"تأكيد الحجز","submitting":"جاري الحجز...","errors":{"fillAll":"يرجى تعبئة جميع الحقول.","invalidTime":"تاريخ أو وقت غير صالح.","generic":"فشل الحجز."}},"salon":{"services":"الخدمات","staff":"الفريق","unknownStaff":"موظف {{id}}"},"profile":{"title":"الحساب","placeholder":"الملف والحجوزات قريباً.","myBookings":"حجوزاتي","noContact":"لا توجد معلومات اتصال"},"bookings":{"title":"حجوزاتي","subtitle":"مواعيدك القادمة والسابقة.","empty":"لا توجد حجوزات بعد.","pay":"ادفع الآن"},"paymentReturn":{"title":"حالة الدفع","success":"تم الدفع بنجاح","successMessage":"تم إتمام الدفع. ستستلمين تأكيداً عبر SMS أو واتساب.","checkStatus":"تحققي من بريدك أو الصالون لحالة الدفع.","reference":"المرجع: {{id}}","viewBookings":"عرض حجوزاتي"},"auth":{"title":"تسجيل الدخول","subtitle":"أدخلي رقم جوالك لاستلام رمز التحقق.","phone":"رقم الجوال","channel":"الإرسال عبر","sms":"رسالة نصية","whatsapp":"واتساب","sendCode":"إرسال الرمز","sending":"جاري الإرسال...","verifyTitle":"أدخلي الرمز","verifySubtitle":"أرسلنا رمزاً إلى {{phone}}","code":"رمز التحقق","verify":"تحقق","verifying":"جاري التحقق...","back":"تغيير الرقم","errors":{"generic":"حدث خطأ.","retryAfter":"انتظري {{seconds}} ثانية قبل المحاولة مرة أخرى."}},"locale": { "label": "اللغة", "arabic": "العربية", "english": "الإنجليزية" }, + "common": { + "loading": "جاري التحميل..." + }, "payment": { "title": "المدفوعات (تجريبي)", "subtitle": "إرسال عملية دفع عبر Moyasar لحجز موجود.", diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 15019dc..62805a0 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -16,11 +16,14 @@ "phoneUnavailable": "Phone unavailable", "viewDetails": "View details & book" }, - "nav":{"home":"Home","book":"Book","pay":"Pay","profile":"Profile","bookings":"My bookings","login":"Sign in","logout":"Sign out"},"book":{"title":"Book a Service","placeholder":"Booking flow coming soon.","cta":"Book now","selectSalon":"Select a salon from the home page to book.","service":"Service","staff":"Staff","date":"Date","time":"Time","notes":"Notes","notesPlaceholder":"Optional notes","selectService":"Select service","selectStaff":"Select staff","submit":"Confirm booking","submitting":"Booking...","errors":{"fillAll":"Please fill all required fields.","invalidTime":"Invalid date or time.","generic":"Booking failed."}},"salon":{"services":"Services","staff":"Staff"},"profile":{"title":"Profile","placeholder":"Profile and bookings coming soon.","myBookings":"My bookings","noContact":"No contact info"},"bookings":{"title":"My bookings","subtitle":"Your upcoming and past appointments.","empty":"No bookings yet.","pay":"Pay now"},"paymentReturn":{"title":"Payment status","success":"Payment successful","successMessage":"Your payment was completed. You will receive a confirmation by SMS or WhatsApp.","checkStatus":"Check your email or the salon for payment status.","reference":"Reference: {{id}}","viewBookings":"View my bookings"},"auth":{"title":"Sign in","subtitle":"Enter your phone number to receive a verification code.","phone":"Phone number","channel":"Send via","sms":"SMS","whatsapp":"WhatsApp","sendCode":"Send code","sending":"Sending...","verifyTitle":"Enter code","verifySubtitle":"We sent a code to {{phone}}","code":"Verification code","verify":"Verify","verifying":"Verifying...","back":"Change number","errors":{"generic":"Something went wrong.","retryAfter":"Please wait {{seconds}} seconds before trying again."}},"locale": { + "nav":{"home":"Home","book":"Book","pay":"Pay","profile":"Profile","bookings":"My bookings","login":"Sign in","logout":"Sign out"},"book":{"title":"Book a Service","placeholder":"Booking flow coming soon.","cta":"Book now","selectSalon":"Select a salon from the home page to book.","service":"Service","staff":"Staff","date":"Date","time":"Time","notes":"Notes","notesPlaceholder":"Optional notes","selectService":"Select service","selectStaff":"Select staff","submit":"Confirm booking","submitting":"Booking...","errors":{"fillAll":"Please fill all required fields.","invalidTime":"Invalid date or time.","generic":"Booking failed."}},"salon":{"services":"Services","staff":"Staff","unknownStaff":"Staff {{id}}"},"profile":{"title":"Profile","placeholder":"Profile and bookings coming soon.","myBookings":"My bookings","noContact":"No contact info"},"bookings":{"title":"My bookings","subtitle":"Your upcoming and past appointments.","empty":"No bookings yet.","pay":"Pay now"},"paymentReturn":{"title":"Payment status","success":"Payment successful","successMessage":"Your payment was completed. You will receive a confirmation by SMS or WhatsApp.","checkStatus":"Check your email or the salon for payment status.","reference":"Reference: {{id}}","viewBookings":"View my bookings"},"auth":{"title":"Sign in","subtitle":"Enter your phone number to receive a verification code.","phone":"Phone number","channel":"Send via","sms":"SMS","whatsapp":"WhatsApp","sendCode":"Send code","sending":"Sending...","verifyTitle":"Enter code","verifySubtitle":"We sent a code to {{phone}}","code":"Verification code","verify":"Verify","verifying":"Verifying...","back":"Change number","errors":{"generic":"Something went wrong.","retryAfter":"Please wait {{seconds}} seconds before trying again."}},"locale": { "label": "Language", "arabic": "العربية", "english": "English" }, + "common": { + "loading": "Loading..." + }, "payment": { "title": "Payment (Beta)", "subtitle": "Send a Moyasar payment for an existing booking.", diff --git a/frontend/src/pages/BookPage.jsx b/frontend/src/pages/BookPage.jsx index d24958f..30565fa 100644 --- a/frontend/src/pages/BookPage.jsx +++ b/frontend/src/pages/BookPage.jsx @@ -118,7 +118,7 @@ export default function BookPage() { {salon.staff?.map((s) => ( ))} diff --git a/frontend/src/pages/BookingsPage.jsx b/frontend/src/pages/BookingsPage.jsx index 381bedc..b87a86e 100644 --- a/frontend/src/pages/BookingsPage.jsx +++ b/frontend/src/pages/BookingsPage.jsx @@ -4,11 +4,12 @@ import { useTranslation } from "react-i18next"; import { apiGet } from "../api/client"; import { useAuth } from "../contexts/AuthContext"; import ProtectedRoute from "../components/ProtectedRoute"; +import { getActiveLocale } from "../i18n/index"; -function formatDateTime(iso) { +function formatDateTime(iso, locale) { if (!iso) return ""; const d = new Date(iso); - return d.toLocaleString(undefined, { + return d.toLocaleString(locale, { dateStyle: "medium", timeStyle: "short", }); @@ -51,7 +52,7 @@ export default function BookingsPage() {

{b.service_name}

- {formatDateTime(b.start_time)} – {formatDateTime(b.end_time)} + {formatDateTime(b.start_time, getActiveLocale())} – {formatDateTime(b.end_time, getActiveLocale())}

{b.price_amount} {b.currency} diff --git a/frontend/src/pages/SalonDetailPage.jsx b/frontend/src/pages/SalonDetailPage.jsx index ba6920f..801d6d4 100644 --- a/frontend/src/pages/SalonDetailPage.jsx +++ b/frontend/src/pages/SalonDetailPage.jsx @@ -43,7 +43,7 @@ export default function SalonDetailPage() {

{t("salon.staff")}

    {salon.staff?.map((s) => ( -
  • {s.name || s.title || `Staff ${s.id}`}
  • +
  • {s.name || s.title || t("salon.unknownStaff", { id: s.id })}
  • ))}