From 0a854c430254dad9100b4ba3d9593b433d1dd14e Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Tue, 18 Aug 2026 12:18:06 +0300 Subject: [PATCH] feat(routing): pure Arabic localization for all navigation instructions (translate Continue onto, toward, keep right, etc.) --- apps/api/src/maps/maps.service.ts | 117 +++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 4 deletions(-) diff --git a/apps/api/src/maps/maps.service.ts b/apps/api/src/maps/maps.service.ts index 3ab2a8d..3111e46 100644 --- a/apps/api/src/maps/maps.service.ts +++ b/apps/api/src/maps/maps.service.ts @@ -241,6 +241,9 @@ export class MapsService { const startIdx = Math.min(interval[0], coords.length - 1); const endIdx = Math.min(interval[1], coords.length - 1); + // 1. Localize text into 100% fluent Arabic (Translate "Continue onto", "toward", "Keep right", etc.) + const localizedBaseText = this.localizeInstructionToArabic(inst.text); + if (startIdx < endIdx) { const startCoord = coords[startIdx]; // [lng, lat] const endCoord = coords[endIdx]; @@ -262,10 +265,10 @@ export class MapsService { if (slopePercent >= 9) { warning_ar = `⚠️ تنبيه: صعود حاد (+${slopePercent}%)`; - steepWarnings.push({ text: inst.text, slopePercent, type: 'incline', street: inst.street_name }); + steepWarnings.push({ text: localizedBaseText, slopePercent, type: 'incline', street: inst.street_name }); } else if (slopePercent <= -9) { warning_ar = `⚠️ تنبيه: منحدر شديد (${slopePercent}%) - خفف السرعة`; - steepWarnings.push({ text: inst.text, slopePercent, type: 'decline', street: inst.street_name }); + steepWarnings.push({ text: localizedBaseText, slopePercent, type: 'decline', street: inst.street_name }); } return { @@ -273,11 +276,14 @@ export class MapsService { slopePercent, elevationChangeMeters: Math.round(elevDiff), slopeWarning: warning_ar || undefined, - text: warning_ar ? `${inst.text} (${warning_ar})` : inst.text + text: warning_ar ? `${localizedBaseText} (${warning_ar})` : localizedBaseText }; } - return inst; + return { + ...inst, + text: localizedBaseText + }; }); return { @@ -293,6 +299,109 @@ export class MapsService { }; } + /** + * Translates any English phrases from routing engines (GraphHopper) into natural, fluent Arabic. + */ + private localizeInstructionToArabic(rawText: string): string { + if (!rawText) return ''; + + let text = rawText.trim(); + + // 1. English Directional & Maneuver Replacements + const phrases: [RegExp, string][] = [ + // Continuations + [/^Continue onto\s+/i, 'تابع السير في '], + [/^Continue on\s+/i, 'تابع السير في '], + [/^Continue straight\s+/i, 'تابع السير بشكل مستقيم في '], + [/^Continue straight$/i, 'تابع السير للأمام مباشرة'], + [/^Continue\s*$/i, 'تابع السير للأمام'], + + // Roundabouts + [/في الدوران\s*،\s*أتخذ مخرج\s+(\d+)\s+من خلال/i, 'عند الدوار، اسلك المخرج $1 عبر'], + [/في الدوران\s*،\s*اتخذ مخرج\s+(\d+)\s+من خلال/i, 'عند الدوار، اسلك المخرج $1 عبر'], + [/في الدوران\s*،\s*أتخذ مخرج\s+(\d+)/i, 'عند الدوار، اسلك المخرج $1'], + [/في الدوران\s*،\s*اتخذ مخرج\s+(\d+)/i, 'عند الدوار، اسلك المخرج $1'], + [/At roundabout, take exit\s+(\d+)\s+onto/i, 'عند الدوار، اسلك المخرج $1 عبر'], + [/At roundabout, take exit\s+(\d+)/i, 'عند الدوار، اسلك المخرج $1'], + [/In roundabout, take exit\s+(\d+)/i, 'عند الدوار، اسلك المخرج $1'], + + // Turns + [/^Turn sharp right onto\s+/i, 'انعطف يميناً بشكل حاد إلى '], + [/^Turn slight right onto\s+/i, 'انعطف يميناً بشكل طفيف إلى '], + [/^Turn right onto\s+/i, 'اتجه يميناً إلى '], + [/^Turn sharp right/i, 'انعطف يميناً بشكل حاد'], + [/^Turn slight right/i, 'انعطف يميناً بشكل طفيف'], + [/^Turn right/i, 'اتجه يميناً'], + + [/^Turn sharp left onto\s+/i, 'انعطف يساراً بشكل حاد إلى '], + [/^Turn slight left onto\s+/i, 'انعطف يساراً بشكل طفيف إلى '], + [/^Turn left onto\s+/i, 'اتجه يساراً إلى '], + [/^Turn sharp left/i, 'انعطف يساراً بشكل حاد'], + [/^Turn slight left/i, 'انعطف يساراً بشكل طفيف'], + [/^Turn left/i, 'اتجه يساراً'], + + // Keeps + [/^Keep right toward\s+/i, 'الزم اليمين باتجاه '], + [/^Keep right onto\s+/i, 'الزم اليمين في '], + [/^Keep right/i, 'الزم اليمين'], + [/^احفظ اليمين toward\s+/i, 'الزم اليمين باتجاه '], + [/^احفظ اليمين خلال\s+/i, 'الزم اليمين عبر '], + [/^احفظ اليمين/i, 'الزم اليمين'], + + [/^Keep left toward\s+/i, 'الزم اليسار باتجاه '], + [/^Keep left onto\s+/i, 'الزم اليسار في '], + [/^Keep left/i, 'الزم اليسار'], + [/^احفظ الشمال toward\s+/i, 'الزم اليسار باتجاه '], + [/^احفظ الشمال خلال\s+/i, 'الزم اليسار عبر '], + [/^احفظ الشمال/i, 'الزم اليسار'], + + // U-Turns + [/^Make a U-turn\s+/i, 'قم بالدوران للخلف (U-turn) عند '], + [/^Make a U-turn/i, 'قم بالدوران للخلف'], + [/^U-turn/i, 'دوران للخلف'], + + // Head / Bearings + [/^Head north onto\s+/i, 'اتجه شمالاً في '], + [/^Head south onto\s+/i, 'اتجه جنوباً في '], + [/^Head east onto\s+/i, 'اتجه شرقاً في '], + [/^Head west onto\s+/i, 'اتجه غرباً في '], + [/^Head northeast onto\s+/i, 'اتجه نحو الشمال الشرقي في '], + [/^Head northwest onto\s+/i, 'اتجه نحو الشمال الغربي في '], + [/^Head southeast onto\s+/i, 'اتجه نحو الجنوب الشرقي في '], + [/^Head southwest onto\s+/i, 'اتجه نحو الجنوب الغربي في '], + + [/^Head north/i, 'اتجه شمالاً'], + [/^Head south/i, 'اتجه جنوباً'], + [/^Head east/i, 'اتجه شرقاً'], + [/^Head west/i, 'اتجه غرباً'], + [/^Head northeast/i, 'اتجه شمال شرق'], + [/^Head northwest/i, 'اتجه شمال غرب'], + [/^Head southeast/i, 'اتجه جنوب شرق'], + [/^Head southwest/i, 'اتجه جنوب غرب'], + + // Toward / Onto / Through prepositions + [/\btoward\b/gi, 'باتجاه'], + [/\btowards\b/gi, 'باتجاه'], + [/\bonto\b/gi, 'في'], + [/\bthrough\b/gi, 'عبر'], + + // Destinations & Finish + [/^Arrive at destination/i, 'الوصول إلى الوجهة'], + [/^Reached destination/i, 'تم الوصول إلى الوجهة'], + [/^Destination/i, 'الوصول إلى الوجهة'], + [/^النهاية/i, 'الوصول إلى الوجهة'] + ]; + + for (const [regex, replacement] of phrases) { + text = text.replace(regex, replacement); + } + + // Clean up excessive whitespace + text = text.replace(/\s+/g, ' ').trim(); + + return text; + } + private estimateElevation(lat: number, lng: number): number { if (lng < 35.6 && lat < 32.2 && lat > 31.0) { return -400 + Math.abs(lng - 35.5) * 3000;