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 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,251 @@
|
||||
# Arabic (Saudi Arabia) translations for Salon booking platform.
|
||||
# Copyright (C) 2026
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 1.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-02 00:45+0300\n"
|
||||
"PO-Revision-Date: 2026-03-02 00:45+0300\n"
|
||||
"Last-Translator: Claude\n"
|
||||
"Language-Team: Arabic (Saudi Arabia)\n"
|
||||
"Language: ar_SA\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=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"
|
||||
@@ -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 (
|
||||
<div className="auth-loading">
|
||||
<p>Loading...</p>
|
||||
<p>{t("common.loading")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 لحجز موجود.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -118,7 +118,7 @@ export default function BookPage() {
|
||||
<option value="">{t("book.selectStaff")}</option>
|
||||
{salon.staff?.map((s) => (
|
||||
<option key={s.id} value={s.id}>
|
||||
{s.name || s.title || `Staff ${s.id}`}
|
||||
{s.name || s.title || t("salon.unknownStaff", { id: s.id })}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -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() {
|
||||
</div>
|
||||
<p className="booking-service">{b.service_name}</p>
|
||||
<p className="booking-time">
|
||||
{formatDateTime(b.start_time)} – {formatDateTime(b.end_time)}
|
||||
{formatDateTime(b.start_time, getActiveLocale())} – {formatDateTime(b.end_time, getActiveLocale())}
|
||||
</p>
|
||||
<p className="booking-price">
|
||||
{b.price_amount} {b.currency}
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function SalonDetailPage() {
|
||||
<h2>{t("salon.staff")}</h2>
|
||||
<ul className="staff-list">
|
||||
{salon.staff?.map((s) => (
|
||||
<li key={s.id}>{s.name || s.title || `Staff ${s.id}`}</li>
|
||||
<li key={s.id}>{s.name || s.title || t("salon.unknownStaff", { id: s.id })}</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user