diff --git a/.github/workflows/translations-pull-request.yml b/.github/workflows/translations-pull-request.yml
index bd7875b2..75d67c08 100644
--- a/.github/workflows/translations-pull-request.yml
+++ b/.github/workflows/translations-pull-request.yml
@@ -26,7 +26,7 @@ jobs:
upload_translations: false
download_translations: true
- download_translations_args: '--language=it --language=es-ES --language=ko --language=zh-CN --language=ru --language=el --language=fr --language=uk --language=id --language=de --language=da --language=pt-PT --language=pl --language=ka'
+ download_translations_args: '--language=it --language=es-ES --language=ko --language=zh-CN --language=ru --language=el --language=fr --language=uk --language=id --language=de --language=da --language=pt-PT --language=pl --language=ka --language=ar'
localization_branch_name: update-translations-crowdin
push_translations: true
commit_message: 'Update translations'
diff --git a/package.json b/package.json
index f449da11..16c9f0d4 100644
--- a/package.json
+++ b/package.json
@@ -192,12 +192,14 @@
"resolutions": {},
"devDependencies": {
"@electron/rebuild": "^4.0.0",
+ "@eslint/js": "^9.0.0",
"@graphql-codegen/cli": "^6.0.0",
"@graphql-codegen/introspection": "^5.0.0",
+ "@graphql-codegen/typed-document-node": "^5.0.0",
"@graphql-codegen/typescript": "^5.0.0",
"@graphql-codegen/typescript-operations": "^5.0.0",
- "@graphql-codegen/typed-document-node": "^5.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.6.0",
+ "@stylistic/eslint-plugin": "^4.0.0",
"@teamsupercell/typings-for-css-modules-loader": "^2.5.2",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^16.0.0",
@@ -213,13 +215,11 @@
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/rimraf": "^4.0.5",
+ "@types/stylis": "^4.2.7",
"@types/uuid": "^9.0.7",
"@types/webpack-bundle-analyzer": "^4.6.3",
"@types/webpack-env": "^1.18.4",
"@types/webpack-node-externals": "^3.0.4",
- "@eslint/js": "^9.0.0",
- "@stylistic/eslint-plugin": "^4.0.0",
- "typescript-eslint": "^8.0.0",
"chalk": "^4.1.0",
"concurrently": "^9.0.0",
"core-js": "^3.35.0",
@@ -233,8 +233,8 @@
"eslint-plugin-jsx-a11y": "^6.10.0",
"eslint-plugin-react": "^7.37.0",
"eslint-plugin-react-hooks": "^5.0.0",
- "globals": "^16.0.0",
"file-loader": "^6.0.0",
+ "globals": "^16.0.0",
"html-webpack-plugin": "^5.6.0",
"husky": "^9.0.0",
"identity-obj-proxy": "^3.0.0",
@@ -252,6 +252,7 @@
"tsconfig-paths-webpack-plugin": "^4.1.0",
"tsx": "^4.21.0",
"typescript": "^5.6.2",
+ "typescript-eslint": "^8.0.0",
"url-loader": "^4.1.0",
"webpack": "^5.73.0",
"webpack-bundle-analyzer": "^5.0.0",
@@ -264,9 +265,11 @@
"@apollo/client": "^4.0.0",
"@apollo/server": "^5.0.0",
"@as-integrations/express5": "^1.0.0",
+ "@emotion/cache": "^11.14.0",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.8",
+ "@graphql-yoga/subscription": "^5.0.0",
"@mui/icons-material": "^7.0.0",
"@mui/material": "^7.0.0",
"@octokit/rest": "^22.0.0",
@@ -282,7 +285,6 @@
"get-port": "^5.1.1",
"graphql": "^16.7.1",
"graphql-scalars": "^1.23.0",
- "@graphql-yoga/subscription": "^5.0.0",
"graphql-tag": "^2.12.6",
"graphql-ws": "^6.0.6",
"i18next": "^26.0.0",
@@ -304,6 +306,8 @@
"semver": "^7.3.8",
"simple-git": "^3.28.0",
"source-map-support": "^0.5.21",
+ "stylis": "^4.4.0",
+ "stylis-plugin-rtl": "^2.1.1",
"ts-node": "^10.9.2",
"type-graphql": "2.0.0-rc.3",
"typedi": "^0.10.0",
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
index ad67b86f..6bfb1172 100644
--- a/src/i18n/index.ts
+++ b/src/i18n/index.ts
@@ -8,6 +8,12 @@ const loadPath = `${
searchParams.get('base_url') ?? 'http://localhost:3500/'
}/locales/{{lng}}/messages.json`;
+// Keep the document direction and language in sync (e.g. RTL for Arabic)
+i18n.on('languageChanged', (lng) => {
+ document.documentElement.dir = i18n.dir(lng);
+ document.documentElement.lang = lng;
+});
+
i18n
.use(Backend)
.use(LanguageDetector)
diff --git a/src/i18n/locales.json b/src/i18n/locales.json
index 513219b9..110b8ff5 100644
--- a/src/i18n/locales.json
+++ b/src/i18n/locales.json
@@ -62,5 +62,9 @@
{
"value": "ka",
"label": "ქართული (Georgian)"
+ },
+ {
+ "value": "ar",
+ "label": "العربية (Arabic)"
}
]
diff --git a/src/i18n/locales/ar/messages.json b/src/i18n/locales/ar/messages.json
new file mode 100644
index 00000000..5241eb26
--- /dev/null
+++ b/src/i18n/locales/ar/messages.json
@@ -0,0 +1,365 @@
+{
+ "BuildNotificationsList": {
+ "VerifyingBuildSystem": "جارٍ التحقق من نظام البناء",
+ "DownloadingFirmware": "تنزيل البرنامَج الثابت",
+ "BuildingUserDefinesTXT": "جارٍ إنشاء مِلَفّ user_defines.txt",
+ "CompilingFirmware": "جارٍ تجميع البرنامَج الثابت\n",
+ "FlashingDevice": "جارٍ تحديث الجهاز"
+ },
+ "FlashingStepper": {
+ "BuildOnlyDoneHint": "البرنامج الثابت جاهز. ارفعه عبر واجهة الويب الخاصة بالجهاز عبر Wi-Fi.",
+ "StockBlDoneHint": "انسخ ملف firmware.elrs إلى بطاقة SD الخاصة بجهاز التحكم، ثم اختر flash ext. ELRS من جهاز التحكم.",
+ "ShowLog": "إظهار السجل الكامل",
+ "Step": {
+ "VERIFYING_BUILD_SYSTEM": "جارٍ التحقق من بيئة البناء",
+ "DOWNLOADING_FIRMWARE": "تنزيل مصدر البرنامج الثابت",
+ "BUILDING_FIRMWARE": "جارٍ تنزيل البرنامَج الثابت\n",
+ "INITIATING_PASSTHROUGH": "جارٍ بدء وضع Passthrough",
+ "INITIATING_PASSTHROUGH_completed": "اكتمل وضع Passthrough",
+ "FLASHING_FIRMWARE": "تثبيت البرنامج الثابت على الجهاز"
+ },
+ "Status": {
+ "InstallingDependencies": "تثبيت أدوات البناء",
+ "CompilingFirmware": "جاري التجميع",
+ "PackagingFirmware": "جارٍ تجهيز ملف البرنامج الثابت",
+ "DetectingDevice": "جارٍ اكتشاف الجهاز",
+ "ConnectingToDevice": "جارٍ الاتصال بالجهاز",
+ "ErasingFlash": "جار مسح ذاكرة الفلاش",
+ "WritingFirmware": "جارٍ كتابة البرنامج الثابت",
+ "VerifyingFirmware": "جارٍ التحقق من البرنامج الثابت",
+ "RestartingDevice": "إعادة تشغيل الجهاز",
+ "UploadingFirmware": "تحميل البرنامج الثابت عبر Wi-Fi",
+ "TargetMismatch": "تم اختيار هدف خاطئ"
+ },
+ "Failed": {
+ "InstallingDependencies": "فشل تثبيت أدوات البناء",
+ "CompilingFirmware": "فشل التجميع",
+ "PackagingFirmware": "فشل تجهيز ملف البرنامج الثابت",
+ "DetectingDevice": "تعذر الكشف عن الجهاز",
+ "ConnectingToDevice": "تعذر الاتصال بالجهاز",
+ "ErasingFlash": "فشل مسح ذاكرة الفلاش",
+ "WritingFirmware": "فشل في كتابة البرنامج الثابت",
+ "VerifyingFirmware": "فشل التحقق من البرنامج الثابت",
+ "RestartingDevice": "فشل في إعادة تشغيل الجهاز",
+ "UploadingFirmware": "فشل تحميل البرنامج الثابت عبر Wi-Fi",
+ "TargetMismatch": "تم اختيار هدف خاطئ",
+ "Generic": "حدث خطأ ما"
+ }
+ },
+ "BuildResponse": {
+ "Error": "خطأ",
+ "GitDependencyError": "خطأ في تبعية Git",
+ "PythonDependencyError": "خطأ في تبعيات Python",
+ "PlatformioDependencyError": "خطأ في تبعيات PlatformIO",
+ "BuildError": "خطأ في البناء",
+ "FlashError": "خطأ في التحديث",
+ "TargetMismatch": "الهدف الذي تحاول تحديثه لا يتطابق مع الهدف الحالي للجهاز. إذا كنت متأكدًا من المتابعة، اضغط «Force Flash» أدناه.",
+ "Success": "تم بنجاح!",
+ "ErrorDetails": "حدث خطأ. راجع السجل أعلاه لمعرفة رسالة الخطأ بدقة. إذا لم تكن قد فعلت ذلك بعد، فانتقل إلى
إذا لم يتم اكتشاف اتصال RC خلال الوقت المحدد بعد بدء التشغيل، فستقوم الوحدة تلقائيًا بتفعيل نقطة اتصال Wi-Fi الخاصة بها. يتيح لك ذلك الاتصال بشبكة Wi-Fi الخاصة بالوحدة والدخول إلى 10.0.0.1 لإجراء تحديثات البرنامج الثابت.
يتم تحديد مهلة التفعيل التلقائي بوحدة الثواني. القيمة الافتراضية: 60 ثانية.
", + "TLMReportIntervalMs": "اجعل وحدة TX ترسل بيانات التليمترِي إلى جهاز التحكم على فاصل زمني ثابت. يساعد ذلك في تقليل تحذيرات 「Telemetry Lost」 عند استخدام نسبة تليمترِي مرتفعة أو معدلات منخفضة مثل 50Hz.
القيمة الافتراضية هي 240LU. إذا أردت تغييرها، يجب إضافة LU بعد قيمة المللي ثانية. على سبيل المثال، لتحديد معدل تحديث تليمترِي قدره 100 ms، أدخل القيمة بهذا الشكل: 100LU.
عادةً يُفضّل الإبقاء على 240LU لأجهزة التحكم التي تعمل بنظام EdgeTX/OpenTX، واستخدام 100LU للأجهزة التي تعمل بنظام ErskyTx.
", + "LockOnFirstConnection": "قفل وضع RF - في الوضع الافتراضي، يتنقّل RX بين أوضاع RF المتاحة مع انتظار 5 ثوانٍ بين كل وضع وآخر، بدءًا من أعلى وضع إلى أدناه، حتى يعثر على الوضع الذي يرسل عليه TX. يتيح ذلك لـ RX التنقّل بين الأوضاع، ولكن بمجرد إنشاء الاتصال، سيتوقف RX عن التنقّل بين أوضاع RF حتى تتم إعادة تشغيل الطاقة.\n", + "JustBeepOnce": "يحدد هذا الخيار ما إذا كان TX سيصدر صفيرًا مرة واحدة فقط بدلًا من تشغيل نغمة بدء التشغيل.\n", + "DisableStartupBeep": "يحدد هذا الخيار ما إذا كان TX سيبقى صامتًا عند بدء التشغيل.", + "DisableAllBeeps": "تعطيل جميع التنبيهات.", + "MyStartupMelody": "استخدم هذا الخيار لتحديد نغمة بدء التشغيل الخاصة بك باستخدام صيغة BlHeli32. يجب تحديد كلٍ من music string وbpm، بينما يُعد semitone offset اختياريًا ويمكن استخدامه لرفع النغمة بالكامل أو خفضها بعدد أنصاف النغمات المحدد. تتوفر أمثلة على نغمات BlHeli32 في
لإنشاء نغمتك الخاصة، يُعد كلٌ من
يدعم هذا الخيار الآن أيضًا النغمات بتنسيق RTTTL. ويُعد
وردت بعض التقارير عن عدم استقرار وحدات R9M عند استخدام قدرة أعلى من 250mW مع نظام التبريد الأصلي. ويرجع ذلك جزئيًا إلى أن ELRS يستخدم دورة تشغيل أعلى للإرسال مقارنة بالبرنامج الثابت الأصلي. افتراضيًا، تكون القدرة محدودة إلى 250mW. يمكنك فتح القدرة حتى 1000mW من خلال تفعيل الخيار التالي. استخدم هذا الخيار على مسؤوليتك الخاصة إذا لم تُجرِ أي تعديلات على نظام التبريد.
قمنا بنشر
هذا الخيار لا يفعّل بروتوكول SBUS.
يفيد هذا الخيار مع وحدات التحكم بالطيران F4 التي لا توفر خيار UART غير معكوس. يعمل الاتصال في اتجاه واحد فقط، لذلك ستفقد إرسال بيانات التليمترِي إلى جهاز التحكم، كما ستفقد إمكانية التفليش عبر Passthrough. سيتم إخراج الإشارة من منفذ S.BUS الافتراضي في R9MM/R9Mini. وقد تحتاج أيضًا إلى ضبط serialrx_inverted = ON داخل Betaflight لضمان التوافق.
", + "HomeWifiSSID": "تعيين اسم شبكة Wi-Fi الرئيسية (SSID). سيسمح هذا الجهاز بتشغيل Wi-Fi للاتصال بالشبكة الرئيسية تلقائياً.", + "HomeWifiPassword": "تعيين اسم شبكة Wi-Fi الرئيسية (SSID). سيسمح هذا الجهاز بتشغيل Wi-Fi للاتصال بالشبكة الرئيسية تلقائياً.", + "RxAsTx": "قم بتثبيت جهاز الاستقبال الخاص بك مع هذا الخيار مفعّل واستخدمه كوحدة TX." + }, + "UserDefinesAdvisor": { + "DisableUARTInvertedWarning": "تعطيل UART_INVERTED غير شائع. الرجاء التأكد من أن المرسل الخاص بك يدعم ذلك." + }, + "UserDefinesList": { + "BindingPhraseHistory": "", + "BindingPhraseHistoryRemove": "احذف من السجل", + "CustomBindingPhrase": "عبارة ربط مخصصة", + "MyStartupMelody": "نغمة بدء التشغيل الخاصة بي", + "Value": "قيمة" + }, + "WifiDeviceList": { + "Name": "الاسم", + "Target": "الهدف", + "Version": "الإصدار", + "Type": "النوع", + "DNS": "نظـام أسماء النطـاقات", + "IP": "عنوان IP", + "Select": "اختر" + }, + "WifiDeviceNotification": { + "NewDevice": "جهاز جديد", + "Select": "اختر" + }, + "WifiDeviceSelect": { + "DefaultWifiIp": "الافتراضي (10.0.0.1)", + "WifiDevices": "أجهزة WiFi", + "Manual": "دليل المستخدم", + "WifiDeviceSelection": "اختيار جهاز WiFi", + "IPAddress": "عنوان بروتوكول IP" + }, + "Updater": { + "FoundUpdates": "تم العثور على تحديثات", + "UpdateNow": "تم العثور على تحديثات، هل تريد التحديث الآن؟", + "Sure": "بالتأكيد", + "Later": "لاحقاً", + "UpdateDownloading": "جارٍ تنزيل التحديث", + "NotifiedWhenReadyToInstall": "يتم تحميل التحديث، سيتم إعلامك عندما تكون جاهزة للتثبيت.", + "Restart": "إعادة تشغيل", + "ApplicationUpdate": "تحديثات التطبيق", + "Update": "تحديث", + "ApplyUpdates": "تم تنزيل إصدار جديد. أعد تشغيل التطبيق لتطبيق التحديثات." + }, + "ConfiguratorView": { + "DownloadLUAScript": "تحميل البرنامج النصي LUA", + "FirmwareVersion": "إصدار البرنامج الثابت", + "Target": "الهدف", + "Notice": "تنويه", + "SelectFirmwareVersionFirst": "الرجاء تحديد إصدار البرنامج الثابت أولاً.", + "DeviceOptions": "خيارات الجهاز", + "ResetDevice": "إعادة تعيين خيارات الجهاز إلى الإعدادات الافتراضية الموصى بها على هدف هذا الجهاز. باستثناء عبارة الربط المخصصة.", + "Reset": "إعادة الضبط", + "Actions": "الإجراءات", + "FlashingOptions": "خيارات التحديث", + "EraseBeforeFlash": "مسح قبل التحديث", + "ForceFlash": "تحديث إجباري", + "Build": "بناء", + "Flash": "تحديث", + "NetworkDevices": "أجهزة الشبكة", + "DeviceSelectError": "خطأ في تحديد الجهاز", + "DeviceCouldNotBeAutomaticallySelected": "لا يمكن اختيار الجهاز المستهدف تلقائياً، ويجب أن يتم ذلك يدوياً.", + "Close": "إغلاق", + "Logs": "السجلات", + "CopyLogToClipboard": "نسخ السجل إلى الحافظة", + "SaveLogToFile": "حفظ السجل في مِلَفّ", + "BuildsTakeMinutesFirstTime": "في بعض الأحيان يستغرق البناء بضع دقائق على الأقل. إنه أمر طبيعي، خاصة لأول مرة.", + "Result": "النتيجة", + "Warning": "تحذير", + "WaitForLEDBeforeDisconnectingPower": "", + "UpdateLuaScript": "تحديث برنامج Lua Script", + "UpdateLuaScriptOnRadio": "تأكد من تحديث LUA SCRIPT على الراديو الخاص بك", + "BuildNotice": "اشعار البناء", + "FirmwareOpenedInFileExplorer": "تم فتح الملف الثنائي للبرنامج الثابت في مستكشف الملفات", + "Back": "عودة", + "FlashAnother": "تحديث آخر", + "Retry": "أعد المحاولة" + }, + "LogsView": { + "Logs": "السجلات", + "OpenLogsFolder": "فتح مجلد السجلات" + }, + "SerialMonitorView": { + "SerialMonitor": "مراقب المنفذ التسلسلي\n", + "Disconnect": "قطع الاتصال" + }, + "SettingsView": { + "Settings": "الاعدادات", + "LanguageSelector": "اختيار اللغة", + "CurrentLanguage": "اللغة الحالية", + "ExpertMode": "وضع الخبير", + "ApplicationOptions": "خيارات التطبيق", + "ExpertModeDescription": "تمكين وضع الخبير لفتح المميزات المتقدمة بما في ذلك مصادر البرامج الثابتة الإضافية. القدرة على بناء برنامج ثابت مع المعلمات المحددة يدويًا للمستخدم، والوصول إلى شاشة الرصد التسلسلية للحصول على تشخيصات مفصلة.", + "ThemeSelector": "مظهر", + "ThemeSystem": "النظام (وفق تفضيلات نظام التشغيل)", + "ThemeLight": "فاتح", + "ThemeDark": "داكن" + }, + "SupportView": { + "Support": "الدعم", + "JoinTheCommunity": "بحاجة إلى مساعدة؟ حائر ؟ انضم إلى المجتمع!", + "ExpressLRSDocumentation": "وثائق ExpressLRS", + "DiscordChat": "دردشة ديسكورد", + "FacebookGroup": "مجموعة الفيسبوك", + "Troubleshooting": "استكشاف الأخطاء وإصلاحها", + "LegalDisclaimer": "إخلاء المسؤولية القانونية", + "UseAtYourOwnRisk": "وقد يتطلب استخدام هذا النوع من الأجهزة وتشغيله ترخيص، وقد تحظر بعض البلدان استخدامه. والأمر متروك تماما للمستخدم النهائي لضمان الامتثال للأنظمة المحلية. هذا برنامج/معدات تجريبية ولا يوجد ضمان للاستقرار أو الموثوقية. استخدم على الفور RISK.", + "CorruptedFirmwareFiles": "ملفات البرامج الثابتة التالفة", + "YouCanManuallyClearAllFiles": "إذا أغلقت Configurator أثناء تنزيل Git لملفات البرنامج الثابت، فقد تتلف حالة مستودع Git. يمكنك مسح جميع الملفات يدويًا ثم بدء عملية البناء من جديد.", + "ClearFirmwareFiles": "مسح ملفات البرامج الثابتة", + "CorruptedFirmwareFilesDeleted": "تم حذف ملفات البرامج الثابتة الفاسدة وتم تنظيف مستودع git. حاول البناء مرة أخرى.", + "CorruptedPlatformioDependencies": "تبعيات PlatformIO تالفة", + "YouCanManuallyClearAllDependencies": "إذا أغلقت Configurator أثناء تنزيل Git لملفات البرنامج الثابت، فقد تتلف حالة مستودع Git. يمكنك مسح جميع الملفات يدويًا ثم بدء عملية البناء من جديد.", + "ClearPlatformioDependencies": "تبعيات PlatformIO تالفة", + "CorruptedPlatformioDependenciesDeleted": "تم حذف ملفات البرامج الثابتة الفاسدة وتم تنظيف مستودع git. حاول البناء مرة أخرى." + }, + "HomeView": { + "Flash": { + "Title": "تثبيت البرنامج الثابت", + "ExpressLRS": "البرنامج الثابت ExpressLRS", + "ExpressLRSDescription": "قم بتشغيل البرنامج الثابت الرئيسي ExExpressLRS إلى جهاز الإرسال أو جهاز الاستقبال.", + "Backpack": "Backpack", + "BackpackDescription": "قم بتحديث برنامج Backpack الثابت إلى وحدات TX أو مستقبلات الفيديو أو متتبعات الهوائيات أو مؤقتات السباقات." + }, + "Resources": { + "Title": "الموارد و البدء", + "GettingStarted": "الحصول على دليل البداية", + "GettingStartedDescription": "جديد إلى ExpressLRS؟ قراءة دليل البداية السريعة خطوة بخطوة.", + "ProductFinder": "الباحث عن المنتج", + "ProductFinderDescription": "العثور على الأجهزة المدعومة وتحديد هوية المرسل أو المستلم الخاص بك.", + "Documentation": "إرشادات الإستخدام", + "DocumentationDescription": "تصفح جميع وثائق ExpressLRS في expresslrs.org.", + "LuaScripts": "Lua Scripts", + "LuaScriptsDescription": "قم بتنزيل البرامج النصية لـ ExpressLRS Lua لـ EdgeTX و OpenTX." + }, + "Community": { + "Title": "إنضم إلى المجتمع", + "Subtitle": "الآلاف من الطيارين والمطورين موجودين هنا.", + "Discord": "ديسكورد", + "DiscordDescription": "انضم إلى المحادثة. احصل على المساعدة، شارك البناءات، واتصل بالآلاف من الطيارين والمطورين.", + "JoinDiscord": "انضم إلى ديسكورد", + "Facebook": "فيسبوك", + "FacebookDescription": "انضم إلى مجموعة الفيس بوك الخاصة بنا - واحدة من أكثر المجتمعات نشاطا في FPV مع الآلاف من الأعضاء.", + "JoinGroup": "الانضمام إلى المجموعة" + }, + "Support": { + "Title": "دعم المشروع", + "Subtitle": "سواء كنت تكتب الكود أو تحسن التوثيق أو أبلغ عن الأخطاء أو تدعم المشروع - كل المساهمة مهمة.", + "GitHub": "GitHub", + "GitHubDescription": "المساعدة في بناء مستقبل الـ RC. المدونة، التوثيق والاختبار - كل المساهمة مهمة.", + "Contribute": "المساهمة", + "OpenCollective": "افتح المجموعة", + "OpenCollectiveDescription": "دعم تطوير ExpressLRS بمساهمة لمرة واحدة أو متكررة.", + "SupportProject": "الدعم" + } + } +} diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 1215f743..c3f479c4 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -2,9 +2,14 @@ import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 're import { Routes, Route, HashRouter, Navigate } from 'react-router'; import CssBaseline from '@mui/material/CssBaseline'; import '@fontsource/roboto'; -import { ThemeProvider, StyledEngineProvider } from '@mui/material'; +import { ThemeProvider } from '@mui/material'; +import { CacheProvider } from '@emotion/react'; +import createCache from '@emotion/cache'; +import { prefixer } from 'stylis'; +import rtlPlugin from 'stylis-plugin-rtl'; +import { useTranslation } from 'react-i18next'; import { ApolloProvider } from '@apollo/client/react'; -import { createAppTheme } from './theme'; +import { createAppTheme, ThemeDirection } from './theme'; import client from './gql'; import ConfiguratorView from './views/ConfiguratorView'; import LogsView from './views/LogsView'; @@ -23,13 +28,31 @@ import useBuildProgressNotifications from './hooks/useBuildProgressNotifications import useBuildLogs from './hooks/useBuildLogs'; import useResolvedThemeMode from './hooks/useResolvedThemeMode'; +// Emotion caches per text direction. The RTL cache flips left/right styles +// (margins, paddings, positions) for right-to-left languages such as Arabic. +// `prepend: true` replaces StyledEngineProvider's injectFirst. +const emotionCaches: Record