fix languages

This commit is contained in:
root
2026-05-22 02:20:20 -04:00
parent cc462e2c01
commit 191fb3cb4e
25 changed files with 134 additions and 126 deletions
@@ -94,12 +94,12 @@ export default function AdminSiteConfigPage() {
}, },
fr: { fr: {
title: 'Configuration du site', title: 'Configuration du site',
description: 'Modifiez ici la homepage principale de la marketplace, ou ouvrez une entreprise pour gérer sa homepage publique et son menu.', description: 'Modifiez ici la page daccueil principale de la marketplace, ou ouvrez une entreprise pour gérer sa page publique et son menu.',
homepageTitle: 'Homepage du site principal', homepageTitle: 'Page daccueil du site principal',
homepageDescription: 'Cette section contrôle la homepage marketplace affichée sur le site principal RentalDriveGo.', homepageDescription: 'Cette section contrôle la page daccueil marketplace affichée sur le site principal de RentalDriveGo.',
saveHomepage: 'Enregistrer la homepage', saveHomepage: 'Enregistrer la page daccueil',
savingHomepage: 'Enregistrement…', savingHomepage: 'Enregistrement…',
homepageSaved: 'Homepage marketplace enregistrée.', homepageSaved: 'Page daccueil marketplace enregistrée.',
search: 'Rechercher par entreprise ou slug…', search: 'Rechercher par entreprise ou slug…',
loading: 'Chargement…', loading: 'Chargement…',
empty: 'Aucune entreprise trouvée', empty: 'Aucune entreprise trouvée',
@@ -111,7 +111,7 @@ export default function AdminSiteConfigPage() {
manageCompany: 'Ouvrir lentreprise', manageCompany: 'Ouvrir lentreprise',
companiesTitle: 'Sites de marque des entreprises', companiesTitle: 'Sites de marque des entreprises',
languageLabel: 'Langue', languageLabel: 'Langue',
hero: 'Hero', hero: 'Bloc principal',
surface: 'Bloc marketplace', surface: 'Bloc marketplace',
companySection: 'Bloc opérateur', companySection: 'Bloc opérateur',
renterSection: 'Bloc client', renterSection: 'Bloc client',
@@ -121,8 +121,8 @@ export default function AdminSiteConfigPage() {
closingSection: 'Appel à laction final', closingSection: 'Appel à laction final',
previewTitle: 'Aperçu en direct', previewTitle: 'Aperçu en direct',
previewDescription: 'Les brouillons apparaissent ici avant lenregistrement.', previewDescription: 'Les brouillons apparaissent ici avant lenregistrement.',
homepageSections: 'Sections de la homepage', homepageSections: 'Sections de la page daccueil',
homepageSectionsDescription: 'Ajoutez ou retirez des blocs de la homepage marketplace principale.', homepageSectionsDescription: 'Ajoutez ou retirez des blocs de la page daccueil marketplace principale.',
addSection: 'Ajouter', addSection: 'Ajouter',
removeSection: 'Retirer', removeSection: 'Retirer',
expand: 'Ouvrir', expand: 'Ouvrir',
+1 -1
View File
@@ -20,7 +20,7 @@ export default function PublicFooter() {
preferences: 'Admin preferences', preferences: 'Admin preferences',
}, },
fr: { fr: {
preferences: 'Preferences admin', preferences: 'Préférences dadministration',
}, },
ar: { ar: {
preferences: 'تفضيلات الإدارة', preferences: 'تفضيلات الإدارة',
+24 -24
View File
@@ -99,11 +99,11 @@
"closing" "closing"
], ],
"heroKicker": "RentalDriveGo", "heroKicker": "RentalDriveGo",
"heroTitle": "Une vitrine marketplace plus nette et plus forte.", "heroTitle": "Une vitrine marketplace plus claire et plus percutante.",
"heroBody": "Les entreprises de location gèrent leurs opérations en privé, les clients parcourent une marketplace commune, et chaque réservation se termine sur le paiement de marque de la société.", "heroBody": "Les entreprises de location gèrent leurs opérations en privé, les clients parcourent une marketplace commune et chaque réservation se finalise sur le parcours de paiement aux couleurs de l'entreprise.",
"startTrial": "Commencer lessai gratuit", "startTrial": "Commencer lessai gratuit",
"exploreVehicles": "Explorer les véhicules", "exploreVehicles": "Explorer les véhicules",
"surfaceLabel": "Surface marketplace", "surfaceLabel": "Vitrine marketplace",
"surfaceTitle": "Pensée pour deux audiences à la fois.", "surfaceTitle": "Pensée pour deux audiences à la fois.",
"surfaceBody": "Les opérateurs veulent du contrôle, les clients veulent de la confiance. La marketplace doit montrer les deux sans ressembler à un modèle générique.", "surfaceBody": "Les opérateurs veulent du contrôle, les clients veulent de la confiance. La marketplace doit montrer les deux sans ressembler à un modèle générique.",
"liveLabel": "Réseau actif", "liveLabel": "Réseau actif",
@@ -111,15 +111,15 @@
"brandedFlows": "Parcours de marque", "brandedFlows": "Parcours de marque",
"multiTenant": "Opérations multi-tenant", "multiTenant": "Opérations multi-tenant",
"companyKicker": "Flux opérateur", "companyKicker": "Flux opérateur",
"companyTitle": "Pilotez inventaire, offres, facturation et équipe depuis une seule couche de commande.", "companyTitle": "Pilotez linventaire, les offres, la facturation et léquipe depuis un seul centre de contrôle.",
"companyBody": "Publiez une seule fois, choisissez ce qui apparaît publiquement, et gardez contrats, paiements et rapports isolés par entreprise.", "companyBody": "Publiez une seule fois, choisissez ce qui apparaît publiquement, et gardez contrats, paiements et rapports isolés par entreprise.",
"renterKicker": "Expérience client", "renterKicker": "Expérience client",
"renterTitle": "Laissez les clients comparer rapidement puis dirigez-les vers le bon site entreprise.", "renterTitle": "Laissez les clients comparer rapidement, puis redirigez-les vers le bon site dentreprise.",
"renterBody": "La marketplace agit comme moteur de découverte, pas comme agrégateur sans suite. Recherche ici, réservation là-bas, paiement direct.", "renterBody": "La marketplace sert de moteur de découverte, pas dagrégateur sans suite. Recherche ici, réservation là-bas, paiement direct.",
"pillars": [ "pillars": [
{ {
"title": "Publication unifiée", "title": "Publication unifiée",
"body": "Les photos, tarifs et offres circulent depuis un seul flux admin vers la découverte marketplace et les pages de réservation de marque." "body": "Les photos, tarifs et offres circulent depuis un seul flux dadministration vers la découverte marketplace et les pages de réservation de marque."
}, },
{ {
"title": "Découverte qualifiée", "title": "Découverte qualifiée",
@@ -127,7 +127,7 @@
}, },
{ {
"title": "Revenus en direct", "title": "Revenus en direct",
"body": "Les réservations basculent vers le paiement propre à lentreprise, pour garder la relation client et lencaissement." "body": "Les réservations basculent vers le paiement propre à lentreprise afin de préserver la relation client et lencaissement."
} }
], ],
"metrics": [ "metrics": [
@@ -141,12 +141,12 @@
}, },
{ {
"value": "03", "value": "03",
"label": "Paiements détenus par la société" "label": "Paiements gérés par lentreprise"
} }
], ],
"featureLabel": "Ce que couvre le produit", "featureLabel": "Ce que couvre le produit",
"features": [ "features": [
"Gestion de flotte avec téléversement multi-photos", "Gestion de flotte avec téléversement de plusieurs photos",
"Offres marketplace et redirection vers la réservation", "Offres marketplace et redirection vers la réservation",
"Site public de réservation par entreprise", "Site public de réservation par entreprise",
"CRM client, analytics et contrôle de facturation" "CRM client, analytics et contrôle de facturation"
@@ -171,8 +171,8 @@
], ],
"stepLabel": "Étape", "stepLabel": "Étape",
"readyKicker": "Prêt à démarrer", "readyKicker": "Prêt à démarrer",
"readyTitle": "Une homepage marketplace doit vendre le système en quelques secondes.", "readyTitle": "La page daccueil marketplace doit présenter le produit en quelques secondes.",
"readyBody": "Utilisez la marketplace pour capter la demande, puis faites passer les clients vers une expérience de marque qui garde prix, paiements et confiance liés à votre entreprise.", "readyBody": "Utilisez la marketplace pour capter la demande, puis orientez les clients vers une expérience de marque où les prix, les paiements et la confiance restent attachés à votre entreprise.",
"viewPricing": "Voir les tarifs", "viewPricing": "Voir les tarifs",
"createWorkspace": "Créer lespace" "createWorkspace": "Créer lespace"
}, },
@@ -187,8 +187,8 @@
"closing" "closing"
], ],
"heroKicker": "RentalDriveGo", "heroKicker": "RentalDriveGo",
"heroTitle": "واجهة سوق أوضح وأقوى.", "heroTitle": "واجهة سوق أكثر وضوحاً وتأثيراً.",
"heroBody": "شركات التأجير تدير عملياتها بشكل خاص، والمستأجرون يتصفحون سوقاً مشتركاً، وكل حجز ينتهي في صفحة دفع تحمل هوية الشركة نفسها.", "heroBody": "شركات التأجير تدير عملياتها بشكل خاص، والمستأجرون يتصفحون سوقاً مشتركاً، وكل حجز ينتهي في صفحة دفع تحمل هوية الشركة ذاتها.",
"startTrial": "ابدأ التجربة المجانية", "startTrial": "ابدأ التجربة المجانية",
"exploreVehicles": "استكشف السيارات", "exploreVehicles": "استكشف السيارات",
"surfaceLabel": "واجهة السوق", "surfaceLabel": "واجهة السوق",
@@ -198,16 +198,16 @@
"trustedFleets": "أساطيل موثوقة", "trustedFleets": "أساطيل موثوقة",
"brandedFlows": "مسارات حجز مخصصة", "brandedFlows": "مسارات حجز مخصصة",
"multiTenant": "عمليات متعددة الشركات", "multiTenant": "عمليات متعددة الشركات",
"companyKicker": "تدفق المشغل", "companyKicker": "سير عمل المشغل",
"companyTitle": "تحكم في المخزون والعروض والفوترة والفريق من طبقة تشغيل واحدة.", "companyTitle": "تحكم في المخزون والعروض والفوترة والفريق من طبقة تشغيل واحدة.",
"companyBody": "انشر المخزون مرة واحدة، وحدد ما يظهر للعامة، واحتفظ بالعقود والمدفوعات والتقارير معزولة لكل شركة.", "companyBody": "انشر المخزون مرة واحدة، وحدد ما يظهر للعامة، واحتفظ بالعقود والمدفوعات والتقارير معزولة لكل شركة.",
"renterKicker": "تجربة المستأجر", "renterKicker": "تجربة المستأجر",
"renterTitle": "دع المستأجر يقارن بسرعة ثم انقله إلى موقع الشركة المناسب.", "renterTitle": "دع المستأجرين يقارنون بسرعة ثم وجّههم إلى موقع الشركة المناسب.",
"renterBody": "السوق هنا محرك اكتشاف وليس مجمعاً بلا نهاية. البحث هنا، الحجز هناك، والدفع مباشرة للشركة.", "renterBody": "هذه المنصة محرك لاكتشاف الخيارات، وليست مُجمِّعاً بلا مسار واضح. ابحث هنا، احجز هناك، وادفع مباشرة للشركة.",
"pillars": [ "pillars": [
{ {
"title": "نشر موحد", "title": "نشر موحد",
"body": "صور السيارات والأسعار والعروض تنتقل من تدفق إدارة واحد إلى السوق وصفحات الحجز ذات الهوية الخاصة." "body": "صور السيارات والأسعار والعروض تنتقل من سير إدارة واحد إلى السوق وصفحات الحجز ذات الهوية الخاصة."
}, },
{ {
"title": "اكتشاف مؤهل", "title": "اكتشاف مؤهل",
@@ -215,7 +215,7 @@
}, },
{ {
"title": "مسار إيراد مباشر", "title": "مسار إيراد مباشر",
"body": "تنتقل الحجوزات إلى صفحة الدفع الخاصة بالشركة حتى تبقى الملكية المالية وعلاقة العميل مع النشاط نفسه." "body": "تنتقل الحجوزات إلى صفحة الدفع الخاصة بالشركة حتى تبقى علاقة العميل والتحصيل المالي بيد الشركة نفسها."
} }
], ],
"metrics": [ "metrics": [
@@ -232,7 +232,7 @@
"label": "مدفوعات مملوكة للشركة" "label": "مدفوعات مملوكة للشركة"
} }
], ],
"featureLabel": "ما الذي يغطيه المنتج", "featureLabel": "ما الذي يقدمه المنتج",
"features": [ "features": [
"إدارة الأسطول مع رفع عدة صور", "إدارة الأسطول مع رفع عدة صور",
"عروض السوق والتحويل إلى مسار الحجز", "عروض السوق والتحويل إلى مسار الحجز",
@@ -243,8 +243,8 @@
"steps": [ "steps": [
{ {
"step": "1", "step": "1",
"title": "أنشئ مساحة شركتك", "title": "أنشئ مساحة عمل شركتك",
"body": "اختر الخطة وابدأ تجربتك لمدة 14 يوماً ثم تحقق من حساب المالك." "body": "اختر الخطة وابدأ تجربتك لمدة 14 يوماً ثم فعّل حساب المالك."
}, },
{ {
"step": "2", "step": "2",
@@ -260,9 +260,9 @@
"stepLabel": "الخطوة", "stepLabel": "الخطوة",
"readyKicker": "جاهز للانطلاق", "readyKicker": "جاهز للانطلاق",
"readyTitle": "يجب أن تشرح الصفحة الرئيسية قيمة المنصة خلال ثوانٍ.", "readyTitle": "يجب أن تشرح الصفحة الرئيسية قيمة المنصة خلال ثوانٍ.",
"readyBody": "استخدم السوق لجذب الطلب، ثم انقل المستأجرين إلى تجربة تحمل هوية شركتك وتحافظ على التسعير والمدفوعات والثقة داخل نشاطك.", "readyBody": "استخدم السوق لجذب الطلب، ثم انقل المستأجرين إلى تجربة تحمل هوية شركتك، بحيث تبقى الأسعار والمدفوعات والثقة مرتبطة بنشاطك.",
"viewPricing": "عرض الأسعار", "viewPricing": "عرض الأسعار",
"createWorkspace": "إنشاء المساحة" "createWorkspace": "إنشاء المساحة"
} }
} }
} }
+3 -3
View File
@@ -104,7 +104,7 @@ export const resetPasswordEmail = {
<p>مرحباً ${firstName}،</p> <p>مرحباً ${firstName}،</p>
<p>طلبت إعادة تعيين كلمة مرور حساب RentalDriveGo الخاص بك.</p> <p>طلبت إعادة تعيين كلمة مرور حساب RentalDriveGo الخاص بك.</p>
<p><a href="${resetUrl}" style="background:#1d4ed8;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;display:inline-block;font-weight:600;">إعادة تعيين كلمة المرور</a></p> <p><a href="${resetUrl}" style="background:#1d4ed8;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;display:inline-block;font-weight:600;">إعادة تعيين كلمة المرور</a></p>
<p>ينتهي صلاحية هذا الرابط خلال ${expireMinutes} دقيقة. إذا لم تطلب ذلك، يمكنك تجاهل هذا البريد الإلكتروني بأمان.</p> <p>تنتهي صلاحية هذا الرابط خلال ${expireMinutes} دقيقة. إذا لم تطلب ذلك، يمكنك تجاهل هذا البريد الإلكتروني بأمان.</p>
<p>RentalDriveGo</p> <p>RentalDriveGo</p>
</div>`, </div>`,
}, lang) }, lang)
@@ -255,7 +255,7 @@ export const reviewRequestEmail = {
}, },
ar: { ar: {
greeting: `مرحباً ${opts.firstName}،`, greeting: `مرحباً ${opts.firstName}،`,
body1: `شكراً لاستئجارك مع <strong>${opts.companyName}</strong>. نأمل أنك استمتعت بـ <strong>${opts.vehicleLabel}</strong>.`, body1: `شكراً لاختيار <strong>${opts.companyName}</strong>. نأمل أنك استمتعت بـ <strong>${opts.vehicleLabel}</strong>.`,
body2: 'تساعد ملاحظاتك الشركة على التحسين وتساعد العملاء الآخرين على اتخاذ قرارات مستنيرة. لا يستغرق الأمر سوى 30 ثانية.', body2: 'تساعد ملاحظاتك الشركة على التحسين وتساعد العملاء الآخرين على اتخاذ قرارات مستنيرة. لا يستغرق الأمر سوى 30 ثانية.',
cta: 'اترك تقييماً', cta: 'اترك تقييماً',
disclaimer: 'هذا الرابط فريد لحجزك ولا يمكن استخدامه إلا مرة واحدة. إذا لم تستأجر مركبة مؤخراً، يمكنك تجاهل هذا البريد الإلكتروني.', disclaimer: 'هذا الرابط فريد لحجزك ولا يمكن استخدامه إلا مرة واحدة. إذا لم تستأجر مركبة مؤخراً، يمكنك تجاهل هذا البريد الإلكتروني.',
@@ -285,6 +285,6 @@ export const reviewRequestEmail = {
}, lang: Lang): string => t({ }, lang: Lang): string => t({
en: `Hi ${opts.firstName},\n\nThank you for renting with ${opts.companyName}. We hope you enjoyed your ${opts.vehicleLabel}.\n\nLeave a review here (one-time link):\n${opts.reviewUrl}\n\nThank you!`, en: `Hi ${opts.firstName},\n\nThank you for renting with ${opts.companyName}. We hope you enjoyed your ${opts.vehicleLabel}.\n\nLeave a review here (one-time link):\n${opts.reviewUrl}\n\nThank you!`,
fr: `Bonjour ${opts.firstName},\n\nMerci d'avoir loué chez ${opts.companyName}. Nous espérons que vous avez apprécié votre ${opts.vehicleLabel}.\n\nLaissez un avis ici (lien unique) :\n${opts.reviewUrl}\n\nMerci !`, fr: `Bonjour ${opts.firstName},\n\nMerci d'avoir loué chez ${opts.companyName}. Nous espérons que vous avez apprécié votre ${opts.vehicleLabel}.\n\nLaissez un avis ici (lien unique) :\n${opts.reviewUrl}\n\nMerci !`,
ar: `مرحباً ${opts.firstName}،\n\nشكراً لاستئجارك مع ${opts.companyName}. نأمل أنك استمتعت بـ ${opts.vehicleLabel}.\n\nاترك تقييماً هنا (رابط فريد):\n${opts.reviewUrl}\n\nشكراً!`, ar: `مرحباً ${opts.firstName}،\n\nشكراً لاختيار ${opts.companyName}. نأمل أنك استمتعت بـ ${opts.vehicleLabel}.\n\nاترك تقييماً هنا (رابط فريد):\n${opts.reviewUrl}\n\nشكراً!`,
}, lang), }, lang),
} }
@@ -3,5 +3,11 @@ export function presentCompany(company: any) {
} }
export function presentBrand(brand: any) { export function presentBrand(brand: any) {
return brand if (!brand) return brand
const { amanpaySecretKey, amanpayMerchantId, paypalEmail, paypalMerchantId, ...safe } = brand
return {
...safe,
amanpayConfigured: !!(amanpayMerchantId && amanpaySecretKey),
paypalConfigured: !!(paypalEmail || paypalMerchantId),
}
} }
@@ -18,7 +18,7 @@ router.post('/webhooks/amanpay', async (req, res, next) => {
try { try {
const rawBody = JSON.stringify(req.body) const rawBody = JSON.stringify(req.body)
const signature = (req.headers['x-amanpay-signature'] as string) ?? '' const signature = (req.headers['x-amanpay-signature'] as string) ?? ''
if (amanpay.isConfigured() && !amanpay.verifyWebhookSignature(rawBody, signature)) { if (!amanpay.isConfigured() || !amanpay.verifyWebhookSignature(rawBody, signature)) {
return res.status(401).json({ error: 'invalid_signature' }) return res.status(401).json({ error: 'invalid_signature' })
} }
await service.handleAmanpayWebhook(req.body) await service.handleAmanpayWebhook(req.body)
@@ -30,7 +30,7 @@ router.post('/webhooks/paypal', async (req, res, next) => {
try { try {
const rawBody = JSON.stringify(req.body) const rawBody = JSON.stringify(req.body)
const isValid = await paypal.verifyWebhookEvent(req.headers as Record<string, string>, rawBody) const isValid = await paypal.verifyWebhookEvent(req.headers as Record<string, string>, rawBody)
if (paypal.isConfigured() && !isValid) return res.status(401).json({ error: 'invalid_signature' }) if (!paypal.isConfigured() || !isValid) return res.status(401).json({ error: 'invalid_signature' })
await service.handlePaypalWebhook(req.body) await service.handlePaypalWebhook(req.body)
res.json({ received: true }) res.json({ received: true })
} catch (err) { next(err) } } catch (err) { next(err) }
@@ -27,7 +27,7 @@ router.post('/webhooks/amanpay', async (req, res, next) => {
try { try {
const rawBody = JSON.stringify(req.body) const rawBody = JSON.stringify(req.body)
const signature = (req.headers['x-amanpay-signature'] as string) ?? '' const signature = (req.headers['x-amanpay-signature'] as string) ?? ''
if (amanpay.isConfigured() && !amanpay.verifyWebhookSignature(rawBody, signature)) { if (!amanpay.isConfigured() || !amanpay.verifyWebhookSignature(rawBody, signature)) {
return res.status(401).json({ error: 'invalid_signature' }) return res.status(401).json({ error: 'invalid_signature' })
} }
await service.handleAmanpayWebhook(req.body) await service.handleAmanpayWebhook(req.body)
@@ -39,7 +39,7 @@ router.post('/webhooks/paypal', async (req, res, next) => {
try { try {
const rawBody = JSON.stringify(req.body) const rawBody = JSON.stringify(req.body)
const isValid = await paypal.verifyWebhookEvent(req.headers as Record<string, string>, rawBody) const isValid = await paypal.verifyWebhookEvent(req.headers as Record<string, string>, rawBody)
if (paypal.isConfigured() && !isValid) return res.status(401).json({ error: 'invalid_signature' }) if (!paypal.isConfigured() || !isValid) return res.status(401).json({ error: 'invalid_signature' })
await service.handlePaypalWebhook(req.body) await service.handlePaypalWebhook(req.body)
res.json({ received: true }) res.json({ received: true })
} catch (err) { next(err) } } catch (err) { next(err) }
@@ -123,7 +123,7 @@ router.delete('/:id/calendar/blocks/:blockId', requireRole('MANAGER'), async (re
router.get('/:id/maintenance', async (req, res, next) => { router.get('/:id/maintenance', async (req, res, next) => {
try { try {
const { id } = parseParams(idParamSchema, req) const { id } = parseParams(idParamSchema, req)
const logs = await service.getMaintenanceLogs(id) const logs = await service.getMaintenanceLogs(id, req.companyId)
ok(res, logs) ok(res, logs)
} catch (err) { next(err) } } catch (err) { next(err) }
}) })
@@ -110,7 +110,9 @@ export async function deleteCalendarBlock(vehicleId: string, companyId: string,
await repo.deleteCalendarBlock(blockId, vehicleId) await repo.deleteCalendarBlock(blockId, vehicleId)
} }
export async function getMaintenanceLogs(id: string) { export async function getMaintenanceLogs(id: string, companyId: string) {
const vehicle = await repo.findById(id, companyId)
if (!vehicle) throw new NotFoundError('Vehicle not found')
return repo.findMaintenanceLogs(id) return repo.findMaintenanceLogs(id)
} }
@@ -641,7 +641,7 @@ export default function ContractDetailPage() {
damageTypeLabels: { SCRATCH: 'Rayure', DENT: 'Bosse', CRACK: 'Fissure', CHIP: 'Éclat', MISSING: 'Manquant', STAIN: 'Tache', OTHER: 'Autre' }, damageTypeLabels: { SCRATCH: 'Rayure', DENT: 'Bosse', CRACK: 'Fissure', CHIP: 'Éclat', MISSING: 'Manquant', STAIN: 'Tache', OTHER: 'Autre' },
severityLabels: { MINOR: 'Léger', MODERATE: 'Modéré', MAJOR: 'Majeur' }, severityLabels: { MINOR: 'Léger', MODERATE: 'Modéré', MAJOR: 'Majeur' },
fuelLevelLabels: { FULL: 'Plein', SEVEN_EIGHTHS: '7/8', THREE_QUARTERS: '3/4', FIVE_EIGHTHS: '5/8', HALF: '1/2', THREE_EIGHTHS: '3/8', QUARTER: '1/4', ONE_EIGHTH: '1/8', EMPTY: 'Vide' }, fuelLevelLabels: { FULL: 'Plein', SEVEN_EIGHTHS: '7/8', THREE_QUARTERS: '3/4', FIVE_EIGHTHS: '5/8', HALF: '1/2', THREE_EIGHTHS: '3/8', QUARTER: '1/4', ONE_EIGHTH: '1/8', EMPTY: 'Vide' },
paymentProviderLabels: { CASH: 'Espèces', STRIPE: 'Stripe', BANK_TRANSFER: 'Virement bancaire', CHECK: 'Chèque', PAYPAL: 'PayPal', CREDIT_CARD: 'Carte crédit', DEBIT_CARD: 'Carte débit' }, paymentProviderLabels: { CASH: 'Espèces', STRIPE: 'Stripe', BANK_TRANSFER: 'Virement bancaire', CHECK: 'Chèque', PAYPAL: 'PayPal', CREDIT_CARD: 'Carte de crédit', DEBIT_CARD: 'Carte de débit' },
}, },
ar: { ar: {
back: 'العودة إلى العقود', back: 'العودة إلى العقود',
@@ -690,7 +690,7 @@ export default function ContractDetailPage() {
noAdditionalDrivers: 'لا يوجد سائقون إضافيون.', noAdditionalDrivers: 'لا يوجد سائقون إضافيون.',
noInsurance: 'لم يتم اختيار تأمين.', noInsurance: 'لم يتم اختيار تأمين.',
noPayments: 'لا توجد مدفوعات مسجلة.', noPayments: 'لا توجد مدفوعات مسجلة.',
loading: 'جار تحميل العقد…', loading: 'جارٍ تحميل العقد…',
yes: 'نعم', yes: 'نعم',
no: 'لا', no: 'لا',
agreementTitle: 'عقد تأجير', agreementTitle: 'عقد تأجير',
@@ -769,8 +769,8 @@ export default function ContractDetailPage() {
contractStatusLabels: { DRAFT: 'مسودة', PENDING: 'قيد الانتظار', ACTIVE: 'نشط', COMPLETED: 'مكتمل', CANCELLED: 'ملغى' }, contractStatusLabels: { DRAFT: 'مسودة', PENDING: 'قيد الانتظار', ACTIVE: 'نشط', COMPLETED: 'مكتمل', CANCELLED: 'ملغى' },
paymentStatusLabels: { UNPAID: 'غير مدفوع', PAID: 'مدفوع', PARTIAL: 'جزئي', REFUNDED: 'مسترد', OVERDUE: 'متأخر', PENDING: 'قيد الانتظار', COMPLETED: 'مكتمل', FAILED: 'فشل' }, paymentStatusLabels: { UNPAID: 'غير مدفوع', PAID: 'مدفوع', PARTIAL: 'جزئي', REFUNDED: 'مسترد', OVERDUE: 'متأخر', PENDING: 'قيد الانتظار', COMPLETED: 'مكتمل', FAILED: 'فشل' },
chargeTypeLabels: { PER_DAY: 'يومي', FLAT: 'مقطوع', FLAT_RATE: 'مقطوع', PER_RENTAL: 'لكل إيجار' }, chargeTypeLabels: { PER_DAY: 'يومي', FLAT: 'مقطوع', FLAT_RATE: 'مقطوع', PER_RENTAL: 'لكل إيجار' },
damageTypeLabels: { SCRATCH: 'خدش', DENT: 'دهس', CRACK: 'تشقق', CHIP: 'تقشر', MISSING: 'مفقود', STAIN: 'بقعة', OTHER: 'أخرى' }, damageTypeLabels: { SCRATCH: 'خدش', DENT: 'انبعاج', CRACK: 'تشقق', CHIP: 'تقشر', MISSING: 'مفقود', STAIN: 'بقعة', OTHER: 'أخرى' },
severityLabels: { MINOR: 'طفيف', MODERATE: 'متوسط', MAJOR: 'خطير' }, severityLabels: { MINOR: 'طفيف', MODERATE: 'متوسط', MAJOR: 'شديد' },
fuelLevelLabels: { FULL: 'ممتلئ', SEVEN_EIGHTHS: '7/8', THREE_QUARTERS: '3/4', FIVE_EIGHTHS: '5/8', HALF: '1/2', THREE_EIGHTHS: '3/8', QUARTER: '1/4', ONE_EIGHTH: '1/8', EMPTY: 'فارغ' }, fuelLevelLabels: { FULL: 'ممتلئ', SEVEN_EIGHTHS: '7/8', THREE_QUARTERS: '3/4', FIVE_EIGHTHS: '5/8', HALF: '1/2', THREE_EIGHTHS: '3/8', QUARTER: '1/4', ONE_EIGHTH: '1/8', EMPTY: 'فارغ' },
paymentProviderLabels: { CASH: 'نقداً', STRIPE: 'Stripe', BANK_TRANSFER: 'تحويل بنكي', CHECK: 'شيك', PAYPAL: 'PayPal', CREDIT_CARD: 'بطاقة ائتمان', DEBIT_CARD: 'بطاقة خصم' }, paymentProviderLabels: { CASH: 'نقداً', STRIPE: 'Stripe', BANK_TRANSFER: 'تحويل بنكي', CHECK: 'شيك', PAYPAL: 'PayPal', CREDIT_CARD: 'بطاقة ائتمان', DEBIT_CARD: 'بطاقة خصم' },
}, },
@@ -171,7 +171,7 @@ export default function SettingsPage() {
saveAccounting: 'Enregistrer la comptabilité', saveAccounting: 'Enregistrer la comptabilité',
displayName: 'Nom affiché', displayName: 'Nom affiché',
tagline: 'Slogan', tagline: 'Slogan',
publicEmail: 'Email public', publicEmail: 'E-mail public',
publicPhone: 'Téléphone public', publicPhone: 'Téléphone public',
city: 'Ville', city: 'Ville',
country: 'Pays', country: 'Pays',
@@ -179,14 +179,14 @@ export default function SettingsPage() {
contractLanguage: 'Langue du contrat', contractLanguage: 'Langue du contrat',
whatsapp: 'Numéro WhatsApp', whatsapp: 'Numéro WhatsApp',
brandColor: 'Couleur de marque', brandColor: 'Couleur de marque',
listedMarketplace: 'Listé sur la marketplace', listedMarketplace: 'Publié sur la marketplace',
logoUpload: 'Logo', logoUpload: 'Logo',
heroUpload: 'Image héro', heroUpload: 'Image principale',
uploading: 'Téléversement…', uploading: 'Téléversement…',
logoUploaded: 'Logo téléversé', logoUploaded: 'Logo téléversé',
noLogo: 'Aucun logo téléversé', noLogo: 'Aucun logo téléversé',
heroUploaded: 'Image héro téléversée', heroUploaded: 'Image principale téléversée',
noHero: 'Aucune image héro téléversée', noHero: 'Aucune image principale téléversée',
subdomain: 'Sous-domaine', subdomain: 'Sous-domaine',
customDomainLabel: 'Domaine personnalisé', customDomainLabel: 'Domaine personnalisé',
domainPlaceholder: 'voitures.exemple.com', domainPlaceholder: 'voitures.exemple.com',
@@ -198,7 +198,7 @@ export default function SettingsPage() {
additionalDriverCharge: 'Supplément conducteur additionnel', additionalDriverCharge: 'Supplément conducteur additionnel',
dailyDriverRate: 'Tarif conducteur/jour', dailyDriverRate: 'Tarif conducteur/jour',
flatDriverRate: 'Tarif conducteur fixe', flatDriverRate: 'Tarif conducteur fixe',
fuelPolicyNote: 'Note politique carburant', fuelPolicyNote: 'Note sur la politique carburant',
damagePolicy: 'Politique dommages', damagePolicy: 'Politique dommages',
active: 'Actif', active: 'Actif',
inactive: 'Inactif', inactive: 'Inactif',
@@ -213,7 +213,7 @@ export default function SettingsPage() {
reportingPeriod: 'Période de reporting', reportingPeriod: 'Période de reporting',
reportFormat: 'Format de rapport', reportFormat: 'Format de rapport',
accountantName: 'Nom du comptable', accountantName: 'Nom du comptable',
accountantEmail: 'Email du comptable', accountantEmail: 'E-mail du comptable',
autoSend: 'Envoyer automatiquement les rapports au comptable', autoSend: 'Envoyer automatiquement les rapports au comptable',
amanpayMerchantId: 'ID marchand AmanPay', amanpayMerchantId: 'ID marchand AmanPay',
amanpaySecretKey: 'Clé secrète AmanPay', amanpaySecretKey: 'Clé secrète AmanPay',
@@ -246,7 +246,7 @@ export default function SettingsPage() {
saveAccounting: 'حفظ المحاسبة', saveAccounting: 'حفظ المحاسبة',
displayName: 'اسم العرض', displayName: 'اسم العرض',
tagline: 'الشعار', tagline: 'الشعار',
publicEmail: 'البريد العام', publicEmail: 'البريد الإلكتروني العام',
publicPhone: 'الهاتف العام', publicPhone: 'الهاتف العام',
city: 'المدينة', city: 'المدينة',
country: 'الدولة', country: 'الدولة',
@@ -39,7 +39,7 @@ export default function ForgotPasswordPageClient({ embedded = false }: { embedde
title: 'نسيت كلمة المرور؟', title: 'نسيت كلمة المرور؟',
subtitle: 'أدخل بريدك الإلكتروني وسنرسل لك رابط إعادة التعيين.', subtitle: 'أدخل بريدك الإلكتروني وسنرسل لك رابط إعادة التعيين.',
email: 'البريد الإلكتروني', email: 'البريد الإلكتروني',
submit: 'إرسال رابط الإعادة', submit: 'إرسال رابط إعادة التعيين',
submitting: 'جارٍ الإرسال…', submitting: 'جارٍ الإرسال…',
backToLogin: 'العودة إلى تسجيل الدخول', backToLogin: 'العودة إلى تسجيل الدخول',
successTitle: 'تحقق من بريدك الإلكتروني', successTitle: 'تحقق من بريدك الإلكتروني',
@@ -51,7 +51,7 @@ function ResetPasswordContent({ embedded = false }: { embedded?: boolean }) {
errorShort: 'Le mot de passe doit contenir au moins 8 caractères.', errorShort: 'Le mot de passe doit contenir au moins 8 caractères.',
errorInvalidToken: 'Ce lien est invalide ou a expiré. Veuillez en demander un nouveau.', errorInvalidToken: 'Ce lien est invalide ou a expiré. Veuillez en demander un nouveau.',
errorGeneric: 'Une erreur est survenue. Veuillez réessayer.', errorGeneric: 'Une erreur est survenue. Veuillez réessayer.',
noToken: 'Aucun jeton trouvé. Veuillez demander une réinitialisation.', noToken: 'Aucun jeton de réinitialisation trouvé. Veuillez demander un nouveau lien.',
requestNew: 'Demander un nouveau lien', requestNew: 'Demander un nouveau lien',
}, },
ar: { ar: {
@@ -69,7 +69,7 @@ function ResetPasswordContent({ embedded = false }: { embedded?: boolean }) {
errorShort: 'يجب أن تتكون كلمة المرور من 8 أحرف على الأقل.', errorShort: 'يجب أن تتكون كلمة المرور من 8 أحرف على الأقل.',
errorInvalidToken: 'رابط إعادة التعيين غير صالح أو منتهي الصلاحية.', errorInvalidToken: 'رابط إعادة التعيين غير صالح أو منتهي الصلاحية.',
errorGeneric: 'حدث خطأ ما. يرجى المحاولة مرة أخرى.', errorGeneric: 'حدث خطأ ما. يرجى المحاولة مرة أخرى.',
noToken: 'لم يتم العثور على رمز. يرجى طلب إعادة تعيين.', noToken: 'لم يتم العثور على رمز إعادة التعيين. يرجى طلب رابط جديد.',
requestNew: 'طلب رابط جديد', requestNew: 'طلب رابط جديد',
}, },
}[language] }[language]
@@ -44,7 +44,7 @@ export default function SignInPageClient({ embedded = false }: { embedded?: bool
unexpectedError: 'Something went wrong. Please try again.', unexpectedError: 'Something went wrong. Please try again.',
}, },
fr: { fr: {
brandName: 'Agence Espace', brandName: 'Espace agence',
title: 'Connexion', title: 'Connexion',
subtitle: 'Saisissez vos identifiants pour accéder à votre compte.', subtitle: 'Saisissez vos identifiants pour accéder à votre compte.',
email: 'Email', email: 'Email',
@@ -57,12 +57,12 @@ export default function SignInPageClient({ embedded = false }: { embedded?: bool
enterCode: "Entrez le code à 6 chiffres de votre application d'authentification.", enterCode: "Entrez le code à 6 chiffres de votre application d'authentification.",
back: 'Retour aux identifiants', back: 'Retour aux identifiants',
forgotPassword: 'Mot de passe oublié ?', forgotPassword: 'Mot de passe oublié ?',
invalidCredentials: 'Email ou mot de passe invalide.', invalidCredentials: 'Adresse e-mail ou mot de passe invalide.',
passwordNotSet: `Aucun mot de passe défini. Vérifiez votre e-mail d'invitation ou utilisez « Mot de passe oublié ? »`, passwordNotSet: `Aucun mot de passe défini. Vérifiez votre e-mail d'invitation ou utilisez « Mot de passe oublié ? »`,
unexpectedError: 'Une erreur est survenue. Veuillez réessayer.', unexpectedError: 'Une erreur est survenue. Veuillez réessayer.',
}, },
ar: { ar: {
brandName: ' فضاء الوكالة ', brandName: 'مساحة الوكالة',
title: 'تسجيل الدخول', title: 'تسجيل الدخول',
subtitle: 'أدخل بياناتك للوصول إلى حسابك.', subtitle: 'أدخل بياناتك للوصول إلى حسابك.',
email: 'البريد الإلكتروني', email: 'البريد الإلكتروني',
@@ -174,14 +174,14 @@ export default function SignUpPage() {
brand: 'RentalDriveGo', brand: 'RentalDriveGo',
pageTitle: 'Lancez votre espace de location', pageTitle: 'Lancez votre espace de location',
pageSubtitle: 'Créez le compte utilisateur, complétez les informations de lentreprise et lancez votre espace.', pageSubtitle: 'Créez le compte utilisateur, complétez les informations de lentreprise et lancez votre espace.',
steps: ['Compte utilisateur', 'Infos entreprise', 'Plan', 'Vérifier'], steps: ['Compte utilisateur', 'Infos entreprise', 'Plan', 'Vérification'],
ownerTitle: 'Compte utilisateur', ownerTitle: 'Compte utilisateur',
ownerBody: 'Créez le compte utilisateur principal qui gérera cet espace entreprise.', ownerBody: 'Créez le compte utilisateur principal qui gérera cet espace entreprise.',
partnerTitle: 'Infos entreprise', partnerTitle: 'Infos entreprise',
partnerBody: 'Complétez les informations de lentreprise requises pour créer lespace.', partnerBody: 'Complétez les informations de lentreprise requises pour créer lespace.',
planTitle: 'Choisissez votre formule', planTitle: 'Choisissez votre formule',
planBody: 'Votre espace démarre immédiatement avec un essai gratuit de 14 jours dans la devise choisie.', planBody: 'Votre espace démarre immédiatement avec un essai gratuit de 14 jours dans la devise choisie.',
reviewTitle: 'Vérifier et lancer', reviewTitle: 'Vérification et lancement',
reviewBody: 'Nous allons créer immédiatement lespace entreprise et démarrer lessai de 14 jours avec la formule choisie.', reviewBody: 'Nous allons créer immédiatement lespace entreprise et démarrer lessai de 14 jours avec la formule choisie.',
firstName: 'Prénom du gérant/propriétaire', firstName: 'Prénom du gérant/propriétaire',
lastName: 'Nom du gérant/propriétaire', lastName: 'Nom du gérant/propriétaire',
@@ -205,7 +205,7 @@ export default function SignUpPage() {
createWorkspace: 'Créer lespace', createWorkspace: 'Créer lespace',
workspaceReady: 'Espace prêt', workspaceReady: 'Espace prêt',
workspaceReadyBody: 'est maintenant actif et le-mail propriétaire', workspaceReadyBody: 'est maintenant actif et le-mail propriétaire',
enterDashboard: 'Entrer dans le dashboard', enterDashboard: 'Accéder au tableau de bord',
signInAnotherDevice: 'Se connecter sur un autre appareil', signInAnotherDevice: 'Se connecter sur un autre appareil',
reviewWorkspace: 'Espace', reviewWorkspace: 'Espace',
reviewPlan: 'Formule', reviewPlan: 'Formule',
@@ -233,7 +233,7 @@ export default function SignUpPage() {
'': 'Sélectionner les années dactivité', '': 'Sélectionner les années dactivité',
LESS_THAN_1: 'Moins de 1 an', LESS_THAN_1: 'Moins de 1 an',
ONE_TO_FIVE: '1 an à 5 ans', ONE_TO_FIVE: '1 an à 5 ans',
MORE_THAN_5: 'Plus > 5 ans', MORE_THAN_5: 'Plus de 5 ans',
} as Record<string, string>, } as Record<string, string>,
planDescriptions: { planDescriptions: {
STARTER: 'Flotte, offres et réservations essentielles.', STARTER: 'Flotte, offres et réservations essentielles.',
@@ -1335,8 +1335,8 @@ const dictionaries: Record<DashboardLanguage, DashboardDictionary> = {
damageMarkersHeading: 'علامات الضرر', damageMarkersHeading: 'علامات الضرر',
removeMarker: 'حذف', removeMarker: 'حذف',
noMarkers: 'لا توجد علامات ضرر محفوظة.', noMarkers: 'لا توجد علامات ضرر محفوظة.',
damageTypeLabels: { SCRATCH: 'خدش', DENT: 'دهس', CRACK: 'تشقق', CHIP: 'تقشر', MISSING: 'مفقود', STAIN: 'بقعة', OTHER: 'أخرى' }, damageTypeLabels: { SCRATCH: 'خدش', DENT: 'انبعاج', CRACK: 'تشقق', CHIP: 'تقشر', MISSING: 'مفقود', STAIN: 'بقعة', OTHER: 'أخرى' },
severityLabels: { MINOR: 'طفيف', MODERATE: 'متوسط', MAJOR: 'خطير' }, severityLabels: { MINOR: 'طفيف', MODERATE: 'متوسط', MAJOR: 'شديد' },
}, },
}, },
} }
@@ -16,8 +16,8 @@ type FooterItem = {
const localeOptions: Array<{ value: DashboardLanguage; label: string; flag: string }> = [ const localeOptions: Array<{ value: DashboardLanguage; label: string; flag: string }> = [
{ value: 'en', label: 'Global (English)', flag: '🇺🇸' }, { value: 'en', label: 'Global (English)', flag: '🇺🇸' },
{ value: 'ar', label: 'NorthAfrica (Arabic)', flag: '🇲🇦' }, { value: 'ar', label: 'North Africa (Arabic)', flag: '🇲🇦' },
{ value: 'fr', label: 'Europ (French)', flag: '🇫🇷' }, { value: 'fr', label: 'Europe (French)', flag: '🇫🇷' },
] ]
function getFooterContent(language: DashboardLanguage): { function getFooterContent(language: DashboardLanguage): {
@@ -42,7 +42,7 @@ function getFooterContent(language: DashboardLanguage): {
{ label: 'Contacter les ventes', href: `${marketplaceUrl}/footer/contact-sales` }, { label: 'Contacter les ventes', href: `${marketplaceUrl}/footer/contact-sales` },
{ label: 'Nos bureaux', href: `${marketplaceUrl}/footer/our-offices` }, { label: 'Nos bureaux', href: `${marketplaceUrl}/footer/our-offices` },
], ],
localeLabel: 'Europ (French)', localeLabel: 'Europe (French)',
rightsLabel: 'Tous droits réservés.', rightsLabel: 'Tous droits réservés.',
} }
case 'ar': case 'ar':
@@ -60,7 +60,7 @@ function getFooterContent(language: DashboardLanguage): {
{ label: 'تواصل مع المبيعات', href: `${marketplaceUrl}/footer/contact-sales` }, { label: 'تواصل مع المبيعات', href: `${marketplaceUrl}/footer/contact-sales` },
{ label: 'مكاتبنا', href: `${marketplaceUrl}/footer/our-offices` }, { label: 'مكاتبنا', href: `${marketplaceUrl}/footer/our-offices` },
], ],
localeLabel: 'NorthAfrica (Arabic)', localeLabel: 'North Africa (Arabic)',
rightsLabel: 'جميع الحقوق محفوظة.', rightsLabel: 'جميع الحقوق محفوظة.',
} }
case 'en': case 'en':
@@ -42,7 +42,7 @@ export default function PublicHeader() {
home: 'Accueil', home: 'Accueil',
marketplace: 'Marketplace', marketplace: 'Marketplace',
signIn: 'Connexion', signIn: 'Connexion',
createAccount: "Créer Espace d'Agence", createAccount: 'Créer un espace dagence',
theme: 'Mode', theme: 'Mode',
light: 'Clair', light: 'Clair',
medium: 'Moyen', medium: 'Moyen',
+1 -1
View File
@@ -113,7 +113,7 @@ export default async function ExplorePage({ searchParams }: { searchParams?: Rec
kicker: 'Découverte uniquement', kicker: 'Découverte uniquement',
title: "Trouvez votre prochaine location auprès d'entreprises locales fiables.", title: "Trouvez votre prochaine location auprès d'entreprises locales fiables.",
body: "Parcourez, filtrez et réservez — l'entreprise confirme et vous contacte directement.", body: "Parcourez, filtrez et réservez — l'entreprise confirme et vous contacte directement.",
searchTitle: 'Réserver un véhicule', searchTitle: 'Trouver un véhicule',
pickupLocation: 'Lieu de départ', pickupLocation: 'Lieu de départ',
dropoffLocation: "Lieu de retour", dropoffLocation: "Lieu de retour",
pickupDate: 'Date de départ', pickupDate: 'Date de départ',
+3 -3
View File
@@ -19,13 +19,13 @@ const copy = {
fr: { fr: {
kicker: 'Fonctionnalités', kicker: 'Fonctionnalités',
title: 'Opérations, découverte marketplace et réservation de marque.', title: 'Opérations, découverte marketplace et réservation de marque.',
body: 'RentalDriveGo combine des opérations privées pour lentreprise avec une marketplace publique qui envoie les clients vers le flux de paiement propre à chaque société.', body: 'RentalDriveGo combine des opérations privées pour lentreprise avec une marketplace publique qui redirige les clients vers le parcours de paiement propre à chaque société.',
cards: [ cards: [
['Tableau de bord privé', 'Flotte, réservations, CRM, permissions d’équipe, facturation et rapports restent isolés par entreprise.'], ['Tableau de bord privé', 'Flotte, réservations, CRM, permissions d’équipe, facturation et rapports restent isolés par entreprise.'],
['Découverte marketplace', 'Les véhicules publiés et les offres publiques apparaissent sur `/explore` pour une navigation multi-entreprises.'], ['Découverte marketplace', 'Les véhicules publiés et les offres publiques apparaissent sur `/explore` pour une navigation multi-entreprises.'],
['Site de réservation de marque', 'Chaque entreprise dispose de son propre sous-domaine où les clients terminent réservation et paiement.'], ['Site de réservation de marque', 'Chaque entreprise dispose de son propre sous-domaine où les clients finalisent la réservation et le paiement.'],
['Flexibilité de paiement', 'Utilisez AmanPay ou PayPal pour les abonnements et pour les paiements côté entreprise.'], ['Flexibilité de paiement', 'Utilisez AmanPay ou PayPal pour les abonnements et pour les paiements côté entreprise.'],
['Opérations avancées', 'Assurance, conducteurs supplémentaires, règles tarifaires et politiques carburant structurées vivent dans le dashboard.'], ['Opérations avancées', 'Assurance, conducteurs supplémentaires, règles tarifaires et politiques carburant structurées sont gérés dans les paramètres du tableau de bord.'],
['Contrôle des notifications', 'Le personnel comme les clients peuvent gérer leurs préférences de notification par événement et canal.'], ['Contrôle des notifications', 'Le personnel comme les clients peuvent gérer leurs préférences de notification par événement et canal.'],
], ],
}, },
@@ -41,7 +41,7 @@ const dictionaries: Record<MarketplaceLanguage, Dictionary> = {
marketplace: 'Accueil', marketplace: 'Accueil',
explore: 'Marketplace', explore: 'Marketplace',
signIn: 'Connexion', signIn: 'Connexion',
ownerSignIn: "Créer Espace d'Agence", ownerSignIn: 'Créer un espace dagence',
language: 'Langue', language: 'Langue',
theme: 'Mode', theme: 'Mode',
light: 'Clair', light: 'Clair',
@@ -75,8 +75,8 @@ const PreferencesContext = createContext<PreferencesContextValue | null>(null)
const localeOptions: Array<{ value: MarketplaceLanguage; label: string; flag: string }> = [ const localeOptions: Array<{ value: MarketplaceLanguage; label: string; flag: string }> = [
{ value: 'en', label: 'Global (English)', flag: '🇺🇸' }, { value: 'en', label: 'Global (English)', flag: '🇺🇸' },
{ value: 'ar', label: 'NorthAfrica (Arabic)', flag: '🇲🇦' }, { value: 'ar', label: 'North Africa (Arabic)', flag: '🇲🇦' },
{ value: 'fr', label: 'Europ (French)', flag: '🇫🇷' }, { value: 'fr', label: 'Europe (French)', flag: '🇫🇷' },
] ]
function getFooterContent(language: MarketplaceLanguage): { function getFooterContent(language: MarketplaceLanguage): {
@@ -101,7 +101,7 @@ function getFooterContent(language: MarketplaceLanguage): {
{ label: 'Contacter les ventes', href: footerPageHref['contact-sales'] }, { label: 'Contacter les ventes', href: footerPageHref['contact-sales'] },
{ label: 'Nos bureaux', href: footerPageHref['our-offices'] }, { label: 'Nos bureaux', href: footerPageHref['our-offices'] },
], ],
localeLabel: 'Europ (French)', localeLabel: 'Europe (French)',
rightsLabel: 'Tous droits réservés.', rightsLabel: 'Tous droits réservés.',
} }
case 'ar': case 'ar':
@@ -119,7 +119,7 @@ function getFooterContent(language: MarketplaceLanguage): {
{ label: 'تواصل مع المبيعات', href: footerPageHref['contact-sales'] }, { label: 'تواصل مع المبيعات', href: footerPageHref['contact-sales'] },
{ label: 'مكاتبنا', href: footerPageHref['our-offices'] }, { label: 'مكاتبنا', href: footerPageHref['our-offices'] },
], ],
localeLabel: 'NorthAfrica (Arabic)', localeLabel: 'North Africa (Arabic)',
rightsLabel: 'جميع الحقوق محفوظة.', rightsLabel: 'جميع الحقوق محفوظة.',
} }
case 'en': case 'en':
@@ -54,7 +54,7 @@ export default function WorkspaceFrame({ target }: { target: FrameId }) {
const frameHeight = isStandaloneAuthRoute ? '100vh' : 'calc(100vh - 56px)' const frameHeight = isStandaloneAuthRoute ? '100vh' : 'calc(100vh - 56px)'
const loadingLabel = { const loadingLabel = {
en: 'Loading', en: 'Loading',
fr: 'Chargement de', fr: 'Chargement',
ar: 'جارٍ تحميل', ar: 'جارٍ تحميل',
}[language] }[language]
@@ -34,35 +34,35 @@ export default function WorkspaceTabs() {
workspaceC: 'Branded booking handoff begins only after a renter chooses a specific vehicle.', workspaceC: 'Branded booking handoff begins only after a renter chooses a specific vehicle.',
}, },
fr: { fr: {
badge: 'Espace unifie', badge: 'Espace unifié',
title: 'Un point d\'entree sur le port 3000 pour la decouverte marketplace, les operations entreprise et l\'administration plateforme.', title: 'Un point dentrée sur le port 3000 pour la découverte marketplace, les opérations de lentreprise et ladministration de la plateforme.',
intro: intro:
'Les applications restent des surfaces Next.js separees en interne, mais la barre de navigation mene directement vers chaque espace sans changer de port manuellement.', 'Les applications restent des interfaces Next.js séparées en interne, mais la barre de navigation mène directement à chaque espace sans avoir à changer de port manuellement.',
sectionBadge: 'Marketplace RentalDriveGo', sectionBadge: 'Marketplace RentalDriveGo',
sectionTitle: 'Parcourez les flottes locales sans perdre l\'identite de l\'entreprise qui gere la location.', sectionTitle: 'Parcourez les flottes locales sans perdre lidentité de lentreprise qui gère la location.',
sectionBodyA: 'La decouverte commence ici. La reservation et le paiement se font sur le site de reservation propre a chaque entreprise.', sectionBodyA: 'La découverte commence ici. La réservation et le paiement se font sur le site de réservation propre à chaque entreprise.',
sectionBodyB: 'Les clients accedent a ce site de reservation seulement apres avoir choisi un vehicule dans la flotte d\'une entreprise.', sectionBodyB: 'Les clients accèdent à ce site de réservation seulement après avoir choisi un véhicule dans la flotte dune entreprise.',
exploreVehicles: 'Explorer les vehicules', exploreVehicles: 'Explorer les véhicules',
viewPricing: 'Voir les tarifs', viewPricing: 'Voir les tarifs',
nowServing: 'Disponible', nowServing: 'Disponible',
promoTitle: 'La decouverte, les tarifs, la connexion proprietaire et l\'entree vers la reservation restent ancrees dans la marketplace.', promoTitle: 'La découverte, les tarifs, la connexion propriétaire et laccès à la réservation restent ancrés dans la marketplace.',
promoBody: promoBody:
'Espace entreprise et Operations plateforme restent accessibles depuis la navigation, tandis que la reservation de marque commence apres le choix d\'un vehicule.', 'Lespace entreprise et les opérations de la plateforme restent accessibles depuis la navigation, tandis que la réservation de marque commence après le choix dun véhicule.',
customerPaths: 'Parcours client', customerPaths: 'Parcours client',
ownerSignIn: 'Connexion proprietaire', ownerSignIn: 'Connexion propriétaire',
pricing: 'Tarifs', pricing: 'Tarifs',
workspaceAreas: 'Espaces de travail', workspaceAreas: 'Espaces de travail',
workspaceA: 'Espace entreprise pour la flotte, l\'equipe, la facturation et l\'analyse.', workspaceA: 'Espace entreprise pour la flotte, l’équipe, la facturation et lanalyse.',
workspaceB: 'Operations plateforme pour le support global et la supervision des tenants.', workspaceB: 'Opérations plateforme pour le support global et la supervision multi-entreprises.',
workspaceC: 'Le transfert vers la reservation de marque commence seulement apres le choix d\'un vehicule precis.', workspaceC: 'Le transfert vers la réservation de marque commence seulement après le choix dun véhicule précis.',
}, },
ar: { ar: {
badge: 'مساحة موحدة', badge: 'مساحة موحدة',
title: 'نقطة دخول واحدة على المنفذ 3000 لاكتشاف السوق وعمليات الشركات وإدارة المنصة.', title: 'نقطة دخول واحدة على المنفذ 3000 لاكتشاف السوق وعمليات الشركات وإدارة المنصة.',
intro: intro:
'ما زالت التطبيقات تعمل كأسطح Next.js منفصلة داخلياً، لكن شريط التنقل ينقلك مباشرة إلى كل مساحة عمل بدون البحث عن المنافذ.', 'ما زالت التطبيقات تعمل كواجهات Next.js منفصلة داخلياً، لكن شريط التنقل ينقلك مباشرة إلى كل مساحة عمل من دون الحاجة إلى البحث عن المنافذ.',
sectionBadge: 'سوق RentalDriveGo', sectionBadge: 'سوق RentalDriveGo',
sectionTitle: 'تصفح أساطيل التأجير المحلية من دون فقدان هوية الشركة التي تدير الحجز.', sectionTitle: 'تصفح أساطيل التأجير المحلية مع الحفاظ على هوية الشركة التي تدير الحجز.',
sectionBodyA: 'الاكتشاف يبدأ هنا. الحجز والدفع يتمان على موقع الحجز الخاص بكل شركة.', sectionBodyA: 'الاكتشاف يبدأ هنا. الحجز والدفع يتمان على موقع الحجز الخاص بكل شركة.',
sectionBodyB: 'لا يصل العميل إلى موقع الحجز الخاص بالشركة إلا بعد اختيار سيارة من أسطول تلك الشركة.', sectionBodyB: 'لا يصل العميل إلى موقع الحجز الخاص بالشركة إلا بعد اختيار سيارة من أسطول تلك الشركة.',
exploreVehicles: 'استكشف السيارات', exploreVehicles: 'استكشف السيارات',
@@ -76,7 +76,7 @@ export default function WorkspaceTabs() {
pricing: 'الأسعار', pricing: 'الأسعار',
workspaceAreas: 'مساحات العمل', workspaceAreas: 'مساحات العمل',
workspaceA: 'مساحة الشركة لإدارة الأسطول والفريق والفوترة والتحليلات.', workspaceA: 'مساحة الشركة لإدارة الأسطول والفريق والفوترة والتحليلات.',
workspaceB: 'عمليات المنصة للدعم والإشراف على المستأجرين على مستوى المنصة.', workspaceB: 'عمليات المنصة للدعم والإشراف على الشركات على مستوى المنصة.',
workspaceC: 'الانتقال إلى موقع الحجز الخاص بالشركة يبدأ فقط بعد اختيار سيارة محددة.', workspaceC: 'الانتقال إلى موقع الحجز الخاص بالشركة يبدأ فقط بعد اختيار سيارة محددة.',
}, },
}[language] }[language]
+6 -6
View File
@@ -3,27 +3,27 @@ export type FuelPolicyType = 'FULL_TO_FULL' | 'FULL_TO_EMPTY' | 'SAME_TO_SAME' |
export const FUEL_POLICY_LABELS: Record<FuelPolicyType, Record<'en' | 'fr' | 'ar', string>> = { export const FUEL_POLICY_LABELS: Record<FuelPolicyType, Record<'en' | 'fr' | 'ar', string>> = {
FULL_TO_FULL: { FULL_TO_FULL: {
en: 'Full-to-Full: Return vehicle with a full tank. A refueling fee applies if returned with less fuel.', en: 'Full-to-Full: Return vehicle with a full tank. A refueling fee applies if returned with less fuel.',
fr: "Plein-à-Plein: Retournez le véhicule avec le plein. Des frais de carburant s'appliquent sinon.", fr: "Plein à plein : retournez le véhicule avec le plein. Des frais de ravitaillement s'appliquent sinon.",
ar: 'ممتلئ إلى ممتلئ: يجب إعادة السيارة بخزان ممتلئ. رسوم إضافية في حال الإعادة بمستوى أقل.', ar: 'ممتلئ إلى ممتلئ: يجب إعادة السيارة بخزان ممتلئ. رسوم إضافية في حال الإعادة بمستوى أقل.',
}, },
FULL_TO_EMPTY: { FULL_TO_EMPTY: {
en: 'Full-to-Empty: Vehicle is provided with a full tank. No refund for unused fuel upon return.', en: 'Full-to-Empty: Vehicle is provided with a full tank. No refund for unused fuel upon return.',
fr: 'Plein-à-Vide: Le véhicule est fourni avec un plein. Aucun remboursement pour carburant non utilisé.', fr: "Plein à vide : le véhicule est livré avec le plein. Aucun remboursement n'est accordé pour le carburant non utilisé.",
ar: 'ممتلئ إلى فارغ: تُسلَّم السيارة بخزان ممتلئ. لا استرداد للوقود غير المستخدم.', ar: 'ممتلئ إلى فارغ: تُسلَّم السيارة بخزان ممتلئ. لا استرداد للوقود غير المستخدم.',
}, },
SAME_TO_SAME: { SAME_TO_SAME: {
en: 'Same-to-Same: Return vehicle with the same fuel level as at pickup.', en: 'Same-to-Same: Return vehicle with the same fuel level as at pickup.',
fr: "Même niveau: Retournez avec le même niveau de carburant qu'au départ.", fr: "Même niveau : retournez le véhicule avec le même niveau de carburant qu'au départ.",
ar: 'نفس المستوى: أعد السيارة بنفس مستوى الوقود عند الاستلام.', ar: 'نفس المستوى: أعد السيارة بنفس مستوى الوقود عند الاستلام.',
}, },
PREPAID: { PREPAID: {
en: 'Prepaid Fuel: You pre-purchase a full tank at a fixed rate. Return at any fuel level.', en: 'Prepaid Fuel: You pre-purchase a full tank at a fixed rate. Return at any fuel level.',
fr: "Carburant prépayé: Vous prépayez un plein à tarif fixe. Retour à n'importe quel niveau.", fr: "Carburant prépayé : vous achetez un plein à tarif fixe à l'avance. Retour possible avec n'importe quel niveau.",
ar: 'الوقود المدفوع مسبقاً: تدفع مقدماً لخزان كامل بسعر ثابت. العودة بأي مستوى.', ar: 'الوقود المدفوع مسبقاً: تدفع مقدماً ثمن خزان كامل بسعر ثابت. يمكن إعادة السيارة بأي مستوى.',
}, },
FREE: { FREE: {
en: 'Fuel Included: Fuel cost is included in the rental price.', en: 'Fuel Included: Fuel cost is included in the rental price.',
fr: 'Carburant inclus: Le coût du carburant est inclus dans le tarif de location.', fr: 'Carburant inclus : le coût du carburant est inclus dans le tarif de location.',
ar: 'الوقود مشمول: تكلفة الوقود مشمولة في سعر الإيجار.', ar: 'الوقود مشمول: تكلفة الوقود مشمولة في سعر الإيجار.',
}, },
} }
+22 -22
View File
@@ -130,12 +130,12 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = {
fr: { fr: {
sections: defaultMarketplaceHomepageSections, sections: defaultMarketplaceHomepageSections,
heroKicker: 'RentalDriveGo', heroKicker: 'RentalDriveGo',
heroTitle: 'Une vitrine marketplace plus nette et plus forte.', heroTitle: 'Une vitrine marketplace plus claire et plus percutante.',
heroBody: heroBody:
'Les entreprises de location gèrent leurs opérations en privé, les clients parcourent une marketplace commune, et chaque réservation se termine sur le paiement de marque de la société.', "Les entreprises de location gèrent leurs opérations en privé, les clients parcourent une marketplace commune et chaque réservation se finalise sur le parcours de paiement aux couleurs de l'entreprise.",
startTrial: 'Commencer lessai gratuit', startTrial: 'Commencer lessai gratuit',
exploreVehicles: 'Explorer les véhicules', exploreVehicles: 'Explorer les véhicules',
surfaceLabel: 'Surface marketplace', surfaceLabel: 'Vitrine marketplace',
surfaceTitle: 'Pensée pour deux audiences à la fois.', surfaceTitle: 'Pensée pour deux audiences à la fois.',
surfaceBody: surfaceBody:
'Les opérateurs veulent du contrôle, les clients veulent de la confiance. La marketplace doit montrer les deux sans ressembler à un modèle générique.', 'Les opérateurs veulent du contrôle, les clients veulent de la confiance. La marketplace doit montrer les deux sans ressembler à un modèle générique.',
@@ -144,26 +144,26 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = {
brandedFlows: 'Parcours de marque', brandedFlows: 'Parcours de marque',
multiTenant: 'Opérations multi-tenant', multiTenant: 'Opérations multi-tenant',
companyKicker: 'Flux opérateur', companyKicker: 'Flux opérateur',
companyTitle: 'Pilotez inventaire, offres, facturation et équipe depuis une seule couche de commande.', companyTitle: 'Pilotez linventaire, les offres, la facturation et léquipe depuis un seul centre de contrôle.',
companyBody: companyBody:
'Publiez une seule fois, choisissez ce qui apparaît publiquement, et gardez contrats, paiements et rapports isolés par entreprise.', 'Publiez une seule fois, choisissez ce qui apparaît publiquement, et gardez contrats, paiements et rapports isolés par entreprise.',
renterKicker: 'Expérience client', renterKicker: 'Expérience client',
renterTitle: 'Laissez les clients comparer rapidement puis dirigez-les vers le bon site entreprise.', renterTitle: 'Laissez les clients comparer rapidement, puis redirigez-les vers le bon site dentreprise.',
renterBody: renterBody:
'La marketplace agit comme moteur de découverte, pas comme agrégateur sans suite. Recherche ici, réservation là-bas, paiement direct.', 'La marketplace sert de moteur de découverte, pas dagrégateur sans suite. Recherche ici, réservation là-bas, paiement direct.',
pillars: [ pillars: [
['Publication unifiée', 'Les photos, tarifs et offres circulent depuis un seul flux admin vers la découverte marketplace et les pages de réservation de marque.'], ['Publication unifiée', 'Les photos, tarifs et offres circulent depuis un seul flux dadministration vers la découverte marketplace et les pages de réservation de marque.'],
['Découverte qualifiée', 'Offres mises en avant, navigation par localisation et signaux de réputation aident les clients à filtrer rapidement.'], ['Découverte qualifiée', 'Offres mises en avant, navigation par localisation et signaux de réputation aident les clients à filtrer rapidement.'],
['Revenus en direct', 'Les réservations basculent vers le paiement propre à lentreprise, pour garder la relation client et lencaissement.'], ['Revenus en direct', 'Les réservations basculent vers le paiement propre à lentreprise afin de préserver la relation client et lencaissement.'],
].map(([title, body]) => ({ title: title!, body: body! })), ].map(([title, body]) => ({ title: title!, body: body! })),
metrics: [ metrics: [
['01', 'Visibilité marketplace partagée'], ['01', 'Visibilité marketplace partagée'],
['02', 'Paiement direct entreprise'], ['02', 'Paiement direct entreprise'],
['03', 'Paiements détenus par la société'], ['03', 'Paiements gérés par lentreprise'],
].map(([value, label]) => ({ value: value!, label: label! })), ].map(([value, label]) => ({ value: value!, label: label! })),
featureLabel: 'Ce que couvre le produit', featureLabel: 'Ce que couvre le produit',
features: [ features: [
'Gestion de flotte avec téléversement multi-photos', 'Gestion de flotte avec téléversement de plusieurs photos',
'Offres marketplace et redirection vers la réservation', 'Offres marketplace et redirection vers la réservation',
'Site public de réservation par entreprise', 'Site public de réservation par entreprise',
'CRM client, analytics et contrôle de facturation', 'CRM client, analytics et contrôle de facturation',
@@ -176,18 +176,18 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = {
].map(([step, title, body]) => ({ step: step!, title: title!, body: body! })), ].map(([step, title, body]) => ({ step: step!, title: title!, body: body! })),
stepLabel: 'Étape', stepLabel: 'Étape',
readyKicker: 'Prêt à démarrer', readyKicker: 'Prêt à démarrer',
readyTitle: 'Une homepage marketplace doit vendre le système en quelques secondes.', readyTitle: 'La page daccueil marketplace doit présenter le produit en quelques secondes.',
readyBody: readyBody:
'Utilisez la marketplace pour capter la demande, puis faites passer les clients vers une expérience de marque qui garde prix, paiements et confiance liés à votre entreprise.', 'Utilisez la marketplace pour capter la demande, puis orientez les clients vers une expérience de marque où les prix, les paiements et la confiance restent attachés à votre entreprise.',
viewPricing: 'Voir les tarifs', viewPricing: 'Voir les tarifs',
createWorkspace: 'Créer lespace', createWorkspace: 'Créer lespace',
}, },
ar: { ar: {
sections: defaultMarketplaceHomepageSections, sections: defaultMarketplaceHomepageSections,
heroKicker: 'RentalDriveGo', heroKicker: 'RentalDriveGo',
heroTitle: 'واجهة سوق أوضح وأقوى.', heroTitle: 'واجهة سوق أكثر وضوحاً وتأثيراً.',
heroBody: heroBody:
'شركات التأجير تدير عملياتها بشكل خاص، والمستأجرون يتصفحون سوقاً مشتركاً، وكل حجز ينتهي في صفحة دفع تحمل هوية الشركة نفسها.', 'شركات التأجير تدير عملياتها بشكل خاص، والمستأجرون يتصفحون سوقاً مشتركاً، وكل حجز ينتهي في صفحة دفع تحمل هوية الشركة ذاتها.',
startTrial: 'ابدأ التجربة المجانية', startTrial: 'ابدأ التجربة المجانية',
exploreVehicles: 'استكشف السيارات', exploreVehicles: 'استكشف السيارات',
surfaceLabel: 'واجهة السوق', surfaceLabel: 'واجهة السوق',
@@ -198,25 +198,25 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = {
trustedFleets: 'أساطيل موثوقة', trustedFleets: 'أساطيل موثوقة',
brandedFlows: 'مسارات حجز مخصصة', brandedFlows: 'مسارات حجز مخصصة',
multiTenant: 'عمليات متعددة الشركات', multiTenant: 'عمليات متعددة الشركات',
companyKicker: 'تدفق المشغل', companyKicker: 'سير عمل المشغل',
companyTitle: 'تحكم في المخزون والعروض والفوترة والفريق من طبقة تشغيل واحدة.', companyTitle: 'تحكم في المخزون والعروض والفوترة والفريق من طبقة تشغيل واحدة.',
companyBody: companyBody:
'انشر المخزون مرة واحدة، وحدد ما يظهر للعامة، واحتفظ بالعقود والمدفوعات والتقارير معزولة لكل شركة.', 'انشر المخزون مرة واحدة، وحدد ما يظهر للعامة، واحتفظ بالعقود والمدفوعات والتقارير معزولة لكل شركة.',
renterKicker: 'تجربة المستأجر', renterKicker: 'تجربة المستأجر',
renterTitle: 'دع المستأجر يقارن بسرعة ثم انقله إلى موقع الشركة المناسب.', renterTitle: 'دع المستأجرين يقارنون بسرعة ثم وجّههم إلى موقع الشركة المناسب.',
renterBody: renterBody:
'السوق هنا محرك اكتشاف وليس مجمعاً بلا نهاية. البحث هنا، الحجز هناك، والدفع مباشرة للشركة.', 'هذه المنصة محرك لاكتشاف الخيارات، وليست مُجمِّعاً بلا مسار واضح. ابحث هنا، احجز هناك، وادفع مباشرة للشركة.',
pillars: [ pillars: [
['نشر موحد', 'صور السيارات والأسعار والعروض تنتقل من تدفق إدارة واحد إلى السوق وصفحات الحجز ذات الهوية الخاصة.'], ['نشر موحد', 'صور السيارات والأسعار والعروض تنتقل من سير إدارة واحد إلى السوق وصفحات الحجز ذات الهوية الخاصة.'],
['اكتشاف مؤهل', 'العروض المميزة والتصفح حسب الموقع وإشارات السمعة تساعد المستأجرين على تضييق الخيارات بسرعة.'], ['اكتشاف مؤهل', 'العروض المميزة والتصفح حسب الموقع وإشارات السمعة تساعد المستأجرين على تضييق الخيارات بسرعة.'],
['مسار إيراد مباشر', 'تنتقل الحجوزات إلى صفحة الدفع الخاصة بالشركة حتى تبقى الملكية المالية وعلاقة العميل مع النشاط نفسه.'], ['مسار إيراد مباشر', 'تنتقل الحجوزات إلى صفحة الدفع الخاصة بالشركة حتى تبقى علاقة العميل والتحصيل المالي بيد الشركة نفسها.'],
].map(([title, body]) => ({ title: title!, body: body! })), ].map(([title, body]) => ({ title: title!, body: body! })),
metrics: [ metrics: [
['01', 'ظهور مشترك في السوق'], ['01', 'ظهور مشترك في السوق'],
['02', 'دفع مباشر للشركة'], ['02', 'دفع مباشر للشركة'],
['03', 'مدفوعات مملوكة للشركة'], ['03', 'مدفوعات مملوكة للشركة'],
].map(([value, label]) => ({ value: value!, label: label! })), ].map(([value, label]) => ({ value: value!, label: label! })),
featureLabel: 'ما الذي يغطيه المنتج', featureLabel: 'ما الذي يقدمه المنتج',
features: [ features: [
'إدارة الأسطول مع رفع عدة صور', 'إدارة الأسطول مع رفع عدة صور',
'عروض السوق والتحويل إلى مسار الحجز', 'عروض السوق والتحويل إلى مسار الحجز',
@@ -225,7 +225,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = {
], ],
stepsTitle: 'كيف تبدأ الشركات', stepsTitle: 'كيف تبدأ الشركات',
steps: [ steps: [
['1', 'أنشئ مساحة شركتك', 'اختر الخطة وابدأ تجربتك لمدة 14 يوماً ثم تحقق من حساب المالك.'], ['1', 'أنشئ مساحة عمل شركتك', 'اختر الخطة وابدأ تجربتك لمدة 14 يوماً ثم فعّل حساب المالك.'],
['2', 'انشر السيارات والعروض', 'ارفع الصور مرة واحدة وتحكم فيما يظهر في السوق وفي الموقع المخصص.'], ['2', 'انشر السيارات والعروض', 'ارفع الصور مرة واحدة وتحكم فيما يظهر في السوق وفي الموقع المخصص.'],
['3', 'استقبل الحجوزات على موقعك', 'يكتشف المستأجرون أسطولك على RentalDriveGo ثم يدفعون مباشرة عبر مسار الحجز الخاص بك.'], ['3', 'استقبل الحجوزات على موقعك', 'يكتشف المستأجرون أسطولك على RentalDriveGo ثم يدفعون مباشرة عبر مسار الحجز الخاص بك.'],
].map(([step, title, body]) => ({ step: step!, title: title!, body: body! })), ].map(([step, title, body]) => ({ step: step!, title: title!, body: body! })),
@@ -233,7 +233,7 @@ export const defaultMarketplaceHomepageContent: MarketplaceHomepageConfig = {
readyKicker: 'جاهز للانطلاق', readyKicker: 'جاهز للانطلاق',
readyTitle: 'يجب أن تشرح الصفحة الرئيسية قيمة المنصة خلال ثوانٍ.', readyTitle: 'يجب أن تشرح الصفحة الرئيسية قيمة المنصة خلال ثوانٍ.',
readyBody: readyBody:
'استخدم السوق لجذب الطلب، ثم انقل المستأجرين إلى تجربة تحمل هوية شركتك وتحافظ على التسعير والمدفوعات والثقة داخل نشاطك.', 'استخدم السوق لجذب الطلب، ثم انقل المستأجرين إلى تجربة تحمل هوية شركتك، بحيث تبقى الأسعار والمدفوعات والثقة مرتبطة بنشاطك.',
viewPricing: 'عرض الأسعار', viewPricing: 'عرض الأسعار',
createWorkspace: 'إنشاء المساحة', createWorkspace: 'إنشاء المساحة',
}, },