From 5b6990e7a0567342b03bb76135242c877bea4690 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Fri, 24 Jul 2026 12:09:57 -0500 Subject: [PATCH 1/3] feat(desktop): add Arabic (ar) locale with RTL support Arabic is the desktop app's first right-to-left locale. The i18n provider now sets `document.dir`/`lang` from the active locale so Tailwind logical utilities flip automatically, and `ar` is registered in the catalog, language options, and alias table. The catalog is a partial `defineLocale` so keys added to English later fall back cleanly. Co-authored-by: 3ssiri Co-authored-by: Da7-Tech <286182457+Da7-Tech@users.noreply.github.com> --- apps/desktop/src/i18n/ar.ts | 2561 +++++++++++++++++++++++ apps/desktop/src/i18n/catalog.ts | 4 +- apps/desktop/src/i18n/context.test.tsx | 18 + apps/desktop/src/i18n/context.tsx | 12 + apps/desktop/src/i18n/languages.test.ts | 5 + apps/desktop/src/i18n/languages.ts | 17 +- apps/desktop/src/i18n/types.ts | 2 +- 7 files changed, 2616 insertions(+), 3 deletions(-) create mode 100644 apps/desktop/src/i18n/ar.ts diff --git a/apps/desktop/src/i18n/ar.ts b/apps/desktop/src/i18n/ar.ts new file mode 100644 index 000000000000..2d61c399998f --- /dev/null +++ b/apps/desktop/src/i18n/ar.ts @@ -0,0 +1,2561 @@ +import { defineLocale } from './define-locale' + +export const ar = defineLocale({ + common: { + apply: 'تطبيق', + back: 'رجوع', + save: 'حفظ', + saving: 'جار الحفظ...', + cancel: 'إلغاء', + change: 'تغيير', + choose: 'اختيار', + clear: 'مسح', + close: 'إغلاق', + collapse: 'طي', + confirm: 'تأكيد', + connect: 'اتصال', + connecting: 'جار الاتصال', + continue: 'متابعة', + copied: 'تم النسخ', + copy: 'نسخ', + copyFailed: 'فشل النسخ', + delete: 'حذف', + docs: 'الوثائق', + done: 'تم', + error: 'خطأ', + failed: 'فشل', + free: 'مجاني', + loading: 'جار التحميل...', + notSet: 'غير مضبوط', + refresh: 'تحديث', + remove: 'إزالة', + replace: 'استبدال', + retry: 'إعادة المحاولة', + run: 'تشغيل', + send: 'إرسال', + set: 'ضبط', + skip: 'تخطي', + update: 'تحديث', + on: 'مفعل', + off: 'معطل' + }, + fileMenu: { + revealFinder: 'إظهار في Finder', + revealExplorer: 'إظهار في File Explorer', + revealFileManager: 'فتح المجلد الحاوي', + revealInSidebar: 'إظهار في شجرة الملفات', + copyPath: 'نسخ المسار', + copyRelativePath: 'نسخ المسار النسبي', + rename: 'إعادة تسمية...', + delete: 'حذف', + renameTitle: 'إعادة تسمية', + renameLabel: 'الاسم الجديد', + deleteTitle: name => `حذف ${name}؟`, + deleteBody: 'سيتم نقله إلى سلة المهملات — يمكنك استعادته من هناك.', + pathCopied: 'تم نسخ المسار' + }, + boot: { + ready: 'Hermes Desktop جاهز', + desktopBootFailedWithMessage: message => `فشل تشغيل سطح المكتب: ${message}`, + steps: { + connectingGateway: 'جار الاتصال ببوابة سطح المكتب', + loadingSettings: 'جار تحميل إعدادات Hermes', + loadingSessions: 'جار تحميل الجلسات الأخيرة', + startingDesktopConnection: 'جار بدء اتصال سطح المكتب', + startingHermesDesktop: 'جار تشغيل Hermes Desktop...' + }, + errors: { + backgroundExited: 'خرجت عملية Hermes الخلفية.', + backgroundExitedDuringStartup: 'خرجت عملية Hermes الخلفية أثناء بدء التشغيل.', + backendStopped: 'توقف الخلفية', + desktopBootFailed: 'فشل تشغيل سطح المكتب', + gatewayConnectionLost: 'انقطع الاتصال بالبوابة', + gatewaySignInRequired: 'تسجيل الدخول للبوابة مطلوب', + ipcBridgeUnavailable: 'جسر IPC لسطح المكتب غير متاح.' + }, + failure: { + title: 'تعذر تشغيل Hermes', + description: 'لم تعمل البوابة الخلفية. جرب إحدى خطوات الاسترداد أدناه. لن يحذف ذلك محادثاتك أو إعداداتك.', + remoteTitle: 'تسجيل الدخول للبوابة البعيدة مطلوب', + remoteDescription: 'انتهت جلسة البوابة البعيدة. سجل الدخول مرة أخرى لإعادة الاتصال.', + retry: 'إعادة المحاولة', + repairInstall: 'إصلاح التثبيت', + useLocalGateway: 'استخدام البوابة المحلية', + openLogs: 'فتح السجلات', + repairHint: 'يعيد الإصلاح تشغيل المثبت وقد يستغرق بضع دقائق على جهاز جديد.', + remoteSignInHint: signInLabel => + `يسجّل الخروج من جلسة المتصفح البعيدة المحفوظة، ثم يفتح ${signInLabel}. استخدم البوابة المحلية للتبديل إلى الخلفية المضمنة.`, + signOutAndSignIn: 'تسجيل الخروج وإعادة تسجيل الدخول', + hideRecentLogs: 'إخفاء السجلات الأخيرة', + showRecentLogs: 'إظهار السجلات الأخيرة', + signedInTitle: 'تم تسجيل الدخول', + signedInMessage: 'جار إعادة الاتصال بالبوابة البعيدة...', + signInIncompleteTitle: 'تسجيل الدخول غير مكتمل', + signInIncompleteMessage: 'أغلقت نافذة تسجيل الدخول قبل اكتمال المصادقة.', + signInFailed: 'فشل تسجيل الدخول', + signInToRemoteGateway: 'تسجيل الدخول للبوابة البعيدة', + signInWithProvider: provider => `تسجيل الدخول عبر ${provider}`, + identityProvider: 'مزود الهوية' + } + }, + notifications: { + region: 'الإشعارات', + hide: 'إخفاء', + show: 'إظهار', + more: count => `${count} إشعار إضافي`, + clearAll: 'مسح الكل', + dismiss: 'إغلاق الإشعار', + details: 'التفاصيل', + copyDetail: 'نسخ التفاصيل', + copyDetailFailed: 'تعذر نسخ تفاصيل الإشعار', + backendOutOfDateTitle: 'الخلفية قديمة', + backendOutOfDateMessage: 'خلفية Hermes أقدم من إصدار سطح المكتب الحالي وقد لا تعمل كما يجب. حدثهما ليتوافقا.', + updateHermes: 'تحديث Hermes', + updateReadyTitle: 'التحديث جاهز', + updateReadyMessage: count => `${count} تغيير جديد متاح.`, + seeWhatsNew: 'عرض الجديد', + errors: { + elevenLabsNeedsKey: 'يتطلب ElevenLabs STT المفتاح ELEVENLABS_API_KEY.', + elevenLabsRejectedKey: 'رفض ElevenLabs مفتاح API (401).', + methodNotAllowed: 'رفضت خلفية سطح المكتب هذا الطلب (405 Method Not Allowed). جرب إعادة تشغيل Hermes Desktop.', + microphonePermission: 'تم رفض إذن الميكروفون.', + openaiRejectedApiKey: 'رفض OpenAI مفتاح API.', + openaiRejectedApiKeyWithStatus: status => `رفض OpenAI مفتاح API (${status} invalid_api_key).`, + openaiTtsNeedsKey: 'يتطلب OpenAI TTS المفتاح VOICE_TOOLS_OPENAI_KEY أو OPENAI_API_KEY.' + }, + voice: { + configureSpeechToText: 'اضبط تحويل الكلام إلى نص لاستخدام وضع الصوت.', + couldNotStartSession: 'تعذر بدء جلسة الصوت', + microphoneAccessDenied: 'تم رفض الوصول إلى الميكروفون.', + microphoneConstraintsUnsupported: 'قيود الميكروفون غير مدعومة على هذا الجهاز.', + microphoneFailed: 'فشل الميكروفون', + microphoneInUse: 'الميكروفون مستخدم من تطبيق آخر.', + microphonePermissionDenied: 'تم رفض إذن الميكروفون.', + microphoneStartFailed: 'تعذر بدء تسجيل الميكروفون.', + microphoneUnsupported: 'هذا المتصفح لا يدعم تسجيل الميكروفون.', + noMicrophone: 'لم يتم العثور على ميكروفون.', + noSpeechDetected: 'لم يتم اكتشاف كلام', + playbackFailed: 'فشل تشغيل الصوت', + recordingFailed: 'فشل التسجيل', + transcriptionFailed: 'فشل التفريغ النصي', + transcriptionUnavailable: 'التفريغ النصي غير متاح.', + tryRecordingAgain: 'حاول التسجيل مرة أخرى.', + unavailable: 'الصوت غير متاح' + }, + native: { + approvalTitle: 'مطلوب موافقة', + approveAction: 'موافقة', + rejectAction: 'رفض', + inputTitle: 'مطلوب إدخال', + inputBody: 'ينتظر Hermes ردّك.', + turnDoneTitle: 'أنهى Hermes', + turnDoneBody: 'الرد جاهز.', + turnErrorTitle: 'فشلت الجولة', + backgroundDoneTitle: 'انتهت المهمة في الخلفية', + backgroundFailedTitle: 'فشلت المهمة في الخلفية' + } + }, + remoteDisplayBanner: { + message: reason => `العرض البرمجي نشط — تم اكتشاف شاشة بعيدة (${reason}). تم تعطيل تسريع GPU لمنع الوميض.` + }, + titlebar: { + hideSidebar: 'إخفاء الشريط الجانبي', + showSidebar: 'إظهار الشريط الجانبي', + search: 'بحث', + searchTitle: 'البحث في الجلسات والعروض والإجراءات', + swapSidebarSides: 'تبديل جانبي الأشرطة', + swapSidebarSidesTitle: 'تبديل موضعي شريط الجلسات ومستكشف الملفات', + hideRightSidebar: 'إخفاء الشريط الأيمن', + showRightSidebar: 'إظهار الشريط الأيمن', + muteHaptics: 'كتم الاهتزازات', + unmuteHaptics: 'تفعيل الاهتزازات', + openSettings: 'فتح الإعدادات', + openStarmap: 'فتح خريطة الذاكرة', + openKeybinds: 'اختصارات لوحة المفاتيح', + layoutEditor: 'محرر التخطيط', + layoutEditorTitle: 'محرر التخطيط — انقر مع ⌘ لإعادة ضبط التخطيط' + }, + keybinds: { + title: 'اختصارات لوحة المفاتيح', + subtitle: open => `انقر على اختصار لإعادة تعيينه · ${open} يعيد فتح هذه اللوحة.`, + search: 'ابحث في الاختصارات...', + rebind: 'إعادة تعيين', + reset: 'إعادة إلى الافتراضي', + resetAll: 'إعادة تعيين الكل', + pressKey: 'اضغط مفتاحا...', + set: 'مضبوط', + conflictWith: label => `مرتبط أيضا بـ “${label}”`, + categories: { + composer: 'المحرّر', + profiles: 'الملفات الشخصية', + session: 'الجلسة', + navigation: 'التنقل', + view: 'العرض' + }, + actions: { + 'keybinds.openPanel': 'فتح اختصارات لوحة المفاتيح', + 'nav.commandPalette': 'فتح لوحة الأوامر', + 'nav.commandCenter': 'فتح مركز الأوامر', + 'nav.settings': 'فتح الإعدادات', + 'nav.profiles': 'فتح الملفات الشخصية', + 'nav.skills': 'فتح المهارات', + 'nav.messaging': 'فتح المراسلة', + 'nav.artifacts': 'فتح العناصر', + 'nav.cron': 'فتح المهام المجدولة', + 'nav.agents': 'فتح الوكلاء', + 'session.new': 'جلسة جديدة', + 'session.newTab': 'علامة تبويب جلسة جديدة', + 'session.newWindow': 'جلسة جديدة في نافذة', + 'session.next': 'الجلسة التالية', + 'session.prev': 'الجلسة السابقة', + 'session.slot.1': 'الانتقال إلى الجلسة الأخيرة 1', + 'session.slot.2': 'الانتقال إلى الجلسة الأخيرة 2', + 'session.slot.3': 'الانتقال إلى الجلسة الأخيرة 3', + 'session.slot.4': 'الانتقال إلى الجلسة الأخيرة 4', + 'session.slot.5': 'الانتقال إلى الجلسة الأخيرة 5', + 'session.slot.6': 'الانتقال إلى الجلسة الأخيرة 6', + 'session.slot.7': 'الانتقال إلى الجلسة الأخيرة 7', + 'session.slot.8': 'الانتقال إلى الجلسة الأخيرة 8', + 'session.slot.9': 'الانتقال إلى الجلسة الأخيرة 9', + 'session.focusSearch': 'البحث في الجلسات', + 'session.togglePin': 'تثبيت / إلغاء تثبيت الجلسة الحالية', + 'workspace.newWorktree': 'worktree جديد', + 'composer.focus': 'التركيز على المحرّر', + 'composer.modelPicker': 'فتح منتقي النموذج', + 'composer.voice': 'بدء / إيقاف المحادثة الصوتية', + 'view.toggleSidebar': 'تبديل الشريط الجانبي للجلسات', + 'view.toggleRightSidebar': 'تبديل متصفح الملفات', + 'view.toggleReview': 'تبديل لوحة المراجعة', + 'view.showFiles': 'إظهار متصفح الملفات', + 'view.showTerminal': 'إظهار الطرفية', + 'view.closeTab': 'إغلاق علامة التبويب', + 'view.reopenTab': 'إعادة فتح علامة التبويب المغلقة', + 'view.terminalSelection': 'إرسال تحديد الطرفية إلى المحرّر', + 'view.closePreviewTab': 'إغلاق علامة تبويب المعاينة', + 'view.flipPanes': 'تبديل جانبي الشريط الجانبي', + 'appearance.toggleMode': 'تبديل الفاتح / الداكن', + 'profile.default': 'الانتقال إلى الملف الشخصي الافتراضي', + 'profile.switch.1': 'الانتقال إلى الملف الشخصي 1', + 'profile.switch.2': 'الانتقال إلى الملف الشخصي 2', + 'profile.switch.3': 'الانتقال إلى الملف الشخصي 3', + 'profile.switch.4': 'الانتقال إلى الملف الشخصي 4', + 'profile.switch.5': 'الانتقال إلى الملف الشخصي 5', + 'profile.switch.6': 'الانتقال إلى الملف الشخصي 6', + 'profile.switch.7': 'الانتقال إلى الملف الشخصي 7', + 'profile.switch.8': 'الانتقال إلى الملف الشخصي 8', + 'profile.switch.9': 'الانتقال إلى الملف الشخصي 9', + 'profile.switch.10': 'الانتقال إلى الملف الشخصي 10', + 'profile.switch.11': 'الانتقال إلى الملف الشخصي 11', + 'profile.switch.12': 'الانتقال إلى الملف الشخصي 12', + 'profile.switch.13': 'الانتقال إلى الملف الشخصي 13', + 'profile.switch.14': 'الانتقال إلى الملف الشخصي 14', + 'profile.switch.15': 'الانتقال إلى الملف الشخصي 15', + 'profile.switch.16': 'الانتقال إلى الملف الشخصي 16', + 'profile.switch.17': 'الانتقال إلى الملف الشخصي 17', + 'profile.switch.18': 'الانتقال إلى الملف الشخصي 18', + 'profile.next': 'الملف الشخصي التالي', + 'profile.prev': 'الملف الشخصي السابق', + 'profile.toggleAll': 'تبديل عرض كل الملفات الشخصية', + 'profile.create': 'إنشاء ملف شخصي', + 'composer.send': 'إرسال الرسالة', + 'composer.newline': 'إدراج سطر جديد', + 'composer.steer': 'توجيه الدور الجاري', + 'composer.sendQueued': 'إرسال الدور التالي في الطابور', + 'composer.mention': 'الإشارة إلى الملفات والمجلدات وعناوين URL', + 'composer.slash': 'لوحة الأوامر المائلة', + 'composer.help': 'مساعدة سريعة', + 'composer.history': 'التنقل في النافذة المنبثقة / السجل', + 'composer.cancel': 'إغلاق النافذة المنبثقة · إلغاء التشغيل' + } + }, + language: { + label: 'اللغة', + description: 'اختر لغة واجهة سطح المكتب.', + saving: 'جار حفظ اللغة...', + saveError: 'تعذر تحديث اللغة', + switchTo: 'تغيير اللغة', + searchPlaceholder: 'ابحث عن لغة...', + noResults: 'لا توجد لغة مطابقة' + }, + settings: { + closeSettings: 'إغلاق الإعدادات', + exportConfig: 'تصدير الإعدادات', + importConfig: 'استيراد الإعدادات', + resetToDefaults: 'إعادة الافتراضيات', + resetConfirm: 'هل تريد إعادة كل الإعدادات إلى افتراضيات Hermes؟', + exportFailed: 'فشل التصدير', + resetFailed: 'فشلت إعادة الضبط', + nav: { + providers: 'المزودون', + providerAccounts: 'الحسابات', + providerApiKeys: 'مفاتيح API', + gateway: 'البوابة', + apiKeys: 'الأدوات والمفاتيح', + keysTools: 'الأدوات', + keysSettings: 'الإعدادات', + mcp: 'MCP', + archivedChats: 'المحادثات المؤرشفة', + about: 'حول', + notifications: 'الإشعارات', + keybinds: 'اختصارات لوحة المفاتيح', + plugins: 'الإضافات' + }, + plugins: { + title: 'إضافات سطح المكتب', + blurb: + 'امتدادات واجهة تُحمّل داخل هذا التطبيق — إما مضمّنة مع البناء، أو موضوعة في مجلد desktop-plugins (بما فيها التي يكتبها Hermes). تعطيل الإضافة يفرغها مباشرة ويبقى بعد إعادة التشغيل.', + count: n => `${n} مثبتة`, + openFolder: 'فتح مجلد الإضافات', + rescan: 'إعادة الفحص', + reveal: 'إظهار في مدير الملفات', + enable: 'تفعيل', + disable: 'تعطيل', + failed: 'فشل', + empty: 'لا توجد إضافات سطح مكتب مثبتة بعد.', + kinds: { bundled: 'مضمّنة', disk: 'على القرص', runtime: 'وقت التشغيل' } + }, + notifications: { + title: 'الإشعارات', + intro: + 'إشعارات سطح المكتب الأصلية، منفصلة عن التنبيهات داخل التطبيق. هذه محلية على الجهاز — كل حاسوب يحتفظ بإعداداته الخاصة.', + enableAll: 'تفعيل الإشعارات', + enableAllDesc: 'مفتاح رئيسي. أوقفه لإسكات كل الإشعارات أدناه.', + focusedHint: 'تنبيهات الاكتمال تظهر فقط عندما يكون Hermes في الخلفية.', + kinds: { + approval: { + label: 'يلزم الموافقة', + description: 'هناك أمر ينتظر موافقتك أو رفضك له.' + }, + input: { + label: 'يلزم إدخال', + description: 'طرح Hermes سؤالا أو يحتاج إلى كلمة مرور أو سر.' + }, + turnDone: { + label: 'الرد جاهز', + description: 'انتهى دور بينما كان Hermes في الخلفية.' + }, + turnError: { + label: 'فشل الدور', + description: 'انتهى دور بخطأ.' + }, + backgroundDone: { + label: 'اكتملت مهمة الخلفية', + description: 'اكتمل أمر طرفية يعمل في الخلفية.' + } + }, + test: 'إرسال إشعار تجريبي', + testTitle: 'Hermes', + testBody: 'الإشعارات تعمل.', + testSent: 'تم إرسال التجربة. إذا لم يظهر شيء، تحقق من أذونات الإشعارات في نظام التشغيل ووضع التركيز/عدم الإزعاج.', + testUnsupported: 'هذا النظام لا يدعم الإشعارات الأصلية.', + completionSoundTitle: 'صوت الاكتمال', + completionSoundDesc: 'يُشغّل عند انتهاء دور الوكيل. اختر إعدادا مسبقا وعاينه هنا.', + completionSoundPreview: 'معاينة' + }, + sections: { + model: 'النموذج', + chat: 'المحادثة', + appearance: 'المظهر', + workspace: 'مساحة العمل', + safety: 'الأمان', + memory: 'الذاكرة والسياق', + voice: 'الصوت', + advanced: 'متقدم' + }, + searchPlaceholder: { + about: 'حول Hermes Desktop', + config: 'ابحث في الإعدادات...', + gateway: 'اتصال البوابة...', + keys: 'ابحث في مفاتيح API...', + mcp: 'ابحث في خوادم MCP...', + sessions: 'ابحث في المحادثات المؤرشفة...' + }, + modeOptions: { + light: { + label: 'فاتح', + description: 'أسطح سطح مكتب ساطعة' + }, + dark: { + label: 'داكن', + description: 'مساحة عمل منخفضة الوهج' + }, + system: { + label: 'النظام', + description: 'اتباع مظهر نظام التشغيل' + } + }, + appearance: { + title: 'المظهر', + intro: 'خصص مظهر Hermes Desktop.', + colorMode: 'نمط الألوان', + colorModeDesc: 'اختر الوضع الفاتح أو الداكن أو اتبع النظام.', + toolViewTitle: 'عرض الأدوات', + toolViewDesc: 'تحكم في كيفية عرض نشاط الأدوات داخل المحادثة.', + translucencyTitle: 'شفافية النافذة', + translucencyDesc: 'إظهار سطح المكتب من خلال النافذة بالكامل. متاح على macOS وWindows فقط.', + backdropTitle: 'خلفية النافذة', + backdropDesc: 'اختيار مقدار مزج خلفية سطح المكتب مع سطح Hermes.', + embedsTitle: 'التضمينات المضمّنة', + embedsDesc: + 'تُحمّل المعاينات الغنية من مواقع طرف ثالث (YouTube، X، …). "اسأل" يعرض عنصرا نائبا حتى تسمح لكل واحد؛ "دائما" يحمّلها تلقائيا؛ "إيقاف" يبقي الروابط عادية.', + embedsAsk: 'اسأل', + embedsAlways: 'دائما', + embedsOff: 'إيقاف', + embedsReset: count => `إعادة تعيين ${count} ${count === 1 ? 'خدمة مسموح بها' : 'خدمة مسموح بها'}`, + product: 'مبسط', + productDesc: 'عرض أنظف يركز على النتيجة.', + technical: 'تقني', + technicalDesc: 'يعرض تفاصيل أكثر عن الأدوات والتنفيذ.', + themeTitle: 'الثيم', + themeDesc: 'اختر ثيم سطح المكتب.', + themeProfileNote: profile => `سيطبق هذا الثيم على الملف الشخصي ${profile}.`, + installTitle: 'تثبيت ثيم', + installDesc: 'ألصق رابط ثيم أو اسمه لتثبيته.', + installPlaceholder: 'رابط أو اسم الثيم', + installButton: 'تثبيت', + installing: 'جار التثبيت...', + installError: 'فشل تثبيت الثيم', + installed: name => `تم تثبيت ${name}`, + removeTheme: 'إزالة الثيم', + importedBadge: 'مستورد', + pet: { + title: 'حيوان أليف', + intro: + 'تبنَّ تعويذة petdex متحركة تطفو فوق التطبيق وتتفاعل مع ما يفعله Hermes — تجري أثناء تنفيذ الأدوات، وتحتفل عند النجاح، وتعبس عند الأخطاء.', + restartHint: + 'تحتاج الحيوانات الأليفة إلى إعادة تشغيل سريعة — بدأ التطبيق العامل قبل إضافة هذه الميزة. أغلق Hermes وأعد فتحه، ثم ارجع إلى هنا.', + on: 'تشغيل', + off: 'إيقاف', + scaleTitle: 'الحجم', + scaleDesc: 'غيّر حجم التعويذة الطافية. يُطبّق في كل مكان فورا.', + chooseTitle: 'اختر حيوانا أليفا', + chooseDesc: 'اختياره يثبّته (إن لزم) ويجعله نشطا.', + searchPlaceholder: 'البحث عن حيوانات أليفة...', + unreachable: 'تعذّر الوصول إلى معرض petdex. تحقق من اتصالك وأعد فتح هذه الصفحة.', + noMatch: query => `لا توجد حيوانات أليفة تطابق "${query}".`, + installedTag: 'مثبّت', + generatedTag: 'مُولّد', + countCapped: (cap, total) => `عرض ${cap} من ${total} — اكتب لتضييق النتائج.`, + count: n => `${n} ${n === 1 ? 'حيوان أليف' : 'حيوان أليف'}.`, + uninstall: name => `إلغاء تثبيت ${name}`, + delete: name => `حذف ${name}`, + deleteTitle: name => `حذف ${name}؟`, + deleteBody: 'هذا يحذف الحيوان الأليف نهائيا — لا يمكن إعادة تثبيته.', + deleteConfirm: 'حذف', + rename: name => `إعادة تسمية ${name}`, + renameTitle: 'إعادة تسمية الحيوان الأليف', + renamePlaceholder: 'سمِّ حيوانك الأليف', + renameSave: 'حفظ', + exportPet: name => `تصدير ${name}`, + adoptFailed: slug => `تعذّر تبني ${slug}`, + uninstallFailed: slug => `تعذّر إلغاء تثبيت ${slug}`, + renameFailed: slug => `تعذّرت إعادة تسمية ${slug}`, + exportFailed: slug => `تعذّر تصدير ${slug}`, + noneAvailable: 'لا توجد حيوانات أليفة متاحة للتشغيل الآن.', + turnOnFailed: 'تعذّر تشغيل الحيوان الأليف.', + turnOffFailed: 'تعذّر إيقاف الحيوان الأليف.' + } + }, + fieldLabels: { + model: 'النموذج الافتراضي', + modelContextLength: 'نافذة السياق', + fallbackProviders: 'النماذج الاحتياطية', + toolsets: 'مجموعات الأدوات المفعلة', + timezone: 'المنطقة الزمنية', + 'display.personality': 'أسلوب المساعد', + 'display.showReasoning': 'إظهار التفكير', + 'agent.maxTurns': 'الحد الأقصى لخطوات الوكيل', + 'agent.imageInputMode': 'مرفقات الصور', + 'agent.apiMaxRetries': 'محاولات API', + 'agent.serviceTier': 'مستوى الخدمة', + 'agent.toolUseEnforcement': 'فرض استخدام الأدوات', + 'terminal.cwd': 'مجلد العمل', + 'terminal.backend': 'خلفية التنفيذ', + 'terminal.timeout': 'مهلة الأوامر', + 'terminal.persistentShell': 'صدفة مستمرة', + 'terminal.envPassthrough': 'تمرير متغيرات البيئة', + 'terminal.dockerImage': 'صورة Docker', + 'terminal.singularityImage': 'صورة Singularity', + 'terminal.modalImage': 'صورة Modal', + 'terminal.daytonaImage': 'صورة Daytona', + fileReadMaxChars: 'حد قراءة الملفات', + 'toolOutput.maxBytes': 'حد مخرجات الطرفية', + 'toolOutput.maxLines': 'حد صفحات الملفات', + 'toolOutput.maxLineLength': 'حد طول السطر', + 'codeExecution.mode': 'نمط تنفيذ الكود', + 'approvals.mode': 'نمط الموافقات', + 'approvals.timeout': 'مهلة الموافقة', + 'approvals.mcpReloadConfirm': 'تأكيد إعادة تحميل MCP', + commandAllowlist: 'قائمة الأوامر المسموحة', + 'security.redactSecrets': 'إخفاء الأسرار', + 'security.allowPrivateUrls': 'السماح بالروابط الخاصة', + 'browser.allowPrivateUrls': 'روابط المتصفح الخاصة', + 'browser.autoLocalForPrivateUrls': 'متصفح محلي للروابط الخاصة', + 'checkpoints.enabled': 'نقاط حفظ الملفات', + 'checkpoints.maxSnapshots': 'حد نقاط الحفظ', + 'voice.recordKey': 'اختصار الصوت', + 'voice.maxRecordingSeconds': 'أقصى مدة للتسجيل', + 'voice.autoTts': 'قراءة الردود صوتياً', + 'stt.enabled': 'تحويل الكلام إلى نص', + 'stt.provider': 'مزود تحويل الكلام إلى نص', + 'stt.local.model': 'نموذج التفريغ المحلي', + 'stt.local.language': 'لغة التفريغ', + 'stt.openai.model': 'نموذج OpenAI STT', + 'stt.groq.model': 'نموذج Groq STT', + 'stt.mistral.model': 'نموذج Mistral STT', + 'stt.elevenlabs.modelId': 'نموذج ElevenLabs STT', + 'stt.elevenlabs.languageCode': 'لغة ElevenLabs', + 'stt.elevenlabs.tagAudioEvents': 'وسم أحداث الصوت', + 'stt.elevenlabs.diarize': 'تمييز المتحدثين', + 'tts.provider': 'مزود تحويل النص إلى كلام', + 'tts.edge.voice': 'صوت Edge', + 'tts.openai.model': 'نموذج OpenAI TTS', + 'tts.openai.voice': 'صوت OpenAI', + 'tts.elevenlabs.voiceId': 'صوت ElevenLabs', + 'tts.elevenlabs.modelId': 'نموذج ElevenLabs', + 'tts.xai.voiceId': 'صوت xAI (Grok)', + 'tts.xai.language': 'لغة xAI', + 'tts.minimax.model': 'نموذج MiniMax TTS', + 'tts.minimax.voiceId': 'صوت MiniMax', + 'tts.mistral.model': 'نموذج Mistral TTS', + 'tts.mistral.voiceId': 'صوت Mistral', + 'tts.gemini.model': 'نموذج Gemini TTS', + 'tts.gemini.voice': 'صوت Gemini', + 'tts.neutts.model': 'نموذج NeuTTS', + 'tts.neutts.device': 'جهاز NeuTTS', + 'tts.kittentts.model': 'نموذج KittenTTS', + 'tts.kittentts.voice': 'صوت KittenTTS', + 'tts.piper.voice': 'صوت Piper', + 'memory.memoryEnabled': 'الذاكرة المستمرة', + 'memory.userProfileEnabled': 'ملف المستخدم', + 'memory.memoryCharLimit': 'ميزانية الذاكرة', + 'memory.userCharLimit': 'ميزانية الملف الشخصي', + 'memory.provider': 'مزود الذاكرة', + 'context.engine': 'محرك السياق', + 'compression.enabled': 'الضغط التلقائي', + 'compression.threshold': 'عتبة الضغط', + 'compression.targetRatio': 'هدف الضغط', + 'compression.protectLastN': 'الرسائل الأخيرة المحمية', + 'delegation.model': 'نموذج الوكيل الفرعي', + 'delegation.provider': 'مزود الوكيل الفرعي', + 'delegation.maxIterations': 'حد دورات الوكيل الفرعي', + 'delegation.maxConcurrentChildren': 'الوكلاء الفرعيون المتوازيون', + 'delegation.childTimeoutSeconds': 'مهلة الوكيل الفرعي', + 'delegation.reasoningEffort': 'جهد تفكير الوكيل الفرعي', + 'updates.nonInteractiveLocalChanges': 'تغييرات التحديث داخل التطبيق' + }, + fieldDescriptions: { + model: 'يستخدم في المحادثات الجديدة ما لم تختر نموذجاً مختلفاً من محرر الرسائل.', + modelContextLength: 'اتركه 0 لاستخدام نافذة السياق المكتشفة للنموذج المحدد.', + fallbackProviders: 'إدخالات احتياطية بصيغة provider:model لتجربتها إذا فشل النموذج الافتراضي.', + 'display.personality': 'أسلوب المساعد الافتراضي للجلسات الجديدة.', + 'display.showReasoning': 'يعرض أقسام التفكير عندما توفرها الخلفية.', + timezone: 'تستخدم عندما يحتاج Hermes إلى سياق الوقت المحلي. اتركها فارغة لاستخدام منطقة النظام.', + 'agent.imageInputMode': 'يتحكم في طريقة إرسال مرفقات الصور إلى النموذج.', + 'agent.maxTurns': 'الحد الأعلى لدورات استدعاء الأدوات قبل أن يوقف Hermes التشغيل.', + 'terminal.cwd': 'مجلد المشروع الافتراضي لعمل الأدوات والطرفية.', + 'terminal.persistentShell': 'يحافظ على حالة الصدفة بين الأوامر عندما تدعمها الخلفية.', + 'terminal.envPassthrough': 'متغيرات البيئة التي تمرر إلى تنفيذ الأدوات.', + 'terminal.dockerImage': 'صورة الحاوية المستخدمة عند اختيار Docker كخلفية تنفيذ.', + 'terminal.singularityImage': 'الصورة المستخدمة عند اختيار Singularity.', + 'terminal.modalImage': 'الصورة المستخدمة عند اختيار Modal.', + 'terminal.daytonaImage': 'الصورة المستخدمة عند اختيار Daytona.', + 'codeExecution.mode': 'مدى تقييد تنفيذ الكود بالمشروع الحالي.', + fileReadMaxChars: 'أقصى عدد أحرف يستطيع Hermes قراءته من ملف واحد في الطلب.', + 'approvals.mode': 'كيف يتعامل Hermes مع الأوامر التي تحتاج موافقة صريحة.', + 'approvals.timeout': 'مدة انتظار طلبات الموافقة قبل انتهاء المهلة.', + 'security.redactSecrets': 'يخفي الأسرار المكتشفة من المحتوى المرئي للنموذج قدر الإمكان.', + 'checkpoints.enabled': 'ينشئ لقطات رجوع قبل تعديلات الملفات.', + 'memory.memoryEnabled': 'يحفظ ذكريات دائمة يمكن أن تساعد الجلسات القادمة.', + 'memory.userProfileEnabled': 'يحافظ على ملف مختصر لتفضيلات المستخدم.', + 'context.engine': 'استراتيجية إدارة المحادثات الطويلة قرب حد السياق.', + 'compression.enabled': 'يلخص السياق الأقدم عندما تكبر المحادثات.', + 'voice.autoTts': 'ينطق ردود المساعد تلقائياً.', + 'tts.xai.voiceId': 'معرف صوت xAI مثل eve أو معرف صوت مخصص.', + 'tts.xai.language': 'رمز لغة النطق، مثل en.', + 'tts.neutts.device': 'جهاز الاستدلال المحلي لـ NeuTTS.', + 'stt.enabled': 'يفعل التفريغ الصوتي المحلي أو عبر مزود.', + 'stt.elevenlabs.languageCode': 'رمز لغة ISO-639-3 اختياري. اتركه فارغاً للاكتشاف التلقائي.', + 'updates.nonInteractiveLocalChanges': + 'عندما يحدّث Hermes نفسه من التطبيق دون موجه طرفية، احتفظ بتعديلات المصدر المحلية أو تجاهلها.' + }, + about: { + heading: 'حول Hermes', + version: value => `الإصدار ${value}`, + versionUnavailable: 'الإصدار غير متاح', + updates: 'التحديثات', + checkNow: 'التحقق الآن', + checking: 'جار التحقق...', + seeWhatsNew: 'عرض الجديد', + updateNow: 'تحديث الآن', + releaseNotes: 'ملاحظات الإصدار', + onLatest: 'أنت على أحدث إصدار', + installing: 'جار التثبيت...', + cantUpdate: 'تعذر التحديث', + cantReach: 'تعذر الوصول لخدمة التحديث', + tapCheck: 'اضغط للتحقق من التحديثات.', + updateReady: count => `${count} تحديث متاح`, + lastChecked: age => `آخر تحقق ${age}`, + justNowSuffix: 'الآن', + automaticUpdates: 'التحديثات التلقائية', + automaticUpdatesDesc: 'اسمح لـ Hermes بالتحقق من التحديثات وتثبيتها.', + branchCommit: (branch, commit) => `${branch} عند ${commit}`, + never: 'أبدا', + justNow: 'الآن', + minAgo: count => `قبل ${count} دقيقة`, + hoursAgo: count => `قبل ${count} ساعة`, + daysAgo: count => `قبل ${count} يوم` + }, + config: { + none: 'لا شيء', + noneParen: '(لا شيء)', + notSet: 'غير مضبوط', + commaSeparated: 'قيم مفصولة بفواصل', + loading: 'جار تحميل إعدادات Hermes...', + emptyTitle: 'لا توجد إعدادات', + emptyDesc: 'لا يحتوي هذا القسم على إعدادات قابلة للتعديل.', + failedLoad: 'فشل تحميل الإعدادات', + autosaveFailed: 'فشل الحفظ التلقائي', + imported: 'تم استيراد الإعدادات', + invalidJson: 'JSON غير صالح' + }, + credentials: { + pasteKey: 'لصق المفتاح', + pasteLabelKey: label => `لصق مفتاح ${label}`, + optional: 'اختياري', + enterValueFirst: 'أدخل قيمة أولا.', + couldNotSave: 'تعذّر حفظ بيانات الاعتماد.', + remove: 'إزالة', + getKey: 'احصل على مفتاح', + saving: 'جار الحفظ' + }, + envActions: { + actionsFor: label => `إجراءات ${label}`, + credentialActions: 'إجراءات بيانات الاعتماد', + docs: 'الوثائق', + hideValue: 'إخفاء القيمة', + revealValue: 'إظهار القيمة', + replace: 'استبدال', + set: 'ضبط', + clear: 'مسح' + }, + gateway: { + loading: 'جار تحميل إعدادات البوابة...', + unavailableTitle: 'إعدادات البوابة غير متاحة', + unavailableDesc: 'جسر IPC في سطح المكتب لا يوفّر إعدادات البوابة.', + title: 'اتصال البوابة', + envOverride: 'تجاوز من البيئة', + intro: + 'يشغّل Hermes Desktop بوابة محلية خاصة افتراضياً. استخدم بوابة بعيدة عندما تريد أن يتحكم هذا التطبيق بخلفية Hermes تعمل مسبقاً على جهاز آخر أو خلف وكيل موثوق.', + appliesTo: 'ينطبق على', + allProfiles: 'كل الملفات الشخصية', + defaultConnection: 'الاتصال الافتراضي لكل ملف شخصي لا يملك تجاوزاً خاصاً.', + profileConnection: profile => + `الاتصال المستخدم فقط عندما يكون "${profile}" هو الملف الشخصي النشط. اضبطه على محلي ليرث الافتراضي.`, + envOverrideTitle: 'متغيرات البيئة تتحكم في جلسة سطح المكتب هذه.', + envOverrideDesc: 'أزل HERMES_DESKTOP_REMOTE_URL و HERMES_DESKTOP_REMOTE_TOKEN لاستخدام الإعداد المحفوظ أدناه.', + localTitle: 'بوابة محلية', + localDesc: 'تشغيل خلفية Hermes خاصة على localhost. هذا هو الافتراضي ويعمل دون اتصال.', + remoteTitle: 'بوابة بعيدة', + remoteDesc: + 'صل واجهة سطح المكتب هذه بخلفية Hermes بعيدة. البوابات المستضافة تستخدم OAuth أو اسم مستخدم وكلمة مرور، والبوابات الذاتية قد تستخدم رمز جلسة.', + remoteUrlTitle: 'رابط البوابة البعيدة', + remoteUrlDesc: 'الرابط الأساسي لخلفية لوحة التحكم البعيدة. يمكن استخدام بادئات مسار مثل /hermes.', + probing: 'جار فحص طريقة مصادقة هذه البوابة...', + probeError: 'تعذر الوصول إلى هذه البوابة الآن. تحقق من الرابط وستظهر طريقة المصادقة عند الاستجابة.', + signedIn: 'تم تسجيل الدخول', + signIn: 'تسجيل الدخول', + signOut: 'تسجيل الخروج', + signInWith: provider => `تسجيل الدخول عبر ${provider}`, + authTitle: 'المصادقة', + authSignedInPassword: 'هذه البوابة تستخدم اسم مستخدم وكلمة مرور. أنت مسجل الدخول والجلسة تتجدد تلقائياً.', + authSignedInOauth: 'هذه البوابة تستخدم OAuth. أنت مسجل الدخول والجلسة تتجدد تلقائياً.', + authNeedsPassword: 'هذه البوابة تستخدم اسم مستخدم وكلمة مرور. سجل الدخول لتفويض تطبيق سطح المكتب.', + authNeedsOauth: provider => `هذه البوابة تستخدم OAuth. سجل الدخول عبر ${provider} لتفويض تطبيق سطح المكتب.`, + tokenTitle: 'رمز الجلسة', + tokenDesc: 'رمز جلسة لوحة التحكم المستخدم لطلبات REST و WebSocket. اتركه فارغاً للإبقاء على الرمز المحفوظ.', + existingToken: value => `رمز موجود ${value}`, + savedToken: 'محفوظ', + pasteSessionToken: 'ألصق رمز الجلسة', + testRemote: 'اختبار البعيد', + saveForRestart: 'حفظ للتشغيل القادم', + saveAndReconnect: 'حفظ وإعادة الاتصال', + diagnostics: 'التشخيصات', + diagnosticsDesc: 'اعرض desktop.log في مدير الملفات، وهذا مفيد عند فشل تشغيل البوابة.', + openLogs: 'فتح السجلات', + incompleteTitle: 'إعداد البوابة البعيدة غير مكتمل', + incompleteSignIn: 'أدخل رابط البوابة البعيدة وسجل الدخول قبل التبديل إلى البعيد.', + incompleteToken: 'أدخل رابط البوابة البعيدة ورمز الجلسة قبل التبديل إلى البعيد.', + incompleteSignInTest: 'أدخل رابط البوابة البعيدة وسجل الدخول قبل الاختبار.', + incompleteTokenTest: 'أدخل رابط البوابة البعيدة ورمز الجلسة قبل الاختبار.', + enterUrlFirst: 'أدخل رابط البوابة البعيدة أولاً.', + restartingTitle: 'جار إعادة تشغيل اتصال البوابة', + savedTitle: 'تم حفظ إعدادات البوابة', + restartingMessage: 'سيعيد Hermes Desktop الاتصال باستخدام الإعدادات المحفوظة.', + savedMessage: 'تم الحفظ للتشغيل القادم.', + connectedTo: (baseUrl, version) => `متصل بـ ${baseUrl}${version ? ` · Hermes ${version}` : ''}`, + reachableTitle: 'البوابة البعيدة قابلة للوصول', + signedOutTitle: 'تم تسجيل الخروج', + signedOutMessage: 'تم مسح جلسة البوابة البعيدة.', + failedLoad: 'فشل تحميل إعدادات البوابة', + signInFailed: 'فشل تسجيل الدخول', + signOutFailed: 'فشل تسجيل الخروج', + testFailed: 'فشل اختبار البوابة البعيدة', + applyFailed: 'تعذر تطبيق إعدادات البوابة', + saveFailed: 'تعذر حفظ إعدادات البوابة' + }, + keys: { + loading: 'جار تحميل مفاتيح API وبيانات الاعتماد...', + failedLoad: 'فشل تحميل مفاتيح API', + empty: 'لا يوجد شيء مضبوط في هذه الفئة بعد.' + }, + mcp: { + loading: 'جار تحميل خوادم MCP...', + failedLoad: 'فشل تحميل إعدادات MCP', + nameRequiredTitle: 'الاسم مطلوب', + nameRequiredMessage: 'أعط هذا الخادم مفتاح إعداد.', + objectRequired: 'يجب أن تكون إعدادات الخادم كائن JSON', + invalidJson: 'JSON الخاص بـ MCP غير صالح', + saveFailed: 'فشل الحفظ', + removeFailed: 'فشلت الإزالة', + gatewayUnavailableTitle: 'البوابة غير متاحة', + gatewayUnavailableMessage: 'أعد الاتصال بالبوابة قبل إعادة تحميل MCP.', + reloadedTitle: 'تمت إعادة تحميل أدوات MCP', + reloadedMessage: 'مخططات الأدوات الجديدة تطبق على الأدوار الجديدة.', + reloadFailed: 'فشلت إعادة تحميل MCP', + savedTitle: 'تم حفظ خادم MCP', + savedMessage: name => `سيطبق ${name} بعد إعادة تحميل MCP.`, + newServer: 'خادم جديد', + reload: 'إعادة تحميل MCP', + reloading: 'جار إعادة التحميل...', + emptyTitle: 'لا توجد خوادم MCP', + emptyDesc: 'أضف خادم stdio أو HTTP لإتاحة أدوات MCP.', + disabled: 'معطل', + editServer: 'تحرير الخادم', + name: 'الاسم', + serverJson: 'JSON الخادم', + remove: 'إزالة', + saveServer: 'حفظ الخادم' + }, + model: { + loading: 'جار تحميل إعدادات النموذج...', + appliesDesc: 'ينطبق على الجلسات الجديدة. استخدم منتقي النموذج في صندوق الإنشاء لتبديل المحادثة النشطة فورا.', + provider: 'المزوّد', + model: 'النموذج', + applying: 'جار التطبيق...', + defaultsLabel: 'الافتراضيات', + reasoning: 'الاستدلال', + reasoningOff: 'إيقاف', + defaultsFailed: 'فشل حفظ افتراضيات النموذج', + auxiliaryTitle: 'النماذج المساعدة', + resetAllToMain: 'إعادة تعيين الكل إلى النموذج الرئيسي', + auxiliaryDesc: 'تعمل المهام المساعدة على النموذج الرئيسي افتراضيا. عيّن نموذجا مخصصا لأي مهمة لتجاوز ذلك.', + setToMain: 'ضبط على الرئيسي', + change: 'تغيير', + autoUseMain: 'تلقائي · استخدام النموذج الرئيسي', + providerDefault: '(افتراضي المزوّد)', + tasks: { + vision: { + label: 'الرؤية', + hint: 'تحليل الصور' + }, + web_extract: { + label: 'استخراج الويب', + hint: 'تلخيص الصفحات' + }, + compression: { + label: 'الضغط', + hint: 'ضغط السياق' + }, + skills_hub: { + label: 'مركز المهارات', + hint: 'بحث المهارات' + }, + approval: { + label: 'الموافقة', + hint: 'موافقة تلقائية ذكية' + }, + mcp: { + label: 'MCP', + hint: 'توجيه أدوات MCP' + }, + title_generation: { + label: 'توليد العناوين', + hint: 'عناوين الجلسات' + }, + curator: { + label: 'المنسّق', + hint: 'مراجعة استخدام المهارات' + } + } + }, + providers: { + connectAccount: 'ربط حساب', + haveApiKey: 'لديك مفتاح API بدلاً من ذلك؟', + intro: 'سجل الدخول باشتراكك دون نسخ مفتاح API. يشغّل Hermes تسجيل الدخول من المتصفح لك داخل التطبيق.', + connected: 'متصل', + collapse: 'طي', + connectAnother: 'ربط مزود آخر', + otherProviders: 'مزودون آخرون', + disconnect: 'قطع الاتصال', + disconnectInTerminal: 'قطع الاتصال (يشغّل أمر الإزالة في الطرفية)', + removeConfirm: provider => `إزالة ${provider}؟`, + removeExternalGeneric: provider => `${provider} يُدار عبر CLI خاص به — أزله من هناك.`, + removeKeyManaged: provider => `${provider} مُعدّ من مفتاح API. أزله من مفاتيح API.`, + removeTerminalConfirm: (provider, command) => + `قطع اتصال ${provider}؟ هذا يشغّل "${command}" في الطرفية لمسح بيانات الاعتماد.`, + removeTerminalRunning: provider => `جار تشغيل قطع اتصال ${provider} في الطرفية…`, + removedTitle: 'تمت إزالة الحساب', + removedMessage: provider => `تمت إزالة ${provider}.`, + failedRemove: provider => `تعذّرت إزالة ${provider}`, + noProviderKeys: 'لا توجد مفاتيح API متاحة للمزودين.', + searchKeys: 'البحث عن مزوّدين...', + noKeysMatch: 'لا يوجد مزوّدون يطابقون بحثك.', + localEndpoint: { + title: 'نقطة نهاية محلية', + description: 'استخدم خادما محليا أو نقطة نهاية متوافقة مع OpenAI لهذا المزود.' + }, + loading: 'جار تحميل المزودين...' + }, + sessions: { + loading: 'جار تحميل الجلسات المؤرشفة...', + archivedTitle: 'الجلسات المؤرشفة', + archivedIntro: + 'تُخفى المحادثات المؤرشفة من الشريط الجانبي مع الاحتفاظ بكل رسائلها. اضغط Ctrl/⌘ مع النقر على محادثة في الشريط الجانبي لأرشفتها.', + emptyArchivedTitle: 'لا توجد محادثات مؤرشفة', + emptyArchivedDesc: 'أرشف محادثة لإخفائها هنا.', + unarchive: 'إلغاء الأرشفة', + deletePermanently: 'حذف نهائي', + messages: count => `${count} ${count === 1 ? 'رسالة' : 'رسائل'}`, + restored: 'تمت الاستعادة', + deleteConfirm: title => `حذف "${title}" نهائياً؟ لا يمكن التراجع عن هذا.`, + defaultDirTitle: 'مجلد المشروع الافتراضي', + defaultDirDesc: 'تبدأ الجلسات الجديدة في هذا المجلد ما لم تختر غيره. اتركه غير مضبوط لاستخدام مجلدك الرئيسي.', + defaultDirUpdated: 'تم تحديث مجلد المشروع الافتراضي، ابدأ محادثة جديدة (Ctrl/⌘+N) ليطبق التغيير', + defaultsTo: label => `الافتراضي هو ${label}.`, + change: 'تغيير', + choose: 'اختيار', + clear: 'مسح', + notSet: 'غير مضبوط', + failedLoad: 'تعذر تحميل الجلسات المؤرشفة', + unarchiveFailed: 'فشل إلغاء الأرشفة', + deleteFailed: 'فشل الحذف', + updateDirFailed: 'تعذر تحديث المجلد الافتراضي', + clearDirFailed: 'تعذر مسح المجلد الافتراضي' + }, + toolsets: { + loadingConfig: 'جار تحميل الإعدادات', + savedTitle: 'تم حفظ بيانات الاعتماد', + savedMessage: key => `تم تحديث ${key}.`, + removedTitle: 'تمت إزالة بيانات الاعتماد', + removedMessage: key => `تمت إزالة ${key}.`, + failedSave: key => `فشل حفظ ${key}`, + failedRemove: key => `فشل إزالة ${key}`, + failedReveal: key => `فشل إظهار ${key}`, + removeConfirm: key => `إزالة ${key} من .env؟`, + set: 'ضبط', + notSet: 'غير مضبوط', + selectedTitle: 'تم اختيار المزوّد', + selectedMessage: provider => `${provider} نشط الآن.`, + failedSelect: provider => `فشل اختيار ${provider}`, + failedLoad: 'فشل تحميل إعدادات الأدوات', + noProviderOptions: 'لا تحتوي مجموعة الأدوات هذه على خيارات مزوّد — فعّلها لتعمل مع إعدادك الحالي.', + noProviders: 'لا يوجد مزوّدون متاحون لمجموعة الأدوات هذه الآن.', + ready: 'جاهز', + nousIncluded: 'مُضمّن مع اشتراك Nous — سجّل الدخول إلى Nous Portal للتفعيل.', + noApiKeyRequired: 'لا يلزم مفتاح API.', + postSetupHint: step => + `تحتاج هذه الخلفية إلى تثبيت لمرة واحدة (${step}). يعمل على هذا الجهاز — قد يستغرق بضع دقائق.`, + postSetupRun: 'تشغيل الإعداد', + postSetupRunning: 'جار التثبيت…', + postSetupStarting: 'جار البدء…', + postSetupCompleteTitle: 'اكتمل الإعداد', + postSetupCompleteMessage: step => `تم تثبيت ${step}.`, + postSetupErrorTitle: 'انتهى الإعداد بأخطاء', + postSetupErrorMessage: step => `تحقق من سجل ${step}.`, + postSetupFailed: step => `فشل تشغيل إعداد ${step}` + } + }, + skills: { + tabSkills: 'المهارات', + tabToolsets: 'مجموعات الأدوات', + all: 'الكل', + searchSkills: 'البحث في المهارات', + searchToolsets: 'البحث في مجموعات الأدوات', + refresh: 'تحديث', + refreshing: 'جار التحديث...', + loading: 'جار التحميل...', + noSkillsTitle: 'لا توجد مهارات', + noSkillsDesc: 'ستظهر المهارات المثبتة هنا.', + noToolsetsTitle: 'لا توجد مجموعات أدوات', + noToolsetsDesc: 'ستظهر مجموعات الأدوات المتاحة هنا.', + noDescription: 'لا يوجد وصف', + configured: 'مضبوط', + needsKeys: 'يحتاج مفاتيح', + toolsetsEnabled: (enabled, total) => `${enabled} من ${total} مفعلة`, + configureToolset: label => `ضبط ${label}`, + toggleToolset: label => `تبديل ${label}`, + skillsLoadFailed: 'فشل تحميل المهارات', + toolsetsRefreshFailed: 'فشل تحديث مجموعات الأدوات', + skillEnabled: 'تم تفعيل المهارة', + skillDisabled: 'تم تعطيل المهارة', + toolsetEnabled: 'تم تفعيل مجموعة الأدوات', + toolsetDisabled: 'تم تعطيل مجموعة الأدوات', + appliesToNewSessions: name => `ينطبق على الجلسات الجديدة في ${name}`, + failedToUpdate: name => `فشل تحديث ${name}` + }, + agents: { + close: 'إغلاق الوكلاء', + title: 'شجرة التوليد', + subtitle: 'نشاط الوكلاء الفرعيين المباشر للدور الحالي.', + emptyTitle: 'لا يوجد وكلاء فرعيون مباشرون', + emptyDesc: 'عندما يفوّض دور ما عملا، يبث الوكلاء الأبناء تقدّمهم هنا.', + running: 'قيد التشغيل', + failed: 'فشل', + done: 'تم', + streaming: 'جار البث', + files: 'الملفات', + moreFiles: count => `+${count} ملفات إضافية`, + delegation: index => `التفويض ${index}`, + workers: count => `${count} عامل`, + workersActive: count => `${count} نشط`, + agentsCount: count => `${count} ${count === 1 ? 'وكيل' : 'وكلاء'}`, + activeCount: count => `${count} نشط`, + failedCount: count => `${count} فشل`, + toolsCount: count => `${count} أداة`, + filesCount: count => `${count} ملف`, + updatedAgo: age => `تم التحديث ${age}`, + ageNow: 'الآن', + ageSeconds: seconds => `قبل ${seconds} ث`, + ageMinutes: minutes => `قبل ${minutes} د`, + ageHours: hours => `قبل ${hours} س`, + durationSeconds: seconds => `${seconds} ث`, + durationMinutes: (minutes, seconds) => `${minutes} د ${seconds} ث`, + tokens: value => `${value} رمز` + }, + commandCenter: { + close: 'إغلاق', + paletteTitle: 'لوحة الأوامر', + back: 'رجوع', + searchPlaceholder: 'ابحث عن أمر أو إعداد...', + goTo: 'انتقال إلى', + goToSession: 'الانتقال إلى الجلسة', + branches: 'الفروع', + startInBranch: branch => `محادثة جديدة في ${branch}`, + commandCenter: 'مركز الأوامر', + appearance: 'المظهر', + settings: 'الإعدادات', + changeTheme: 'تغيير الثيم', + changeColorMode: 'تغيير نمط الألوان', + pets: { + title: 'الحيوانات الأليفة', + placeholder: 'البحث في الحيوانات الأليفة...', + loading: 'جار تحميل معرض petdex...', + error: 'تعذّر الوصول إلى معرض petdex.', + staleBackend: 'أعد تشغيل Hermes لاستخدام الحيوانات الأليفة — الخادم الخلفي أقدم من هذه الميزة.', + empty: 'لا توجد حيوانات أليفة مطابقة.', + turnOff: 'إيقاف التشغيل', + turnOn: 'تشغيل', + installed: 'مثبّت', + generatedTag: 'مُولّد', + adoptFailed: 'تعذّر تبنّي ذلك الحيوان الأليف.', + toggleFailed: 'تعذّر تبديل الحيوان الأليف.', + noneAvailable: 'لا توجد حيوانات أليفة متاحة — اختر واحدا أدناه لتثبيته.' + }, + generatePet: { + title: 'توليد حيوان أليف', + placeholder: 'صف حيوانا أليفا لتوليده...', + promptHint: 'اكتب وصفا، ثم اضغط Enter لرسم أربعة مظاهر.', + readyHint: 'اضغط Enter لرسم أربعة مظاهر من وصفك.', + generate: 'توليد', + generating: 'جار التوليد...', + retry: 'إعادة المحاولة', + hatch: 'تفقيس', + spawning: 'جار الإنشاء...', + hatching: 'جار تفقيس حيوانك الأليف...', + hatchingSub: 'جار بثّ الحياة فيه...', + hatched: 'تم التفقيس!', + hatchRow: (_state, done, total) => `جار رسم الإطار ${done} من ${total}...`, + hatchComposing: 'جار تجميع الأجزاء...', + hatchSaving: 'أوشكنا على الانتهاء...', + namePlaceholder: 'سمِّ حيوانك الأليف', + staleBackend: 'حدّث Hermes لتوليد الحيوانات الأليفة.', + backgroundHint: 'يمكنك إغلاق هذا — سيُعلِمك Hermes عند الانتهاء.', + slowProviderHint: 'قد يستغرق هذا عدة دقائق', + remix: 'إعادة مزج', + remixConfirmTitle: 'إعادة مزج هذا المظهر؟', + remixConfirmBody: 'يولّد هذا مجموعة جديدة من المسوّدات باستخدام هذا كنقطة بداية. قد يستغرق عدة دقائق.', + genericError: 'فشل التوليد — حاول مجددا أو اختر اقتراحا.', + referenceImageTooLarge: 'صورة المرجع كبيرة جدا. استخدم واحدة أقل من 16 MB.', + referenceImageInvalid: 'تعذّرت قراءة صورة المرجع تلك. جرّب PNG أو JPG أو WebP أو GIF.', + adopt: 'تبنّي', + startOver: 'البدء من جديد' + }, + installTheme: { + title: 'تثبيت سمة...', + placeholder: 'البحث في VS Code Marketplace...', + loading: 'جار البحث في Marketplace...', + error: 'تعذّر الوصول إلى Marketplace.', + empty: 'لا توجد سمات مطابقة.', + install: 'تثبيت', + installing: 'جار التثبيت...', + installed: 'مثبّت', + installs: count => `${count} عملية تثبيت` + }, + settingsFields: 'حقول الإعدادات', + mcpServers: 'خوادم MCP', + archivedChats: 'المحادثات المؤرشفة', + commands: 'الأوامر', + sections: { + sessions: 'الجلسات', + system: 'النظام', + usage: 'الاستخدام' + }, + sectionDescriptions: { + sessions: 'البحث في الجلسات وإدارتها', + system: 'الحالة والسجلات وإجراءات النظام', + usage: 'نشاط الرموز والتكلفة والمهارات عبر الزمن' + }, + nav: { + newChat: { + title: 'جلسة جديدة', + detail: 'بدء جلسة جديدة' + }, + settings: { + title: 'الإعدادات', + detail: 'تكوين Hermes desktop' + }, + skills: { + title: 'المهارات والأدوات', + detail: 'تفعيل المهارات ومجموعات الأدوات والمزوّدين' + }, + messaging: { + title: 'المراسلة', + detail: 'إعداد Telegram وSlack وDiscord والمزيد' + }, + artifacts: { + title: 'العناصر', + detail: 'استعراض المخرجات المولّدة' + } + }, + sectionEntries: { + sessions: { + title: 'لوحة الجلسات', + detail: 'البحث في الجلسات وتثبيتها وإدارتها' + }, + system: { + title: 'لوحة النظام', + detail: 'حالة البوابة والسجلات وإعادة التشغيل/التحديث' + }, + usage: { + title: 'لوحة الاستخدام', + detail: 'نشاط الرموز والتكلفة والمهارات' + } + }, + providerNavigate: 'فتح المزود', + providerSessions: 'جلسات المزود', + refresh: 'تحديث', + refreshing: 'جار التحديث...', + noResults: 'لا توجد نتائج', + pinSession: 'تثبيت الجلسة', + unpinSession: 'إلغاء تثبيت الجلسة', + exportSession: 'تصدير الجلسة', + deleteSession: 'حذف الجلسة', + noSessions: 'لا توجد جلسات', + gatewayRunning: 'البوابة تعمل', + gatewayStopped: 'البوابة متوقفة', + hermesActiveSessions: (version, count) => `Hermes ${version} لديه ${count} جلسة نشطة`, + restartGateway: 'إعادة تشغيل البوابة', + gatewayRestartFailed: 'فشل إعادة تشغيل البوابة.', + updateHermes: 'تحديث Hermes', + actionRunning: 'الإجراء قيد التشغيل', + actionDone: 'اكتمل الإجراء', + actionFailed: 'فشل الإجراء', + actionStartedWaiting: 'بدأ الإجراء، جار الانتظار...', + loadingStatus: 'جار تحميل الحالة', + recentLogs: 'السجلات الأخيرة', + noLogs: 'لا توجد سجلات', + days: count => `${count} يوم`, + statSessions: 'الجلسات', + statApiCalls: 'نداءات API', + statTokens: 'الرموز', + statCost: 'التكلفة', + actualCost: cost => `التكلفة الفعلية ${cost}`, + loadingUsage: 'جار تحميل الاستخدام', + noUsage: period => `لا يوجد استخدام خلال ${period} يوم`, + retry: 'إعادة المحاولة', + dailyTokens: 'الرموز اليومية', + input: 'إدخال', + output: 'إخراج', + noDailyActivity: 'لا يوجد نشاط يومي', + topModels: 'أكثر النماذج استخداما', + noModelUsage: 'لا يوجد استخدام نماذج', + topSkills: 'أكثر المهارات استخداما', + noSkillActivity: 'لا يوجد نشاط مهارات', + actions: count => `${count} إجراء` + }, + messaging: { + search: 'بحث', + loading: 'جار التحميل...', + loadFailed: 'فشل التحميل', + states: { + connected: 'متصل', + connecting: 'جار الاتصال', + disabled: 'معطّل', + fatal: 'خطأ', + gateway_stopped: 'تم إيقاف بوابة المراسلة', + not_configured: 'يحتاج إعدادا', + pending_restart: 'يلزم إعادة التشغيل', + retrying: 'جار إعادة المحاولة', + startup_failed: 'فشل بدء التشغيل' + }, + unknown: 'غير معروف', + hintPendingRestart: 'تحتاج إعادة تشغيل لتطبيق التغييرات.', + hintGatewayStopped: 'البوابة متوقفة.', + credentialsSet: 'بيانات الاعتماد مضبوطة', + needsSetup: 'يحتاج إعدادا', + gatewayStopped: 'البوابة متوقفة', + getCredentials: 'الحصول على بيانات الاعتماد', + openSetupGuide: 'فتح دليل الإعداد', + required: 'مطلوب', + recommended: 'موصى به', + advanced: count => `${count} إعدادات متقدمة`, + noTokenNeeded: 'لا يحتاج رمز', + enabled: 'مفعل', + disabled: 'معطل', + unsavedChanges: 'تغييرات غير محفوظة', + saving: 'جار الحفظ...', + saveChanges: 'حفظ التغييرات', + saved: 'تم الحفظ', + replaceValue: 'استبدال القيمة', + openDocs: 'فتح الوثائق', + clearField: key => `مسح ${key}`, + enableAria: name => `تفعيل ${name}`, + disableAria: name => `تعطيل ${name}`, + platformEnabled: name => `تم تفعيل ${name}`, + platformDisabled: name => `تم تعطيل ${name}`, + restartToApply: 'أعد التشغيل لتطبيق التغييرات.', + setupSaved: name => `تم حفظ إعداد ${name}`, + restartToReconnect: 'أعد التشغيل لإعادة الاتصال.', + keyCleared: key => `تم مسح ${key}`, + setupUpdated: name => `تم تحديث إعداد ${name}`, + failedUpdate: name => `فشل تحديث ${name}`, + failedSave: name => `فشل حفظ ${name}`, + failedClear: key => `فشل مسح ${key}`, + fieldCopy: { + TELEGRAM_BOT_TOKEN: { + label: 'رمز البوت (token)', + help: 'أنشئ بوتا عبر @BotFather، ثم الصق الرمز الذي يمنحك إياه.', + placeholder: 'الصق رمز بوت Telegram' + }, + TELEGRAM_ALLOWED_USERS: { + label: 'معرّفات مستخدمي Telegram المسموح بهم', + help: 'موصى به. معرّفات رقمية مفصولة بفواصل من @userinfobot. بدون ذلك، يمكن لأي شخص مراسلة بوتك مباشرة.' + }, + TELEGRAM_PROXY: { + label: 'رابط الـ Proxy', + help: 'مطلوب فقط على الشبكات التي يكون فيها Telegram محجوبا.' + }, + DISCORD_BOT_TOKEN: { + label: 'رمز البوت (token)', + help: 'أنشئ تطبيقا في Discord Developer Portal، وأضف بوتا، ثم الصق رمزه.' + }, + DISCORD_ALLOWED_USERS: { + label: 'معرّفات مستخدمي Discord المسموح بهم', + help: 'موصى به. معرّفات مستخدمي Discord مفصولة بفواصل.' + }, + DISCORD_REPLY_TO_MODE: { + label: 'نمط الرد', + help: 'first أو all أو off.' + }, + DISCORD_ALLOW_ALL_USERS: { + label: 'السماح لكل مستخدمي Discord', + help: 'للتطوير فقط. عند التفعيل، يمكن لأي شخص مراسلة البوت مباشرة دون قائمة سماح.' + }, + DISCORD_HOME_CHANNEL: { + label: 'معرّف القناة الرئيسية', + help: 'القناة التي يرسل فيها البوت الرسائل الاستباقية (مخرجات cron، التذكيرات).' + }, + DISCORD_HOME_CHANNEL_NAME: { + label: 'اسم القناة الرئيسية', + help: 'الاسم المعروض للقناة الرئيسية في السجلات ومخرجات الحالة.' + }, + BLUEBUBBLES_ALLOW_ALL_USERS: { + label: 'السماح لكل مستخدمي iMessage', + help: 'عند التفعيل، يتم تخطي قائمة سماح BlueBubbles.' + }, + MATTERMOST_ALLOW_ALL_USERS: { + label: 'السماح لكل مستخدمي Mattermost' + }, + MATTERMOST_HOME_CHANNEL: { + label: 'القناة الرئيسية' + }, + QQ_ALLOW_ALL_USERS: { + label: 'السماح لكل مستخدمي QQ' + }, + QQBOT_HOME_CHANNEL: { + label: 'قناة QQ الرئيسية', + help: 'القناة أو المجموعة الافتراضية لتسليم cron.' + }, + QQBOT_HOME_CHANNEL_NAME: { + label: 'اسم قناة QQ الرئيسية' + }, + SLACK_BOT_TOKEN: { + label: 'رمز بوت Slack', + help: 'استخدم رمز البوت من OAuth & Permissions بعد تثبيت تطبيق Slack الخاص بك.', + placeholder: 'الصق رمز بوت Slack' + }, + SLACK_APP_TOKEN: { + label: 'رمز تطبيق Slack', + help: 'استخدم الرمز على مستوى التطبيق المطلوب لـ Socket Mode.', + placeholder: 'الصق رمز تطبيق Slack' + }, + SLACK_ALLOWED_USERS: { + label: 'معرّفات مستخدمي Slack المسموح بهم', + help: 'موصى به. معرّفات مستخدمي Slack مفصولة بفواصل.' + }, + MATTERMOST_URL: { + label: 'رابط الخادم', + placeholder: 'https://mattermost.example.com' + }, + MATTERMOST_TOKEN: { + label: 'رمز البوت (token)' + }, + MATTERMOST_ALLOWED_USERS: { + label: 'معرّفات المستخدمين المسموح بهم', + help: 'موصى به. معرّفات مستخدمي Mattermost مفصولة بفواصل.' + }, + MATRIX_HOMESERVER: { + label: 'رابط Homeserver', + placeholder: 'https://matrix.org' + }, + MATRIX_ACCESS_TOKEN: { + label: 'رمز الوصول' + }, + MATRIX_USER_ID: { + label: 'معرّف مستخدم البوت', + placeholder: '@hermes:example.org' + }, + MATRIX_ALLOWED_USERS: { + label: 'معرّفات مستخدمي Matrix المسموح بهم', + help: 'موصى به. معرّفات مستخدمين مفصولة بفواصل بصيغة @user:server.' + }, + SIGNAL_HTTP_URL: { + label: 'رابط جسر Signal', + placeholder: 'http://127.0.0.1:8080', + help: 'رابط جسر signal-cli REST قيد التشغيل.' + }, + SIGNAL_ACCOUNT: { + label: 'رقم الهاتف', + help: 'الرقم المسجّل مع جسر signal-cli الخاص بك.' + }, + SIGNAL_ALLOWED_USERS: { + label: 'مستخدمو Signal المسموح بهم', + help: 'موصى به. معرّفات Signal مفصولة بفواصل.' + }, + WHATSAPP_ENABLED: { + label: 'تفعيل جسر WhatsApp', + help: 'يُضبط تلقائيا عبر المفتاح أدناه. اتركه دون تغيير ما لم تكن متأكدا من حاجتك إليه.' + }, + WHATSAPP_MODE: { + label: 'وضع الجسر' + }, + WHATSAPP_ALLOWED_USERS: { + label: 'مستخدمو WhatsApp المسموح بهم', + help: 'موصى به. أرقام هواتف أو معرّفات WhatsApp مفصولة بفواصل.' + } + }, + platformIntro: {} + }, + profiles: { + close: 'إغلاق', + nameHint: 'اسم الملف الشخصي', + title: 'الملفات الشخصية', + count: count => `${count} ملف شخصي`, + loading: 'جار التحميل...', + newProfile: 'ملف شخصي جديد', + allProfiles: 'كل الملفات الشخصية', + showAllProfiles: 'إظهار كل الملفات الشخصية', + switchToProfile: name => `التبديل إلى ${name}`, + manageProfiles: 'إدارة الملفات الشخصية', + actionsFor: name => `إجراءات ${name}`, + color: 'اللون', + colorFor: name => `لون ${name}`, + setColor: color => `ضبط اللون ${color}`, + autoColor: 'لون تلقائي', + noProfiles: 'لا توجد ملفات شخصية', + selectPrompt: 'اختر ملفا شخصيا', + refresh: 'تحديث', + refreshing: 'جار التحديث...', + default: 'الافتراضي', + skills: count => `${count} مهارة`, + env: 'البيئة', + defaultBadge: 'افتراضي', + rename: 'إعادة تسمية', + copySetup: 'نسخ الإعداد', + copying: 'جار النسخ...', + modelLabel: 'النموذج', + skillsLabel: 'المهارات', + notSet: 'غير مضبوط', + soulDesc: 'الموجّه (prompt) النظامي وتعليمات الشخصية المضمّنة في هذا الملف الشخصي.', + soulOptional: 'اختياري', + soulPlaceholder: mode => + `الموجّه (prompt) النظامي / الشخصية لهذا الملف الشخصي.\nاتركه فارغا للإبقاء على افتراضي ${mode}.`, + soulPlaceholderCloned: 'مستنسخ', + soulPlaceholderEmpty: 'فارغ', + unsavedChanges: 'تغييرات غير محفوظة', + loadingSoul: 'جار تحميل SOUL.md...', + emptySoul: 'SOUL.md فارغ — ابدأ بكتابة الشخصية...', + saving: 'جار الحفظ...', + saveSoul: 'حفظ التعليمات', + deleteTitle: 'حذف الملف الشخصي', + deleteDescPrefix: 'سيؤدي هذا إلى حذف ', + deleteDescMid: ' وإزالة ', + deleteDescSuffix: ' الخاص به. لا يمكن التراجع عن هذا.', + deleting: 'جار الحذف...', + createDesc: 'أنشئ ملفا شخصيا بإعدادات منفصلة.', + nameLabel: 'الاسم', + cloneFrom: 'استنساخ من', + cloneFromNone: 'لا شيء (فارغ)', + cloneFromDesc: 'ينسخ الإعدادات والمهارات وSOUL.md من الملف الشخصي المصدر المحدد.', + cloneFromDefault: 'نسخ إعداد الافتراضي', + cloneFromDefaultDesc: 'ابدأ من إعدادات الملف الافتراضي.', + invalidName: hint => `اسم غير صالح: ${hint}`, + nameRequired: 'الاسم مطلوب', + creating: 'جار الإنشاء...', + createAction: 'إنشاء ملف شخصي', + renameTitle: 'إعادة تسمية الملف الشخصي', + renameDescPrefix: 'تؤدي إعادة التسمية إلى تحديث دليل الملف الشخصي وأي سكربتات تغليف في ', + renameDescSuffix: '.', + newNameLabel: 'الاسم الجديد', + renaming: 'جار إعادة التسمية...', + created: 'تم إنشاء الملف الشخصي', + renamed: 'تمت إعادة التسمية', + deleted: 'تم الحذف', + setupCopied: 'تم نسخ الإعداد', + soulSaved: 'تم حفظ التعليمات', + failedLoad: 'فشل تحميل الملفات الشخصية', + failedDelete: 'فشل الحذف', + failedCopy: 'فشل النسخ', + failedLoadSoul: 'فشل تحميل التعليمات', + failedSaveSoul: 'فشل حفظ التعليمات', + failedCreate: 'فشل الإنشاء', + failedRename: 'فشل إعادة التسمية' + }, + cron: { + close: 'إغلاق', + search: 'بحث', + loading: 'جار التحميل...', + states: { + enabled: 'مُفعّل', + scheduled: 'مجدول', + running: 'قيد التشغيل', + paused: 'متوقف مؤقتا', + disabled: 'معطّل', + error: 'خطأ', + completed: 'مكتمل' + }, + deliveryLabels: { + local: 'سطح المكتب هذا', + telegram: 'Telegram', + discord: 'Discord', + slack: 'Slack', + email: 'البريد الإلكتروني' + }, + scheduleLabels: { + daily: 'يوميا', + weekdays: 'أيام الأسبوع', + weekly: 'أسبوعيا', + monthly: 'شهريا', + hourly: 'كل ساعة', + 'every-15-minutes': 'كل 15 دقيقة', + custom: 'مخصص' + }, + scheduleHints: { + daily: 'كل يوم في الساعة 9:00 صباحا', + weekdays: 'من الاثنين إلى الجمعة في الساعة 9:00 صباحا', + weekly: 'كل اثنين في الساعة 9:00 صباحا', + monthly: 'أول يوم من كل شهر في الساعة 9:00 صباحا', + hourly: 'في بداية كل ساعة', + 'every-15-minutes': 'كل 15 دقيقة', + custom: 'صياغة cron أو لغة طبيعية' + }, + days: { + '0': 'الأحد', + '1': 'الاثنين', + '2': 'الثلاثاء', + '3': 'الأربعاء', + '4': 'الخميس', + '5': 'الجمعة', + '6': 'السبت', + '7': 'الأحد' + }, + dayFallback: value => `اليوم ${value}`, + everyDayAt: time => `كل يوم في ${time}`, + weekdaysAt: time => `أيام الأسبوع في ${time}`, + everyDayOfWeekAt: (day, time) => `كل ${day} في ${time}`, + monthlyOnDayAt: (dayOfMonth, time) => `شهريا في اليوم ${dayOfMonth} في ${time}`, + topOfHour: 'في بداية كل ساعة', + everyHourAt: minute => `كل ساعة عند :${minute}`, + newCron: 'مهمة مجدولة جديدة', + emptyDescNew: 'أنشئ مهمة مجدولة لتشغيل Hermes تلقائيا.', + emptyDescSearch: 'لا توجد مهام تطابق البحث.', + emptyTitleNew: 'لا توجد مهام مجدولة', + emptyTitleSearch: 'لا توجد نتائج', + last: 'آخر تشغيل', + next: 'التالي', + noRuns: 'لا توجد تشغيلات', + manage: 'إدارة', + showRuns: 'إظهار التشغيلات', + hideRuns: 'إخفاء التشغيلات', + runHistory: 'سجل التشغيل', + actionsFor: title => `إجراءات ${title}`, + actionsTitle: 'الإجراءات', + resume: 'استئناف', + pause: 'إيقاف مؤقت', + resumeTitle: 'استئناف المهمة', + pauseTitle: 'إيقاف المهمة مؤقتا', + triggerNow: 'تشغيل الآن', + edit: 'تحرير', + deleteTitle: 'حذف المهمة', + deleteDescPrefix: 'سيؤدي هذا إلى إزالة ', + deleteDescSuffix: ' نهائيا. سيتوقف عن العمل فورا.', + deleting: 'جار الحذف...', + resumed: 'تم الاستئناف', + paused: 'تم الإيقاف مؤقتا', + triggered: 'تم التشغيل', + deleted: 'تم الحذف', + created: 'تم الإنشاء', + updated: 'تم التحديث', + failedLoad: 'فشل تحميل المهام', + failedUpdate: 'فشل التحديث', + failedTrigger: 'فشل التشغيل', + failedDelete: 'فشل الحذف', + failedSave: 'فشل الحفظ', + editTitle: 'تحرير المهمة المجدولة', + createTitle: 'إنشاء مهمة مجدولة', + editDesc: 'عدل الجدول والرسالة.', + createDesc: 'اضبط مهمة يشغلها Hermes تلقائيا.', + nameLabel: 'الاسم', + namePlaceholder: 'مثال: الملخص الصباحي', + promptLabel: 'الرسالة', + promptPlaceholder: 'ماذا تريد من Hermes أن يفعل؟', + frequencyLabel: 'التكرار', + deliverLabel: 'التسليم', + customScheduleLabel: 'جدول مخصص', + customPlaceholder: 'تعبير cron', + customHint: 'استخدم صيغة cron القياسية.', + optional: 'اختياري', + promptScheduleRequired: 'الرسالة والجدول مطلوبان', + saveChanges: 'حفظ التغييرات', + createAction: 'إنشاء' + }, + artifacts: { + search: 'بحث', + refresh: 'تحديث', + refreshing: 'جار التحديث...', + indexing: 'جار الفهرسة...', + tabAll: 'الكل', + tabImages: 'الصور', + tabFiles: 'الملفات', + tabLinks: 'الروابط', + noArtifactsTitle: 'لا توجد عناصر', + noArtifactsDesc: 'ستظهر الصور والملفات والروابط من محادثاتك هنا.', + failedLoad: 'فشل تحميل العناصر', + openFailed: 'فشل الفتح', + itemsImage: 'صور', + itemsLink: 'روابط', + itemsFile: 'ملفات', + itemsGeneric: 'عناصر', + zero: 'صفر', + rangeOf: (start, end, total) => `${start}-${end} من ${total}`, + goToPage: (itemLabel, page) => `الانتقال إلى صفحة ${page} من ${itemLabel}`, + colTitleLink: 'الرابط', + colTitleFile: 'الملف', + colTitleDefault: 'العنوان', + colLocationLink: 'الموقع', + colLocationFile: 'الموقع', + colLocationDefault: 'الموقع', + colSession: 'الجلسة', + kindImage: 'صورة', + kindFile: 'ملف', + kindLink: 'رابط', + chat: 'المحادثة', + copyUrl: 'نسخ الرابط', + copyPath: 'نسخ المسار' + }, + sidebar: { + nav: { + 'new-session': 'جلسة جديدة', + skills: 'المهارات', + messaging: 'المراسلة', + artifacts: 'العناصر', + chat: 'المحادثة', + settings: 'الإعدادات', + cron: 'المهام المجدولة', + agents: 'الوكلاء' + }, + searchAria: 'البحث في الجلسات', + searchPlaceholder: 'البحث في الجلسات...', + clearSearch: 'مسح البحث', + noMatch: query => `لا توجد جلسات تطابق "${query}"`, + results: 'النتائج', + pinned: 'المثبتة', + sessions: 'الجلسات', + cronJobs: 'المهام المجدولة', + groupAriaGrouped: 'الجلسات مجمعة حسب مساحة العمل', + groupAriaUngrouped: 'الجلسات غير مجمعة', + showProjects: 'عرض المشاريع', + showSessions: 'عرض الجلسات', + groupTitleGrouped: 'مجمعة حسب مساحة العمل', + groupTitleUngrouped: 'كل الجلسات', + allPinned: 'كل الجلسات مثبتة', + shiftClickHint: 'استخدم Shift للتحديد المتعدد', + noWorkspace: 'بدون مساحة عمل', + noProject: 'لا يوجد مشروع', + projectEmpty: 'لا توجد جلسات بعد', + noSessions: 'لا توجد جلسات بعد', + projects: { + sectionLabel: 'المشاريع', + newButton: 'مشروع جديد', + createTitle: 'مشروع جديد', + createDesc: 'سمِّ مساحة العمل وأضف مجلدا أو أكثر.', + renameTitle: 'إعادة تسمية المشروع', + addFolderTitle: 'إضافة مجلد', + namePlaceholder: 'مثال: Skunkworks', + foldersLabel: 'المجلدات', + ideaLabel: 'الفكرة', + ideaPlaceholder: 'ما موضوع هذا المشروع؟ (يُحفظ في IDEA.md)', + ideaGenerate: 'توليد فكرة', + ideaGenerating: 'جار التوليد...', + ideaShuffle: 'خلط القوالب', + noFolders: 'لم تتم إضافة أي مجلدات بعد.', + addFolder: 'إضافة مجلد', + primaryBadge: 'أساسي', + removeFolder: 'إزالة', + create: 'إنشاء', + menu: 'إجراءات المشروع', + menuRename: 'إعادة تسمية', + menuAppearance: 'المظهر', + noColor: 'بلا لون', + menuAddFolder: 'إضافة مجلد', + menuSetActive: 'تعيين كنشط', + menuDelete: 'حذف', + reveal: 'إظهار في المجلد', + copyPath: 'نسخ المسار', + removeFromSidebar: 'إخفاء من الشريط الجانبي', + createFailed: 'تعذّر إنشاء المشروع', + deleteConfirm: 'هذا يزيل المشروع المحفوظ من Hermes. تبقى الملفات ومستودعات git وأشجار العمل دون تغيير.', + startWork: 'شجرة عمل جديدة', + newWorktreeTitle: 'شجرة عمل جديدة', + newWorktreeDesc: 'سمِّ الفرع لشجرة العمل هذه.', + branchPlaceholder: 'مثال: my-feature', + startWorkFailed: 'تعذّر إنشاء شجرة العمل', + convertBranch: 'تحويل فرع...', + convertBranchTitle: 'تحويل فرع', + convertBranchDesc: 'افتح الفروع المسحوبة، أو أنشئ شجرة عمل لفرع حر.', + convertBranchPlaceholder: 'البحث في الفروع...', + convertBranchInstead: 'تحويل فرع موجود', + branchOpenExisting: 'فتح', + branchSwitchHome: 'تبديل الموطن', + branchCreateWorktree: 'شجرة عمل جديدة', + branchesLoading: 'جار تحميل الفروع...', + noBranches: 'لم يتم العثور على فروع', + removeWorktree: 'إزالة شجرة العمل', + removeWorktreeFailed: 'تعذّر إزالة شجرة العمل (تغييرات غير مُودعة؟)', + removeWorktreeConfirm: + 'أزِلها من git (يحذف مجلد شجرة العمل؛ يبقى الفرع)، أو فقط أخفِ المسار من الشريط الجانبي واترك شجرة العمل على القرص.', + removeWorktreeDirty: + 'تحتوي شجرة العمل هذه على تغييرات غير مُودعة. أزِلها بالقوة (يتجاهل تلك التغييرات)، أو فقط أخفِ المسار واحتفظ بها على القرص.', + forceRemove: 'إزالة بالقوة', + enter: label => `فتح ${label}`, + reorder: label => `إعادة ترتيب ${label}`, + toggle: label => `تبديل جلسات ${label}`, + back: 'كل المشاريع' + }, + newSessionIn: label => `جلسة جديدة في ${label}`, + showMoreIn: (count, label) => `إظهار ${count} أخرى في ${label}`, + loading: 'جار التحميل...', + loadMore: 'تحميل المزيد', + loadCount: step => `تحميل ${step} أخرى`, + row: { + pin: 'تثبيت', + unpin: 'إلغاء التثبيت', + copyId: 'نسخ المعرف', + export: 'تصدير', + branchFrom: 'فرع', + rename: 'إعادة تسمية', + archive: 'أرشفة', + newWindow: 'فتح في نافذة جديدة', + copyIdFailed: 'فشل نسخ المعرف', + actionsFor: title => `إجراءات ${title}`, + sessionActions: 'إجراءات الجلسة', + sessionRunning: 'الجلسة تعمل', + needsInput: 'تحتاج إدخالا', + waitingForAnswer: 'بانتظار إجابة', + backgroundRunning: 'تعمل في الخلفية', + finishedUnread: 'اكتملت وفيها جديد', + hideTabBar: 'إخفاء شريط التبويبات', + openInNewTab: 'فتح في تبويب جديد', + openInSplit: 'فتح في تقسيم', + ownedByProfile: profile => `مملوكة للملف الشخصي ${profile}`, + untitledChat: id => `محادثة ${id}`, + handoffOrigin: platform => `قادمة من ${platform}`, + renamed: 'تمت إعادة التسمية', + renameFailed: 'فشلت إعادة التسمية', + renameTitle: 'إعادة تسمية الجلسة', + renameDesc: 'اختر اسما جديدا لهذه الجلسة.', + untitledPlaceholder: 'جلسة بلا عنوان', + ageNow: 'الآن', + ageDay: 'يوم', + ageHour: 'ساعة', + ageMin: 'دقيقة' + } + }, + composer: { + message: 'الرسالة', + wakingProfile: profile => `جار إيقاظ ${profile}`, + placeholderStarting: 'جار بدء Hermes...', + placeholderReconnecting: 'جار إعادة الاتصال...', + placeholderFollowUp: 'اكتب متابعة...', + newSessionPlaceholders: ['اسأل Hermes عن شيء...', 'اطلب من Hermes تنفيذ مهمة...', 'ابدأ محادثة جديدة...'], + followUpPlaceholders: ['اكتب متابعة...', 'أضف توجيها...', 'اسأل سؤالا آخر...'], + startVoice: 'بدء الصوت', + queueMessage: 'إضافة الرسالة للطابور', + steer: 'توجيه', + stop: 'إيقاف', + send: 'إرسال', + speaking: 'يتحدث', + transcribing: 'جار التفريغ', + thinking: 'يفكر', + muted: 'مكتوم', + listening: 'يستمع', + muteMic: 'كتم الميكروفون', + unmuteMic: 'إلغاء كتم الميكروفون', + stopListening: 'إيقاف الاستماع', + stopShort: 'إيقاف', + endConversation: 'إنهاء المحادثة', + endShort: 'إنهاء', + stopDictation: 'إيقاف الإملاء', + transcribingDictation: 'جار تفريغ الإملاء', + voiceDictation: 'إملاء صوتي', + lookupLoading: 'جار البحث...', + lookupNoMatches: 'لا توجد نتائج', + lookupTry: 'جرب', + lookupOr: 'أو', + commonCommands: 'الأوامر الشائعة', + hotkeys: 'الاختصارات', + helpFooter: 'استخدم الأسهم للتنقل و Enter للاختيار.', + commandDescs: { + '/help': 'قائمة كاملة بالأوامر + اختصارات لوحة المفاتيح', + '/clear': 'بدء جلسة جديدة', + '/resume': 'استئناف جلسة سابقة', + '/details': 'التحكم في مستوى تفاصيل النص', + '/copy': 'نسخ التحديد أو آخر رسالة من المساعد', + '/quit': 'الخروج من hermes' + }, + hotkeyDescs: { + 'composer.mention': 'الإشارة إلى الملفات والمجلدات والروابط وgit', + 'composer.slash': 'لوحة أوامر الشرطة المائلة', + 'composer.help': 'هذه المساعدة السريعة (احذف للإخفاء)', + 'composer.sendNewline': 'إرسال · Shift+Enter لسطر جديد', + 'composer.sendQueued': 'إرسال الدور التالي في قائمة الانتظار', + 'keybinds.openPanel': 'كل اختصارات لوحة المفاتيح', + 'composer.cancel': 'إغلاق النافذة المنبثقة · إلغاء التشغيل', + 'composer.history': 'التنقل في النافذة المنبثقة / السجل' + }, + attachUrlTitle: 'إرفاق رابط', + attachUrlDesc: 'أضف رابطا إلى الرسالة.', + urlPlaceholder: 'https://example.com', + urlHintPre: 'سيقرأ Hermes الرابط ضمن السياق.', + attach: 'إرفاق', + queued: count => `${count} في الطابور`, + attachmentOnly: 'إرفاق فقط', + emptyTurn: 'اكتب رسالة أو أرفق ملفا.', + attachments: count => `${count} مرفق`, + editingInComposer: 'جار التحرير في صندوق الكتابة', + editingQueuedInComposer: 'جار تحرير رسالة في الطابور', + queueEdit: 'تحرير الرسالة المجدولة', + queueSendNext: 'إرسالها تاليا', + queueSend: 'إرسالها الآن', + queueDelete: 'حذف من الطابور', + queueStuckTitle: 'لم تُرسل الرسالة في قائمة الانتظار', + queueStuckBody: 'ظل دور في قائمة الانتظار يفشل في الإرسال. ما زال في قائمة الانتظار — حاول إرساله مرة أخرى.', + previewUnavailable: 'المعاينة غير متاحة', + previewLabel: label => `معاينة ${label}`, + couldNotPreview: label => `تعذرت معاينة ${label}`, + removeAttachment: label => `إزالة ${label}`, + dictating: 'جار الإملاء', + preparingAudio: 'جار تجهيز الصوت', + speakingResponse: 'جار قراءة الرد', + readingAloud: 'جار القراءة بصوت عال', + themeSuggestions: 'اقتراحات سمات سطح المكتب', + noMatchingThemes: 'لا توجد سمات مطابقة.', + themeTryPre: 'جرّب ', + themeTryPost: '.', + attachLabel: 'إرفاق', + files: 'ملفات', + folder: 'مجلد', + images: 'صور', + pasteImage: 'لصق صورة', + url: 'رابط', + promptSnippets: 'مقتطفات جاهزة', + tipPre: 'نصيحة: اكتب ', + tipPost: ' للإشارة إلى الملفات ضمن النص.', + snippetsTitle: 'مقتطفات الموجّهات', + snippetsDesc: 'اختر موجّهًا أوليًا لإدراجه في المحرّر.', + dropFiles: 'أفلت الملفات للإرفاق', + dropSession: 'أفلت الجلسة للفتح', + snippets: { + codeReview: { + label: 'مراجعة الكود', + description: 'دقّق التغيير الحالي بحثًا عن الانحدارات والحالات الطرفية المُسقطة والاختبارات المفقودة.', + text: 'يرجى مراجعة هذا بحثًا عن الأخطاء والانحدارات والاختبارات المفقودة.' + }, + implementationPlan: { + label: 'خطة التنفيذ', + description: 'حدّد نهجًا قبل لمس الكود حتى يبقى الفرق مركّزًا.', + text: 'يرجى وضع خطة تنفيذ موجزة قبل تغيير الكود.' + }, + explainThis: { + label: 'اشرح هذا', + description: 'اشرح كيفية عمل الكود المحدد واربطه بالملفات الرئيسية.', + text: 'يرجى شرح كيفية عمل هذا وإرشادي إلى الملفات الرئيسية.' + } + } + }, + statusStack: { + agents: 'الوكلاء', + background: count => `${count} في الخلفية`, + subagents: count => `${count} ${count === 1 ? 'وكيل فرعي' : 'وكيل فرعي'}`, + todos: (done, total) => `المهام ${done}/${total}`, + running: 'قيد التشغيل', + stop: 'إيقاف', + dismiss: 'تجاهل', + exit: code => `خروج ${code}`, + coding: { + title: 'شجرة العمل', + noBranch: 'لا يوجد فرع', + detached: 'منفصل', + clean: 'نظيف', + changed: count => `${count} مُغيَّر`, + ahead: count => `${count} متقدم`, + behind: count => `${count} متأخر`, + review: 'مراجعة', + close: 'إغلاق', + openChanges: 'فتح التغييرات', + openFile: 'فتح الملف', + stage: 'إدراج', + unstage: 'إلغاء الإدراج', + stageAll: 'إدراج الكل', + viewAsTree: 'عرض كشجرة', + viewAsList: 'عرض كقائمة', + revert: 'تراجع', + revertAll: 'التراجع عن الكل', + revertConfirm: 'هل تريد تجاهل التغييرات على هذا الملف واستعادته إلى الحالة المُودعة؟ لا يمكن التراجع عن هذا.', + revertAllConfirm: 'هل تريد تجاهل كل التغييرات واستعادة الملفات إلى الحالة المُودعة؟ لا يمكن التراجع عن هذا.', + staged: 'مُدرَج', + noChanges: 'لا توجد تغييرات', + notRepo: 'ليس مستودع git', + noDiff: 'لا يوجد فرق لعرضه', + scopeUncommitted: 'غير مُودَع', + scopeBranch: 'فرع', + scopeLastTurn: 'آخر دور', + commit: 'إيداع', + commitAndPush: 'إيداع ودفع', + commitPlaceholder: 'رسالة (⌘↵ للإيداع)', + generateCommitMessage: 'توليد رسالة الإيداع', + stopGenerating: 'إيقاف التوليد', + createPr: 'إنشاء PR', + openPr: 'فتح PR', + ghMissing: 'ثبّت GitHub CLI (gh) وسجّل الدخول لفتح طلبات السحب', + agentShip: 'اطلب من Hermes فتح PR', + agentShipPrompt: 'راجع التغييرات الحالية، وأودعها برسالة إيداع تقليدية واضحة، وادفع الفرع، وافتح طلب سحب.', + newBranch: 'فرع جديد', + branchOffFrom: base => `فرع جديد من ${base}`, + switchTo: branch => `التبديل إلى ${branch}`, + switchFailed: branch => `تعذّر التبديل إلى ${branch}`, + worktrees: 'أشجار العمل' + } + }, + updates: { + stages: { + idle: 'جار التحضير...', + prepare: 'جار التحضير...', + fetch: 'جار التنزيل...', + pull: 'أوشكنا على الانتهاء...', + pydeps: 'جار الإنهاء...', + update: 'جار تحديث Hermes...', + rebuild: 'جار إعادة بناء تطبيق سطح المكتب...', + restart: 'جار إعادة تشغيل Hermes...', + done: 'اكتمل التحديث', + manual: 'التحديث من الطرفية', + guiSkew: 'تحديث تطبيق سطح المكتب', + error: 'تم إيقاف التحديث مؤقتا' + }, + checking: 'جار البحث عن تحديثات...', + checkFailedTitle: 'تعذّر التحقق من التحديثات', + tryAgain: 'إعادة المحاولة', + notAvailableTitle: 'التحديث غير متاح', + unsupportedMessage: 'لا يمكن لهذا الإصدار من Hermes تحديث نفسه من داخل التطبيق.', + connectionRetry: 'تحقق من اتصالك وأعد المحاولة.', + latestBody: 'أنت تستخدم أحدث إصدار.', + latestBodyBackend: 'الواجهة الخلفية تعمل بأحدث إصدار.', + allSetTitle: 'كل شيء جاهز', + availableTitle: 'يتوفر تحديث جديد', + availableBody: 'إصدار جديد من Hermes جاهز للتثبيت.', + availableTitleBackend: 'يتوفر تحديث للواجهة الخلفية', + availableBodyBackend: 'إصدار أحدث من واجهة Hermes الخلفية المتصلة جاهز للتثبيت.', + availableBodyNoChangelog: 'إصدار أحدث جاهز. ملاحظات الإصدار غير متاحة لنوع التثبيت هذا.', + updateNow: 'التحديث الآن', + maybeLater: 'ربما لاحقا', + moreChanges: count => `+ ${count} تغيير${count === 1 ? '' : 'ات'} إضافي مُضمَّن.`, + manualTitle: 'التحديث من الطرفية', + manualBody: 'لقد ثبّتت Hermes من سطر الأوامر، لذا تُجرى التحديثات من هناك أيضا. الصق هذا في طرفيتك:', + manualPickedUp: 'سيلتقط Hermes الإصدار الجديد في المرة التالية التي تشغّله فيها.', + guiSkewTitle: 'تحديث تطبيق سطح المكتب', + guiSkewBody: + 'تم تحديث الواجهة الخلفية، لكن حزمة تطبيق سطح المكتب هذه لم تتغير. حدّث أو أعد تثبيت تطبيق Hermes لسطح المكتب (ملف AppImage / ‎.deb / ‎.rpm) لمطابقته.', + copy: 'نسخ', + copied: 'تم النسخ', + done: 'تم', + applyingBody: + 'يتولّى مُحدِّث Hermes المهمة في نافذته الخاصة ويعيد فتح Hermes تلقائيا عند الانتهاء. الرجاء عدم إعادة فتح Hermes بنفسك أثناء التحديث.', + applyingBodyBackend: + 'تطبّق الواجهة الخلفية البعيدة التحديث وستعيد التشغيل. يعيد Hermes الاتصال تلقائيا عند عودتها.', + applyingClose: 'ستُغلق هذه النافذة أثناء تشغيل التحديث، ثم يعيد Hermes فتح نفسه تلقائيا.', + errorTitle: 'لم يكتمل التحديث', + errorBody: 'لا داعي للقلق — لم يُفقد شيء. يمكنك إعادة المحاولة الآن.', + notNow: 'ليس الآن', + applyStatus: { + preparing: 'جار تحديث الواجهة الخلفية...', + pulling: 'جار تحديث الواجهة الخلفية...', + restarting: 'تعيد الواجهة الخلفية التشغيل لتحميل التحديث...', + notAvailable: 'التحديث غير متاح لهذه الواجهة الخلفية.', + failed: 'فشل تحديث الواجهة الخلفية.', + noReturn: 'لم تعد الواجهة الخلفية إلى الاتصال. قد لا يكون التحديث قد اكتمل — تحقق من مضيف الواجهة الخلفية.' + } + }, + install: { + stageStates: { + pending: 'قيد الانتظار', + running: 'جار التثبيت', + succeeded: 'تم', + skipped: 'تم التخطي', + failed: 'فشل' + }, + oneTimeTitle: 'يحتاج Hermes إلى تثبيت لمرة واحدة', + unsupportedDesc: platform => + `التثبيت التلقائي عند أول تشغيل غير متاح على ${platform} بعد. افتح الطرفية وشغّل الأمر أدناه، ثم أعد تشغيل هذا التطبيق. ستتخطى عمليات التشغيل اللاحقة هذه الخطوة.`, + installCommand: 'أمر التثبيت', + copyCommand: 'نسخ الأمر', + viewDocs: 'عرض مستندات التثبيت', + installTo: 'سيتم التثبيت في', + retryAfterRun: 'لقد شغّلته -- إعادة المحاولة', + failedTitle: 'فشل التثبيت', + settingUpTitle: 'جار إعداد وكيل Hermes', + finishingTitle: 'جار الإنهاء', + failedDesc: + 'فشلت إحدى خطوات التثبيت. على Windows، قد يحدث هذا إذا كان هناك نسخة أخرى من Hermes CLI أو تطبيق سطح المكتب قيد التشغيل. أوقف أي نسخ Hermes قيد التشغيل، ثم أعد المحاولة. تحقق من التفاصيل أدناه أو من سجل سطح المكتب للحصول على النص الكامل.', + activeDesc: + 'هذا إعداد لمرة واحدة. يقوم مثبّت Hermes بتنزيل التبعيات وتهيئة جهازك. ستتخطى عمليات التشغيل اللاحقة هذه الخطوة.', + progress: (completed, total) => `اكتملت ${completed} من ${total} خطوة`, + currentStage: stage => ` -- الآن: ${stage}`, + fetchingManifest: 'جار جلب بيان المثبّت...', + error: 'خطأ', + hideOutput: 'إخفاء مخرجات المثبّت', + showOutput: 'إظهار مخرجات المثبّت', + lines: count => `${count} سطر`, + noOutput: 'لا توجد مخرجات بعد.', + cancelling: 'جار الإلغاء...', + cancelInstall: 'إلغاء التثبيت', + transcriptSaved: 'تم حفظ النص الكامل في', + copiedOutput: 'تم النسخ!', + copyOutput: 'نسخ المخرجات', + reloadRetry: 'إعادة التحميل وإعادة المحاولة' + }, + onboarding: { + headerTitle: 'لنُعِدّ لك Hermes Agent', + headerDesc: 'اربط مزوّد نماذج لبدء المحادثة. معظم الخيارات تتطلب نقرة واحدة.', + preparingInstall: 'يُكمل Hermes التثبيت. عادة ما يستغرق ذلك أقل من دقيقة في أول تشغيل.', + starting: 'جار بدء Hermes...', + lookingUpProviders: 'جار البحث عن المزوّدين...', + collapse: 'طي', + otherProviders: 'مزودون آخرون', + haveApiKey: 'لديك مفتاح API', + chooseLater: 'سأختار مزوّدا لاحقا', + recommended: 'موصى به', + connected: 'متصل', + featuredPitch: 'اشتراك واحد، أكثر من 300 نموذج متقدم — الطريقة الموصى بها لتشغيل Hermes', + fireworksPitch: 'نماذج مفتوحة سريعة مع استضافة Fireworks.', + openRouterPitch: 'مفتاح واحد لمئات النماذج — خيار افتراضي جيد', + apiKeyOptions: { + openrouter: { + short: 'مفتاح واحد، نماذج كثيرة', + description: 'يستضيف مئات النماذج خلف مفتاح واحد. خيار افتراضي جيد للتثبيتات الجديدة.' + }, + openai: { + short: 'نماذج من فئة GPT', + description: 'وصول مباشر إلى نماذج OpenAI.' + }, + gemini: { + short: 'نماذج Gemini', + description: 'وصول مباشر إلى نماذج Google Gemini.' + }, + xai: { + short: 'نماذج Grok', + description: 'وصول مباشر إلى نماذج xAI Grok.' + }, + local: { + short: 'مستضاف ذاتيا', + description: + 'وجّه Hermes إلى نقطة نهاية محلية أو مستضافة ذاتيا متوافقة مع OpenAI (vLLM، llama.cpp، Ollama، إلخ).' + } + }, + backToSignIn: 'العودة إلى تسجيل الدخول', + getKey: 'الحصول على مفتاح', + replaceCurrent: 'استبدال القيمة الحالية', + pasteApiKey: 'ألصق مفتاح API', + localApiKeyPlaceholder: 'مفتاح API (اختياري — فقط إذا كانت نقطة النهاية تتطلبه)', + couldNotSave: 'تعذر حفظ بيانات الاعتماد.', + connecting: 'جار الاتصال', + update: 'تحديث', + flowSubtitles: { + pkce: 'يفتح المتصفح لتسجيل الدخول ثم يتابع هنا', + device_code: 'يفتح صفحة تحقق في المتصفح — يتصل Hermes تلقائياً', + loopback: 'يفتح المتصفح لتسجيل الدخول — يتصل Hermes تلقائياً', + external: 'سجل الدخول مرة واحدة في الطرفية ثم عد إلى المحادثة' + }, + startingSignIn: provider => `جار بدء تسجيل الدخول لـ ${provider}...`, + verifyingCode: provider => `جار التحقق من الرمز عبر ${provider}...`, + connectedProvider: provider => `تم ربط ${provider}`, + connectedPicking: provider => `تم ربط ${provider}. جار اختيار نموذج افتراضي...`, + signInFailed: 'فشل تسجيل الدخول. حاول مرة أخرى.', + pickDifferentProvider: 'اختر مزوداً آخر', + signInWith: provider => `تسجيل الدخول عبر ${provider}`, + openedBrowser: provider => `فتحنا ${provider} في المتصفح.`, + authorizeThere: 'صرّح لـ Hermes هناك.', + copyAuthCode: 'انسخ رمز التفويض وألصقه أدناه.', + pasteAuthCode: 'ألصق رمز التفويض', + reopenAuthPage: 'إعادة فتح صفحة التفويض', + autoBrowser: provider => `فتحنا ${provider} في المتصفح. صرّح لـ Hermes هناك وسيتم الاتصال تلقائياً دون نسخ أو لصق.`, + reopenSignInPage: 'إعادة فتح صفحة تسجيل الدخول', + waitingAuthorize: 'بانتظار التفويض...', + externalPending: provider => + `${provider} يسجل الدخول عبر أداة سطر الأوامر الخاصة به. شغّل هذا الأمر في الطرفية، ثم عد واختر "سجلت الدخول":`, + signedIn: 'سجلت الدخول', + deviceCodeOpened: provider => `فتحنا ${provider} في المتصفح. أدخل هذا الرمز هناك:`, + reopenVerification: 'إعادة فتح صفحة التحقق', + copy: 'نسخ', + defaultModel: 'النموذج الافتراضي', + freeTier: 'الطبقة المجانية', + pro: 'مدفوع', + free: 'مجاني', + price: (input, output) => `${input} إدخال / ${output} إخراج لكل مليون رمز`, + change: 'تغيير', + startChatting: 'ابدأ', + docs: provider => `وثائق ${provider}` + }, + modelPicker: { + title: 'اختيار النموذج', + current: 'الحالي', + unknown: 'غير معروف', + search: 'البحث عن نموذج...', + noModels: 'لا توجد نماذج', + addProvider: 'إضافة مزود', + loadFailed: 'فشل تحميل النماذج', + noAuthenticatedProviders: 'لا توجد مزودات مصادق عليها', + pro: 'Pro', + proNeedsSubscription: 'يتطلب اشتراكا', + free: 'مجاني', + freeTier: 'طبقة مجانية', + priceTitle: 'السعر' + }, + modelVisibility: { + title: 'النماذج', + search: 'بحث في النماذج', + noAuthenticatedProviders: 'لا يوجد مزوّدون مصادق عليهم.', + addProvider: 'إضافة مزوّد…' + }, + shell: { + windowControls: 'تحكم النافذة', + paneControls: 'تحكم اللوحات', + appControls: 'تحكم التطبيق', + modelMenu: { + search: 'البحث عن نموذج...', + noModels: 'لا توجد نماذج', + editModels: 'تحرير النماذج', + refreshModels: 'تحديث النماذج', + fast: 'سريع', + medium: 'متوسط' + }, + modelOptions: { + noOptions: 'لا توجد خيارات لهذا النموذج', + options: 'الخيارات', + thinking: 'التفكير', + fast: 'سريع', + effort: 'الجهد', + minimal: 'أدنى', + low: 'منخفض', + medium: 'متوسط', + high: 'عالٍ', + max: 'أقصى', + updateFailed: 'فشل تحديث خيار النموذج', + fastFailed: 'فشل تحديث الوضع السريع' + }, + gatewayMenu: { + gateway: 'البوابة', + connected: 'متصل', + connecting: 'جار الاتصال', + offline: 'غير متصل', + inferenceReady: 'الاستدلال جاهز', + inferenceNotReady: 'الاستدلال غير جاهز', + checkingInference: 'جار فحص الاستدلال', + disconnected: 'منقطع', + openSystem: 'فتح النظام', + connection: label => `الاتصال: ${label}`, + recentActivity: 'النشاط الأخير', + viewAllLogs: 'عرض كل السجلات', + messagingPlatforms: 'منصات المراسلة' + }, + statusbar: { + unknown: 'غير معروف', + restart: 'إعادة تشغيل', + update: 'تحديث', + updateInProgress: 'التحديث جار', + commitsBehind: (count, branch) => `${count} commits خلف ${branch}`, + desktopVersion: version => `سطح المكتب ${version}`, + backendVersion: version => `الخلفية ${version}`, + clientLabel: version => `العميل ${version}`, + backendLabel: version => `الخلفية ${version}`, + commit: sha => `commit ${sha}`, + branch: branch => `الفرع ${branch}`, + closeCommandCenter: 'إغلاق مركز الأوامر', + openCommandCenter: 'فتح مركز الأوامر', + showTerminal: 'إظهار الطرفية', + hideTerminal: 'إخفاء الطرفية', + gateway: 'البوابة', + gatewayReady: 'البوابة جاهزة', + gatewayNeedsSetup: 'البوابة تحتاج إعدادا', + gatewayChecking: 'جار فحص البوابة', + gatewayConnecting: 'جار اتصال البوابة', + gatewayOffline: 'البوابة غير متصلة', + gatewayRestarting: 'جار إعادة التشغيل...', + gatewayTitle: 'حالة البوابة', + agents: 'الوكلاء', + closeAgents: 'إغلاق الوكلاء', + openAgents: 'فتح الوكلاء', + subagents: count => `${count} وكيل فرعي`, + failed: count => `${count} فشل`, + running: count => `${count} يعمل`, + cron: 'المهام', + openCron: 'فتح المهام المجدولة', + turnRunning: 'الدور يعمل', + currentTurnElapsed: 'وقت الدور الحالي', + contextUsage: 'استخدام السياق', + session: 'الجلسة', + runtimeSessionElapsed: 'وقت الجلسة', + yoloOn: 'YOLO مفعل', + yoloOff: 'YOLO معطل', + modelNone: 'لا نموذج', + noModel: 'لا يوجد نموذج', + switchModel: 'تبديل النموذج', + openModelPicker: 'فتح اختيار النموذج', + modelPinned: 'النموذج مثبت', + modelTitle: (provider, model) => `${provider}: ${model}`, + providerModelTitle: (provider, model) => `${provider}: ${model}` + } + }, + rightSidebar: { + aria: 'الشريط الجانبي الأيمن', + panelsAria: 'لوحات الشريط الأيمن', + files: 'الملفات', + terminal: 'الطرفية', + noFolderSelected: 'لم يتم اختيار مجلد', + changeCwdTitle: 'تغيير مجلد العمل', + remotePickerTitle: 'اختر مجلدا بعيدا', + remotePickerDescription: 'استعرض المجلدات على الخادم الخلفي المتصل.', + remotePickerSelect: 'تحديد المجلد', + folderTip: cwd => `المجلد الحالي: ${cwd}`, + openFolder: 'فتح مجلد', + refreshTree: 'تحديث الشجرة', + collapseAll: 'طي الكل', + previewUnavailable: 'المعاينة غير متاحة', + couldNotPreview: path => `تعذرت معاينة ${path}`, + noProjectTitle: 'لا يوجد مشروع', + noProjectBody: 'اختر مجلد عمل لعرض الملفات.', + noProjectOpen: 'لا يوجد مشروع مفتوح', + noDiffs: 'لا توجد فروقات', + unreadableTitle: 'تعذر قراءة المجلد', + unreadableBody: error => `السبب: ${error}`, + emptyTitle: 'المجلد فارغ', + emptyBody: 'لا توجد ملفات للعرض.', + treeErrorTitle: 'فشل تحميل شجرة الملفات', + treeErrorBody: 'حاول التحديث أو اختر مجلدا آخر.', + tryAgain: 'حاول مرة أخرى', + loadingTree: 'جار تحميل الشجرة...', + loadingFiles: 'جار تحميل الملفات...', + terminalHide: 'إخفاء الطرفية', + addToChat: 'إضافة للمحادثة' + }, + preview: { + tab: 'معاينة', + closeTab: label => `إغلاق ${label}`, + closeOthers: 'إغلاق الأخرى', + closeToRight: 'إغلاق ما على اليمين', + closeAll: 'إغلاق الكل', + closePane: 'إغلاق جزء المعاينة', + loading: 'جار تحميل المعاينة', + unavailable: 'المعاينة غير متاحة', + opening: 'جار الفتح...', + hide: 'إخفاء', + openPreview: 'فتح المعاينة', + openInBrowser: 'فتح في المتصفح', + linkHint: '⌘/Ctrl-نقر لجزء المعاينة', + sourceLineTitle: 'انقر للتحديد · shift-نقر للتوسيع · اسحب إلى المُنشئ', + source: 'المصدر', + renderedPreview: 'المعاينة', + diff: 'الفرق', + unknownSize: 'حجم غير معروف', + binaryTitle: 'يبدو هذا ملفا ثنائيا', + binaryBody: label => `قد تعرض معاينة ${label} نصا غير قابل للقراءة.`, + largeTitle: 'هذا الملف كبير', + largeBody: (label, size) => `حجم ${label} هو ${size}. سيعرض Hermes أول 512 KB فقط.`, + previewAnyway: 'معاينة على أي حال', + truncated: 'عرض أول 512 KB.', + noInlineTitle: 'لا توجد معاينة مضمّنة', + noInlineBody: mimeType => `${mimeType || 'هذا النوع من الملفات'} لا يزال بالإمكان إرفاقه كسياق.`, + edit: 'تحرير', + editing: 'جار التحرير', + unsavedChanges: 'تغييرات غير محفوظة', + saveFailed: message => `تعذّر الحفظ: ${message}`, + diskChangedTitle: 'تغيّر الملف على القرص', + diskChangedBody: 'تغيّر هذا الملف منذ أن فتحته. هل تريد الكتابة فوقه بنسختك، أم تجاهل تعديلاتك وإعادة التحميل؟', + overwrite: 'الكتابة فوقه', + discardReload: 'تجاهل وإعادة التحميل', + console: { + deselect: 'إلغاء تحديد الإدخال', + select: 'تحديد الإدخال', + copyFailed: 'تعذّر نسخ مخرجات الكونسول', + copyEntry: 'نسخ هذا الإدخال', + sendEntry: 'إرسال هذا الإدخال إلى المحادثة', + messages: count => `${count} رسائل كونسول`, + resize: 'تغيير حجم كونسول المعاينة', + title: 'كونسول المعاينة', + selected: count => `${count} محدد`, + sendToChat: 'إرسال إلى المحادثة', + copySelected: 'نسخ المحدد إلى الحافظة', + copyAll: 'نسخ الكل إلى الحافظة', + copy: 'نسخ', + clear: 'مسح', + empty: 'لا توجد رسائل كونسول بعد.', + promptHeader: 'كونسول المعاينة:', + sentTitle: 'تم الإرسال إلى المحادثة', + sentMessage: count => `أُضيف ${count} ${count === 1 ? 'إدخال سجل' : 'إدخالات سجل'} إلى المُنشئ` + }, + web: { + appFailedToBoot: 'فشل إقلاع تطبيق المعاينة', + serverNotFound: 'الخادم غير موجود', + failedToLoad: 'فشل تحميل المعاينة', + tryAgain: 'إعادة المحاولة', + restarting: 'جار إعادة تشغيل Hermes...', + askRestart: 'اطلب من Hermes إعادة تشغيل الخادم', + lookingRestart: taskId => `يبحث Hermes عن خادم معاينة لإعادة تشغيله (${taskId})`, + restartingTitle: 'جار إعادة تشغيل خادم المعاينة', + restartingMessage: 'يعمل Hermes في الخلفية. راقب كونسول المعاينة لمتابعة التقدم.', + startRestartFailed: message => `تعذّر بدء إعادة تشغيل الخادم: ${message}`, + restartFailed: 'فشلت إعادة تشغيل الخادم', + hideConsole: 'إخفاء كونسول المعاينة', + showConsole: 'إظهار كونسول المعاينة', + hideDevTools: 'إخفاء DevTools المعاينة', + openDevTools: 'فتح DevTools المعاينة', + finishedRestarting: message => `أنهى Hermes إعادة تشغيل خادم المعاينة${message ? `: ${message}` : ''}`, + failedRestarting: message => `فشلت إعادة تشغيل الخادم: ${message}`, + unknownError: 'خطأ غير معروف', + restartedTitle: 'تمت إعادة تشغيل خادم المعاينة', + reloadingNow: 'جار إعادة تحميل المعاينة الآن.', + restartFailedTitle: 'فشلت إعادة تشغيل المعاينة', + restartFailedMessage: 'تعذّر على Hermes إعادة تشغيل الخادم.', + stillWorking: + 'لا يزال Hermes يعمل، لكن لم تصل نتيجة إعادة التشغيل بعد. قد يكون أمر الخادم قيد التشغيل في المقدمة.', + workspaceReloading: 'تغيّرت مساحة العمل، جار إعادة تحميل المعاينة', + fileChanged: url => `تغيّر الملف، جار إعادة تحميل المعاينة: ${url}`, + filesChanged: (count, url) => `${count} تغييرات ملفات، جار إعادة تحميل المعاينة: ${url}`, + watchFailed: message => `تعذّر مراقبة ملف المعاينة: ${message}`, + moduleMimeDescription: + 'يتم تقديم نصوص الوحدات بنوع MIME خاطئ. عادة ما يعني هذا أن خادم ملفات ثابتة يقدّم تطبيق Vite/React بدلا من خادم تطوير المشروع.', + loadFailedConsole: (code, message) => `فشل التحميل${code ? ` (${code})` : ''}: ${message}`, + unreachableDescription: 'تعذّر الوصول إلى صفحة المعاينة.', + openTarget: url => `فتح ${url}`, + fallbackTitle: 'معاينة' + } + }, + zones: { + showHeader: 'إظهار الرأس', + hideHeader: 'إخفاء الرأس', + minimize: 'تصغير', + restore: 'استعادة', + closeRunningTitle: 'إغلاق تبويب يعمل؟', + closeRunningBody: + 'هذه المحادثة ما زالت تعمل (أو تنتظر إدخالك). إغلاق التبويب يخفيها فقط — ستحتفظ الجلسة بتقدمها ويمكن إعادة فتحها من الشريط الجانبي.', + closeRunningConfirm: 'إغلاق التبويب', + closeOthers: 'إغلاق الأخرى', + closeToRight: 'إغلاق ما على اليمين', + closeAll: 'إغلاق الكل', + split: dir => `تقسيم ${dir}`, + move: dir => `نقل ${dir}`, + dirUp: 'للأعلى', + dirDown: 'للأسفل', + dirLeft: 'لليسار', + dirRight: 'لليمين', + pluginDisabled: pluginId => `الإضافة "${pluginId}" معطلة`, + pluginDisabledBody: 'أعد تفعيلها من الإعدادات ← الإضافات لإرجاع اللوحة.', + missingPane: paneId => `لوحة مفقودة: ${paneId}`, + editTitle: 'التخطيطات', + editHint: 'اختر تخطيطا، أو اسحب اللوحات بين المناطق. انقر بزر الفأرة الأيمن على منطقة لتقسيمها.', + reset: 'إعادة ضبط', + templates: 'القوالب', + custom: 'مخصص', + newGridLayout: 'تخطيط شبكة جديد', + saveCurrentAs: 'حفظ الترتيب الحالي كقالب', + nameLayoutPlaceholder: 'سم هذا التخطيط...', + deletePreset: name => `حذف ${name}`, + zoneEditorTitle: 'محرر المناطق', + editorHintPre: 'انقر للتقسيم · ', + editorHintPost: ' يعكس الخط · اسحب عبر المناطق للدمج · اسحب الحواف المشتركة لتغيير الحجم', + templateColumns: 'أعمدة', + templateRows: 'صفوف', + templateGrid: 'شبكة', + templatePriority: 'أولوية', + zoneTag: index => `منطقة ${index}`, + mergeZones: count => `دمج ${count} مناطق`, + customZoneName: count => `مخصص من ${count} مناطق`, + layoutNamePlaceholder: fallback => `اسم التخطيط (${fallback})`, + saveApply: 'حفظ وتطبيق', + notExpressible: 'هذا الترتيب متشابك — لا يمكن تمثيله كتقسيمات متداخلة بعد', + zoneCount: count => `${count} مناطق` + }, + assistant: { + thread: { + loadingSession: 'جار تحميل الجلسة...', + showEarlier: 'عرض الرسائل الأقدم', + loadingResponse: 'جار تحميل الرد...', + resumeWhenBackgroundDone: count => + count === 1 ? 'سيُستأنف عند انتهاء المهمة الخلفية' : `سيُستأنف عند انتهاء ${count} مهام خلفية`, + thinking: 'يفكر...', + today: time => `اليوم ${time}`, + yesterday: time => `أمس ${time}`, + copy: 'نسخ', + refresh: 'تحديث', + moreActions: 'إجراءات إضافية', + branchNewChat: 'تفريع إلى محادثة جديدة', + dismissError: 'تجاهل الخطأ', + readAloudFailed: 'فشلت القراءة بصوت عال', + preparingAudio: 'جار تجهيز الصوت', + stopReading: 'إيقاف القراءة', + readAloud: 'قراءة بصوت عال', + editMessage: 'تحرير الرسالة', + scrollToBottom: 'التمرير إلى الأسفل', + stop: 'إيقاف', + restorePrevious: 'استعادة السابق', + restoreCheckpoint: 'استعادة النقطة', + restoreFromHere: 'استعادة نقطة التحقق — إعادة التشغيل من هذا الموجّه', + restoreTitle: 'الاستعادة إلى نقطة التحقق هذه؟', + restoreBody: 'يُزال كل ما يلي هذا الموجّه من المحادثة، ويُعاد تشغيل الموجّه من هنا.', + restoreConfirm: 'استعادة وإعادة تشغيل', + restoreNext: 'استعادة التالي', + goForward: 'تقدم', + sendEdited: 'إرسال التعديل', + attachingFile: 'جار إرفاق الملف' + }, + approval: { + gatewayDisconnected: 'البوابة غير متصلة', + sendFailed: 'فشل الإرسال', + run: 'تشغيل', + command: 'الأمر', + moreOptions: 'خيارات إضافية', + allowSession: 'السماح لهذه الجلسة', + alwaysAllowMenu: 'السماح دائما', + jumpToApproval: 'الموافقة مطلوبة', + reject: 'رفض', + alwaysTitle: 'السماح دائما', + alwaysDescription: pattern => `السماح دائما بالأوامر المطابقة لـ ${pattern}`, + alwaysAllow: 'السماح دائما' + }, + clarify: { + notReady: 'غير جاهز', + gatewayDisconnected: 'البوابة غير متصلة', + sendFailed: 'فشل الإرسال', + loadingQuestion: 'جار تحميل السؤال...', + other: 'غير ذلك', + placeholder: 'اكتب إجابتك...', + skip: 'تخطي', + continueLabel: 'متابعة' + }, + tool: { + code: 'الكود', + copyCode: 'نسخ الكود', + renderingImage: 'جار عرض الصورة...', + copyOutput: 'نسخ الإخراج', + copyCommand: 'نسخ الأمر', + copyContent: 'نسخ المحتوى', + copyUrl: 'نسخ الرابط', + copyResults: 'نسخ النتائج', + copyQuery: 'نسخ الاستعلام', + copyFile: 'نسخ الملف', + copyPath: 'نسخ المسار', + outputAlt: 'إخراج الأداة', + rawResponse: 'الرد الخام', + copyActivity: 'نسخ النشاط', + recoveredOne: 'تم الاسترداد', + recoveredMany: count => `تم استرداد ${count}`, + failedOne: 'فشل', + failedMany: count => `فشل ${count}`, + statusRunning: 'يعمل', + statusError: 'خطأ', + statusRecovered: 'تم الاسترداد', + statusDone: 'تم', + actions: { + read: 'قراءة', + reading: 'جار القراءة', + opened: 'تم الفتح', + opening: 'جار الفتح', + searched: 'تم البحث', + searching: 'جار البحث', + ran: 'تم التشغيل', + running: 'جار التشغيل', + ranCode: 'تم تشغيل الكود', + runningCode: 'جار البرمجة' + }, + prefixes: { + browser: 'المتصفح', + web: 'الويب' + }, + titleTemplates: { + actionCommand: (action, command) => `${action} ${command}`, + actionQuoted: (action, value) => `${action} “${value}”`, + actionTarget: (action, target) => `${action} ${target}`, + prefixedDone: (prefix, action) => `${prefix} ${action}`, + runningPrefixedTool: (prefix, action) => `جار تشغيل ${prefix.toLowerCase()} ${action.toLowerCase()}`, + runningTool: action => `جار تشغيل ${action.toLowerCase()}` + }, + titles: { + browser_click: { + done: 'تم النقر على عنصر الصفحة', + pending: 'جار النقر على عنصر الصفحة', + pendingAction: 'جار النقر' + }, + browser_fill: { + done: 'تم ملء حقل النموذج', + pending: 'جار ملء حقل النموذج', + pendingAction: 'جار الملء' + }, + browser_navigate: { + done: 'تم فتح الصفحة', + pending: 'جار فتح الصفحة', + pendingAction: 'جار الفتح' + }, + browser_snapshot: { + done: 'تم التقاط لقطة الصفحة', + pending: 'جار التقاط لقطة الصفحة', + pendingAction: 'جار الالتقاط' + }, + browser_take_screenshot: { + done: 'تم التقاط لقطة الشاشة', + pending: 'جار التقاط لقطة الشاشة', + pendingAction: 'جار الالتقاط' + }, + browser_type: { + done: 'تمت الكتابة على الصفحة', + pending: 'جار الكتابة على الصفحة', + pendingAction: 'جار الكتابة' + }, + clarify: { + done: 'تم طرح سؤال', + pending: 'جار طرح سؤال', + pendingAction: 'جار السؤال' + }, + cronjob: { + done: 'مهمة مجدولة', + pending: 'جار جدولة المهمة', + pendingAction: 'جار الجدولة' + }, + edit_file: { + done: 'تم تحرير الملف', + pending: 'جار تحرير الملف', + pendingAction: 'جار التحرير' + }, + execute_code: { + done: 'تم تشغيل الكود', + pending: 'جار البرمجة', + pendingAction: 'جار البرمجة' + }, + image_generate: { + done: 'تم إنشاء الصورة', + pending: 'جار إنشاء الصورة', + pendingAction: 'جار الإنشاء' + }, + list_files: { + done: 'تم سرد الملفات', + pending: 'جار سرد الملفات', + pendingAction: 'جار السرد' + }, + patch: { + done: 'تم تصحيح الملف', + pending: 'جار تصحيح الملف', + pendingAction: 'جار التصحيح' + }, + read_file: { + done: 'تمت قراءة الملف', + pending: 'جار قراءة الملف', + pendingAction: 'جار القراءة' + }, + search_files: { + done: 'تم البحث في الملفات', + pending: 'جار البحث في الملفات', + pendingAction: 'جار البحث' + }, + session_search_recall: { + done: 'تم البحث في سجل الجلسة', + pending: 'جار البحث في سجل الجلسة', + pendingAction: 'جار البحث' + }, + terminal: { + done: 'تم تشغيل الأمر', + pending: 'جار تشغيل الأمر', + pendingAction: 'جار التشغيل' + }, + todo: { + done: 'تم تحديث المهام', + pending: 'جار تحديث المهام', + pendingAction: 'جار التحديث' + }, + vision_analyze: { + done: 'تم تحليل الصورة', + pending: 'جار تحليل الصورة', + pendingAction: 'جار التحليل' + }, + web_extract: { + done: 'تمت قراءة صفحة الويب', + pending: 'جار قراءة صفحة الويب', + pendingAction: 'جار القراءة' + }, + web_search: { + done: 'تم البحث في الويب', + pending: 'جار البحث في الويب', + pendingAction: 'جار البحث' + }, + write_file: { + done: 'تم تحرير الملف', + pending: 'جار تحرير الملف', + pendingAction: 'جار التحرير' + } + } + } + }, + prompts: { + gatewayDisconnected: 'البوابة غير متصلة', + sudoSendFailed: 'فشل إرسال كلمة مرور sudo', + secretSendFailed: 'فشل إرسال السر', + sudoTitle: 'مطلوب sudo', + sudoDesc: 'أدخل كلمة المرور لمتابعة الأمر.', + sudoPlaceholder: 'كلمة المرور', + secretTitle: 'مطلوب سر', + secretDesc: 'أدخل القيمة المطلوبة لمتابعة المهمة.', + secretPlaceholder: 'القيمة السرية' + }, + desktop: { + audioReadFailed: 'فشلت قراءة الصوت', + sessionUnavailable: 'الجلسة غير متاحة', + createSessionFailed: 'فشل إنشاء الجلسة', + promptFailed: 'فشل إرسال الرسالة', + providerCredentialRequired: 'مطلوب اعتماد المزود', + emptySlashCommand: 'أمر slash فارغ', + desktopCommands: 'أوامر سطح المكتب', + skillCommandsAvailable: count => `${count} أمر مهارة متاح`, + warningLine: message => `تحذير: ${message}`, + yoloArmed: 'YOLO مفعل', + yoloOff: 'YOLO معطل', + yoloSystem: active => (active ? 'YOLO مفعل للنظام' : 'YOLO معطل للنظام'), + yoloTitle: 'وضع YOLO', + yoloToggleFailed: 'فشل تبديل YOLO', + profileStatus: current => `الملف الحالي: ${current}`, + unknownProfile: 'ملف شخصي غير معروف', + noProfileNamed: (target, available) => `لا يوجد ملف باسم ${target}. المتاح: ${available}`, + newChatsProfile: name => `المحادثات الجديدة تستخدم ${name}`, + setProfileFailed: 'فشل ضبط الملف الشخصي', + sttDisabled: 'تحويل الكلام إلى نص معطل', + stopFailed: 'فشل الإيقاف', + regenerateFailed: 'فشلت إعادة التوليد', + editFailed: 'فشل التحرير', + resumeFailed: 'فشل الاستئناف', + resumeStrandedTitle: 'تعذّر تحميل هذه الجلسة', + resumeStrandedBody: + 'فشل الاتصال بهذه الجلسة وتوقفت إعادة المحاولة التلقائية. تأكد من تشغيل البوابة، ثم حاول مجددا.', + resumeRetry: 'إعادة المحاولة', + nothingToBranch: 'لا يوجد ما يمكن تفريعه', + branchNeedsChat: 'يحتاج التفريع إلى محادثة', + sessionBusy: 'الجلسة مشغولة', + branchStopCurrent: 'أوقف الدور الحالي قبل التفريع', + branchNoText: 'لا يوجد نص للتفريع', + branchTitle: n => `مسودة: تفريع #${n}`, + branchFailed: 'فشل التفريع', + deleteFailed: 'فشل الحذف', + archived: 'تمت الأرشفة', + archiveFailed: 'فشلت الأرشفة', + cwdChangeFailed: 'فشل تغيير مجلد العمل', + cwdStagedTitle: 'تم تجهيز مجلد العمل', + cwdStagedMessage: 'سيطبق مجلد العمل على الرسالة التالية.', + modelSwitchFailed: 'فشل تبديل النموذج', + sessionExported: 'تم تصدير الجلسة', + sessionExportFailed: 'فشل تصدير الجلسة', + imageSaved: 'تم حفظ الصورة', + downloadStarted: 'بدأ التنزيل', + restartToUseSaveImage: 'أعد التشغيل لاستخدام حفظ الصور', + restartToSaveImages: 'أعد التشغيل لحفظ الصور', + imageDownloadFailed: 'فشل تنزيل الصورة', + openImage: 'فتح الصورة', + downloadImage: 'تنزيل الصورة', + savingImage: 'جار حفظ الصورة', + imagePreviewFailed: 'فشلت معاينة الصورة', + imageAttach: 'إرفاق الصورة', + imageWriteFailed: 'فشل كتابة الصورة', + imageAttachFailed: 'فشل إرفاق الصورة', + attachImages: 'إرفاق الصور', + clipboard: 'الحافظة', + noClipboardImage: 'لا توجد صورة في الحافظة', + clipboardPasteFailed: 'فشل اللصق من الحافظة', + dropFiles: 'أفلت الملفات هنا', + handoff: { + pickPlatform: 'اختر وجهة', + success: platform => `تم التسليم إلى ${platform}. استأنف هنا في أي وقت.`, + systemNote: platform => `↻ تم التسليم إلى ${platform} — استأنف هنا في أي وقت.`, + failed: error => `فشل التسليم: ${error}`, + timedOut: 'انتهت المهلة في انتظار البوابة. هل `hermes gateway` قيد التشغيل؟' + } + }, + errors: { + genericFailure: 'حدث خطأ', + boundaryTitle: 'تعطل جزء من الواجهة', + boundaryDesc: 'يمكنك إعادة تحميل النافذة أو فتح السجلات لمعرفة التفاصيل.', + reloadWindow: 'إعادة تحميل النافذة', + openLogs: 'فتح السجلات' + }, + ui: { + search: { + clear: 'مسح البحث' + }, + pagination: { + label: 'ترقيم الصفحات', + previous: 'السابق', + previousAria: 'الصفحة السابقة', + next: 'التالي', + nextAria: 'الصفحة التالية' + }, + sidebar: { + title: 'الشريط الجانبي', + description: 'تنقل التطبيق', + toggle: 'تبديل الشريط الجانبي' + } + } +}) diff --git a/apps/desktop/src/i18n/catalog.ts b/apps/desktop/src/i18n/catalog.ts index 19556cb45c49..f35c957076db 100644 --- a/apps/desktop/src/i18n/catalog.ts +++ b/apps/desktop/src/i18n/catalog.ts @@ -1,3 +1,4 @@ +import { ar } from './ar' import { en } from './en' import { ja } from './ja' import type { Locale, Translations } from './types' @@ -8,5 +9,6 @@ export const TRANSLATIONS: Record = { en, zh, 'zh-hant': zhHant, - ja + ja, + ar } diff --git a/apps/desktop/src/i18n/context.test.tsx b/apps/desktop/src/i18n/context.test.tsx index 3028c86ffa07..729ced7a4da4 100644 --- a/apps/desktop/src/i18n/context.test.tsx +++ b/apps/desktop/src/i18n/context.test.tsx @@ -209,6 +209,24 @@ describe('I18nProvider', () => { expect(screen.getByTestId('locale').textContent).toBe('ja') }) + it('applies RTL direction for Arabic and restores LTR on switch back', async () => { + render( + + + + ) + + expect(screen.getByTestId('locale').textContent).toBe('ar') + expect(document.documentElement.dir).toBe('rtl') + expect(document.documentElement.lang).toBe('ar') + + fireEvent.click(screen.getByRole('button', { name: 'switch' })) + + await waitFor(() => expect(screen.getByTestId('locale').textContent).toBe('en')) + expect(document.documentElement.dir).toBe('ltr') + expect(document.documentElement.lang).toBe('en') + }) + it('rolls back the visible locale when saving fails', async () => { const configClient: I18nConfigClient = { getConfig: vi.fn().mockResolvedValue({ display: { language: 'en' } }), diff --git a/apps/desktop/src/i18n/context.tsx b/apps/desktop/src/i18n/context.tsx index 103a63047e77..67f335826192 100644 --- a/apps/desktop/src/i18n/context.tsx +++ b/apps/desktop/src/i18n/context.tsx @@ -55,6 +55,17 @@ function toError(error: unknown): Error { return error instanceof Error ? error : new Error(String(error)) } +const RTL_LOCALES = new Set(['ar']) + +function applyDocumentLocale(locale: Locale) { + if (typeof document === 'undefined') { + return + } + + document.documentElement.lang = locale + document.documentElement.dir = RTL_LOCALES.has(locale) ? 'rtl' : 'ltr' +} + export interface I18nContextValue { configLoadError: Error | null isLoadingConfig: boolean @@ -92,6 +103,7 @@ export function I18nProvider({ children, configClient = defaultConfigClient, ini useEffect(() => { localeRef.current = locale setRuntimeI18nLocale(locale) + applyDocumentLocale(locale) }, [locale]) useEffect(() => { diff --git a/apps/desktop/src/i18n/languages.test.ts b/apps/desktop/src/i18n/languages.test.ts index 792aad5f586b..fa6a8a4c22de 100644 --- a/apps/desktop/src/i18n/languages.test.ts +++ b/apps/desktop/src/i18n/languages.test.ts @@ -15,6 +15,9 @@ describe('desktop i18n languages', () => { expect(normalizeLocale('zh_HK')).toBe('zh-hant') expect(normalizeLocale('ja')).toBe('ja') expect(normalizeLocale('ja-JP')).toBe('ja') + expect(normalizeLocale('ar')).toBe('ar') + expect(normalizeLocale('AR-SA')).toBe('ar') + expect(normalizeLocale(' ar_eg ')).toBe('ar') }) it('falls back to English for empty or unsupported values', () => { @@ -32,6 +35,7 @@ describe('desktop i18n languages', () => { expect(isLocale('zh')).toBe(true) expect(isLocale('zh-hant')).toBe(true) expect(isLocale('ja')).toBe(true) + expect(isLocale('ar')).toBe(true) }) it('returns the persisted config value for supported locales', () => { @@ -39,5 +43,6 @@ describe('desktop i18n languages', () => { expect(localeConfigValue('zh')).toBe('zh') expect(localeConfigValue('zh-hant')).toBe('zh-hant') expect(localeConfigValue('ja')).toBe('ja') + expect(localeConfigValue('ar')).toBe('ar') }) }) diff --git a/apps/desktop/src/i18n/languages.ts b/apps/desktop/src/i18n/languages.ts index 96daf9b598dd..e4a1fb1e02eb 100644 --- a/apps/desktop/src/i18n/languages.ts +++ b/apps/desktop/src/i18n/languages.ts @@ -28,6 +28,12 @@ export const LOCALE_OPTIONS = [ name: '日本語', englishName: 'Japanese', configValue: 'ja' + }, + { + id: 'ar', + name: 'العربية', + englishName: 'Arabic', + configValue: 'ar' } ] as const satisfies readonly { configValue: string; englishName: string; id: Locale; name: string }[] @@ -64,7 +70,16 @@ const LOCALE_ALIASES: Record = { zh_hant_hk: 'zh-hant', ja: 'ja', 'ja-jp': 'ja', - ja_jp: 'ja' + ja_jp: 'ja', + ar: 'ar', + 'ar-sa': 'ar', + ar_sa: 'ar', + 'ar-ae': 'ar', + ar_ae: 'ar', + 'ar-eg': 'ar', + ar_eg: 'ar', + arabic: 'ar', + العربية: 'ar' } export function isLocale(value: unknown): value is Locale { diff --git a/apps/desktop/src/i18n/types.ts b/apps/desktop/src/i18n/types.ts index 3957d668a824..58836148a45d 100644 --- a/apps/desktop/src/i18n/types.ts +++ b/apps/desktop/src/i18n/types.ts @@ -5,7 +5,7 @@ // partial locales should use `defineLocale()` so missing desktop-only strings // fall back to English while new keys remain type-checked. -export type Locale = 'en' | 'zh' | 'zh-hant' | 'ja' +export type Locale = 'en' | 'zh' | 'zh-hant' | 'ja' | 'ar' export type ToolTitleKey = | 'browser_click' From e39e3fb011c12b7dc0cfb441129ad785a13b752d Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Fri, 24 Jul 2026 12:10:00 -0500 Subject: [PATCH 2/3] feat(web): add Arabic (ar) locale with RTL support Adds the Arabic catalog to the dashboard, registers it in the locale list and picker, and flips the document direction to RTL when Arabic is active. Introduces a `defineLocale` merge helper (mirroring the desktop app) so the Arabic catalog can be a partial override that falls back to English for any untranslated key instead of hand-porting every future string. Co-authored-by: morolab --- web/src/i18n/ar.ts | 701 ++++++++++++++++++++++++++++++++++ web/src/i18n/context.tsx | 15 +- web/src/i18n/define-locale.ts | 46 +++ web/src/i18n/types.ts | 3 +- web/src/index.css | 8 + 5 files changed, 771 insertions(+), 2 deletions(-) create mode 100644 web/src/i18n/ar.ts create mode 100644 web/src/i18n/define-locale.ts diff --git a/web/src/i18n/ar.ts b/web/src/i18n/ar.ts new file mode 100644 index 000000000000..0d961d9b49eb --- /dev/null +++ b/web/src/i18n/ar.ts @@ -0,0 +1,701 @@ +import { defineLocale } from "./define-locale"; + +export const ar = defineLocale({ + common: { + save: "حفظ", + saving: "جاري الحفظ...", + cancel: "إلغاء", + close: "إغلاق", + confirm: "تأكيد", + delete: "حذف", + refresh: "تحديث", + retry: "إعادة المحاولة", + search: "بحث...", + loading: "جاري التحميل...", + create: "إنشاء", + creating: "جاري الإنشاء...", + set: "تعيين", + replace: "استبدال", + clear: "مسح", + live: "مباشر", + off: "مغلق", + enabled: "مفعّل", + disabled: "معطّل", + active: "نشط", + inactive: "غير نشط", + unknown: "غير معروف", + untitled: "بدون عنوان", + none: "لا يوجد", + form: "نموذج", + noResults: "لا توجد نتائج", + of: "من", + page: "صفحة", + msgs: "رسائل", + tools: "أدوات", + match: "مطابقة", + other: "آخر", + configured: "مكوَّن", + removed: "مزال", + failedToToggle: "فشل التبديل", + failedToRemove: "فشل الحذف", + failedToReveal: "فشل الإظهار", + collapse: "طي", + expand: "توسيع", + general: "عام", + messaging: "مراسلة", + pluginLoadFailed: + "تعذر تحميل سكربت هذا المكوِّن الإضافي. تفقَّد علامة الشبكة (dashboard-plugins/…) ومسار ملحقات الخادم.", + pluginNotRegistered: + "لم يستدعِ سكربت المكوِّن الإضافي register()، أو حدث خطأ في السكربت. افتح وحدة تحكم المتصفح للتفاصيل.", + }, + + app: { + brand: "Hermes Agent", + brandShort: "HA", + closeNavigation: "إغلاق التنقل", + closeModelTools: "إغلاق النموذج والأدوات", + footer: { + org: "Nous Research", + }, + activeSessionsLabel: "الجلسات النشطة:", + gatewayStatusLabel: "حالة البوابة:", + gatewayStrip: { + failed: "فشل البدء", + off: "مغلق", + running: "يعمل", + starting: "قيد البدء", + stopped: "متوقف", + }, + nav: { + analytics: "التحليلات", + chat: "المحادثة", + config: "الإعدادات", + cron: "المهام المجدولة", + documentation: "التوثيق", + keys: "المفاتيح", + logs: "السجلات", + models: "النماذج", + profiles: "الملفات الشخصية", + plugins: "المكوِّنات الإضافية", + sessions: "الجلسات", + skills: "المهارات", + }, + modelToolsSheetSubtitle: "& أدوات", + modelToolsSheetTitle: "النموذج", + navigation: "التنقل", + openDocumentation: "فتح التوثيق في تبويب جديد", + openNavigation: "فتح التنقل", + pluginNavSection: "المكوِّنات الإضافية", + sessionsActiveCount: "{count} نشطة", + statusOverview: "نظرة عامة على الحالة", + system: "النظام", + webUi: "واجهة الويب", + }, + + status: { + actionFailed: "فشلت الإجراء", + actionFinished: "انتهى", + actions: "إجراءات", + agent: "العامل", + activeSessions: "الجلسات النشطة", + connected: "متصل", + connectedPlatforms: "المنصات المتصلة", + disconnected: "غير متصل", + error: "خطأ", + failed: "فشل", + gateway: "البوابة", + gatewayFailedToStart: "فشل بدء البوابة", + lastUpdate: "آخر تحديث", + noneRunning: "لا شيء", + notRunning: "لا يعمل", + pid: "PID", + platformDisconnected: "غير متصل", + platformError: "خطأ", + recentSessions: "الجلسات الأخيرة", + restartGateway: "إعادة تشغيل البوابة", + restartingGateway: "إعادة تشغيل البوابة…", + running: "يعمل", + runningRemote: "يعمل (عن بعد)", + startFailed: "فشل البدء", + starting: "قيد البدء", + startedInBackground: "بدء في الخلفية — تحقق من السجلات للتقدم", + stopped: "متوقف", + updateHermes: "تحديث Hermes", + updatingHermes: "جاري تحديث Hermes…", + waitingForOutput: "في انتظار الناتج…", + }, + + sessions: { + title: "الجلسات", + searchPlaceholder: "بحث في محتوى الرسائل...", + noSessions: "لا توجد جلسات حتى الآن", + noMatch: "لا توجد جلسات تطابق بحثك", + startConversation: "ابدأ محادثة لتظهر هنا", + noMessages: "لا توجد رسائل", + untitledSession: "جلسة بدون عنوان", + deleteSession: "حذف الجلسة", + confirmDeleteTitle: "حذف الجلسة؟", + confirmDeleteMessage: + "سيزيل هذا نهائيًا المحادثة وجميع رسائلها. لا يمكن التراجع عن هذا الإجراء.", + sessionDeleted: "تم حذف الجلسة", + failedToDelete: "فشل حذف الجلسة", + resumeInChat: "استئناف في المحادثة", + previousPage: "الصفحة السابقة", + nextPage: "الصفحة التالية", + roles: { + user: "المستخدم", + assistant: "المساعد", + system: "النظام", + tool: "أداة", + }, + }, + + analytics: { + period: "الفترة:", + totalTokens: "إجمالي الرموز", + totalSessions: "إجمالي الجلسات", + apiCalls: "استدعاءات API", + dailyTokenUsage: "استخدام الرموز اليومي", + dailyBreakdown: "التفصيل اليومي", + perModelBreakdown: "التفصيل حسب النموذج", + topSkills: "أعلى المهارات استخدامًا", + skill: "مهارة", + loads: "تم تحميل العامل", + edits: "تم إدارة العامل", + lastUsed: "آخر استخدام", + input: "إدخال", + output: "إخراج", + total: "الإجمالي", + noUsageData: "لا توجد بيانات استخدام لهذه الفترة", + startSession: "ابدأ جلسة لرؤية التحليلات هنا", + date: "التاريخ", + model: "النموذج", + tokens: "رموز", + perDayAvg: "في اليوم (متوسط)", + acrossModels: "عبر {count} نموذج", + inOut: "{input} إدخال / {output} إخراج", + }, + + models: { + modelsUsed: "النماذج المستخدمة", + estimatedCost: "التكلفة التقريبية", + tokens: "رموز", + sessions: "جلسات", + avgPerSession: "متوسط/جلسة", + apiCalls: "استدعاءات API", + toolCalls: "استدعاءات الأدوات", + noModelsData: "لا توجد بيانات نموذج لهذه الفترة", + startSession: "ابدأ جلسة لرؤية بيانات النماذج هنا", + }, + + logs: { + title: "السجلات", + autoRefresh: "تحديث تلقائي", + file: "ملف", + level: "المستوى", + component: "المكوِّن", + lines: "الأسطر", + noLogLines: "لم يُعثر على أسطر سجل", + }, + + cron: { + confirmDeleteMessage: + "يزيل هذا المهمة من الجدولة. لا يمكن التراجع عن الإجراء.", + confirmDeleteTitle: "حذف المهمة المجدولة؟", + newJob: "مهمة cron جديدة", + nameOptional: "الاسم (اختياري)", + namePlaceholder: "مثال: ملخص يومي", + prompt: "المطالبة", + promptPlaceholder: "ماذا يجب أن يفعل العامل في كل تشغيل؟", + schedule: "الجدولة (تعبير cron)", + schedulePlaceholder: "0 9 * * *", + deliverTo: "التسليم إلى", + scheduledJobs: "المهام المجدولة", + noJobs: "لم يتم تكوين أي مهام cron. أنشئ واحدة أعلاه.", + last: "آخر", + next: "التالي", + pause: "إيقاف مؤقت", + resume: "استئناف", + triggerNow: "تشغيل الآن", + delivery: { + local: "محلي", + telegram: "Telegram", + discord: "Discord", + slack: "Slack", + email: "البريد الإلكتروني", + }, + }, + + profiles: { + newProfile: "ملف شخصي جديد", + name: "الاسم", + namePlaceholder: "مثال: coder, writer, إلخ.", + nameRequired: "الاسم مطلوب", + nameRule: + "حروف صغيرة، أرقام، _ و - فقط؛ يجب البدء بحرف أو رقم؛ حتى 64 حرفًا.", + invalidName: "اسم ملف شخصي غير صالح", + allProfiles: "الملفات الشخصية", + noProfiles: "لم يُعثر على ملفات شخصية.", + defaultBadge: "افتراضي", + hasEnv: "env", + model: "النموذج", + skills: "المهارات", + rename: "إعادة تسمية", + editSoul: "تعديل SOUL.md", + soulSection: "SOUL.md (شخصية / موجه النظام)", + soulPlaceholder: "# كيف يجب أن يتصرف هذا العامل…", + saveSoul: "حفظ SOUL", + soulSaved: "تم حفظ SOUL.md", + openInTerminal: "نسخ أمر CLI", + commandCopied: "تم النسخ إلى الحافظة", + copyFailed: "تعذر النسخ", + confirmDeleteTitle: "حذف الملف الشخصي؟", + confirmDeleteMessage: + "هذا يحذف نهائيًا الملف الشخصي '{name}' — الإعدادات، المفاتيح، الذكريات، الجلسات، المهارات، مهام cron. لا يمكن التراجع عن الإجراء.", + created: "تم الإنشاء", + deleted: "تم الحذف", + renamed: "تمت إعادة التسمية", + }, + + pluginsPage: { + contextEngineLabel: "محرك السياق", + dashboardSlots: "فتحات لوحة التحكم", + disableRuntime: "تعطيل", + enableAfterInstall: "تفعيل بعد التثبيت", + enableRuntime: "تفعيل", + forceReinstall: "إعادة تثبيت إجباري (حذف المجلد الموجود أولاً)", + headline: + "اكتشف وثبِّت وفعِّل وحدِّث مكوِّنات Hermes الإضافية (مطابقة `hermes plugins`).", + identifierLabel: "رابط Git أو owner/repo", + inactive: "غير نشط", + installBtn: "تثبيت من Git", + installHeading: "تثبيت من GitHub / رابط Git", + installHint: "استخدم اختصار owner/repo أو رابط استنساخ كامل يبدأ بـ https:// أو git@.", + memoryProviderLabel: "مزوِّد الذاكرة", + missingEnvWarn: "عيّن هذه المتغيرات في المفاتيح قبل أن يتمكن المكوِّن الإضافي من العمل:", + noDashboardTab: "لا يوجد تبويب لوحة تحكم", + openTab: "فتح", + orphanHeading: "امتدادات لوحة التحكم فقط (لا يوجد تطابق plugin.yaml للعامل)", + pluginListHeading: "المكوِّنات الإضافية المثبتة", + providerDefaults: "مدمج / افتراضي", + providersHeading: "مكوِّنات مزوِّدات وقت التشغيل", + providersHint: + "يكتب memory.provider (فارغ = مدمج) و context.engine إلى config.yaml. يسري في الجلسة التالية.", + refreshDashboard: "إعادة فحص امتدادات لوحة التحكم", + removeConfirm: "إزالة هذا المكوِّن الإضافي من ~/.hermes/plugins/؟", + removeHint: "يمكن إزالة المكوِّنات المثبتة من المستخدم تحت ~/.hermes/plugins فقط.", + rescanHeading: "سجل مكوِّنات SPA", + rescanHint: "أعد الفحص بعد إضافة ملفات على القرص لتمكن الشريط الجانبي من التقاط المخطوطات الجديدة.", + runtimeHeading: "وقت تشغيل البوابة (مكوِّنات YAML)", + saveProviders: "حفظ إعدادات مزوِّد الذاكرة", + savedProviders: "تم حفظ إعدادات مزوِّد الذاكرة.", + sourceBadge: "المصدر", + authRequired: "مصادقة مطلوبة", + authRequiredHint: "شغِّل هذا الأمر للمصادقة:", + updateGit: "سحب من Git", + versionBadge: "الإصدار", + showInSidebar: "إظهار في الشريط الجانبي", + hideFromSidebar: "إخفاء من الشريط الجانبي", + }, + + skills: { + title: "المهارات", + searchPlaceholder: "بحث في المهارات ومجموعات الأدوات...", + enabledOf: "{enabled}/{total} مفعلة", + all: "الكل", + categories: "الفئات", + filters: "الفلاتر", + noSkills: "لم يُعثر على مهارات. يتم تحميل المهارات من ~/.hermes/skills/", + noSkillsMatch: "لا توجد مهارات تطابق بحثك أو الفلاتر.", + skillCount: "{count} مهارة{s}", + resultCount: "{count} نتيجة{s}", + noDescription: "لا يوجد وصف متاح.", + toolsets: "مجموعات الأدوات", + toolsetLabel: "مجموعة أدوات {name}", + noToolsetsMatch: "لا توجد مجموعات أدوات تطابق البحث.", + setupNeeded: "يلزم الإعداد", + disabledForCli: "معطَّل لواجهة CLI", + more: "+{count} المزيد", + }, + + config: { + configPath: "~/.hermes/config.yaml", + filters: "الفلاتر", + sections: "الأقسام", + exportConfig: "تصدير الإعدادات كـ JSON", + importConfig: "استيراد الإعدادات من JSON", + resetDefaults: "إعادة التعيين للافتراضيات", + resetScopeTooltip: "إعادة تعيين {scope} للافتراضيات", + confirmResetScope: + "إعادة تعيين جميع إعدادات {scope} للافتراضيات؟ هذا يحدِّث النموذج فقط — لا تُكتب التغييرات في config.yaml حتى تضغط على حفظ.", + resetScopeToast: "تمت إعادة تعيين {scope} للافتراضيات — راجع واحفظ للتثبيت", + rawYaml: "إعدادات YAML الأولية", + searchResults: "نتائج البحث", + fields: "حقل{s}", + noFieldsMatch: 'لا توجد حقول تطابق "{query}"', + configSaved: "تم حفظ الإعدادات", + yamlConfigSaved: "تم حفظ إعدادات YAML", + failedToSave: "فشل الحفظ", + failedToSaveYaml: "فشل حفظ YAML", + failedToLoadRaw: "فشل تحميل الإعدادات الأولية", + configImported: "تم استيراد الإعدادات — راجع واحفظ", + invalidJson: "ملف JSON غير صالح", + categories: { + general: "عام", + agent: "العامل", + terminal: "الطرفية", + display: "العرض", + delegation: "التفويض", + memory: "الذاكرة", + compression: "الضغط", + security: "الأمان", + browser: "المتصفح", + voice: "الصوت", + tts: "تحويل النص إلى كلام", + stt: "تحويل الكلام إلى نص", + logging: "التسجيل", + discord: "Discord", + auxiliary: "إضافي", + }, + }, + + env: { + changesNote: "يتم حفظ التغييرات على القرص فورًا. تلتقط الجلسات النشطة المفاتيح الجديدة تلقائيًا.", + confirmClearMessage: + "سيتم إزالة القيمة المخزنة لهذا المتغير من ملف .env. لا يمكن التراجع عن هذا من الواجهة.", + confirmClearTitle: "مسح هذا المفتاح؟", + description: "إدارة مفاتيح API والأسرار المخزَّنة في", + hideAdvanced: "إخفاء المتقدمة", + showAdvanced: "إظهار المتقدمة", + llmProviders: "مزوِّدو LLM", + providersConfigured: "{configured} من {total} مزوِّدين مكوَّنين", + getKey: "الحصول على المفتاح", + notConfigured: "{count} غير مكوَّن", + notSet: "غير معيّن", + keysCount: "{count} مفتاح{s}", + enterValue: "أدخل القيمة...", + replaceCurrentValue: "استبدال القيمة الحالية ({preview})", + showValue: "إظهار القيمة الحقيقية", + hideValue: "إخفاء القيمة", + }, + + oauth: { + title: "عمليات تسجيل دخول المزوِّدين (OAuth)", + providerLogins: "عمليات تسجيل دخول المزوِّدين (OAuth)", + description: + "{connected} من {total} مزوِّدي OAuth متصلون. تتدفق عمليات تسجيل الدخول حاليًا عبر CLI؛ انسخ الأمر وألصقه في طرفية للإعداد.", + connected: "متصل", + expired: "منتهي الصلاحية", + notConnected: "غير متصل. شغِّل {command} في طرفية.", + runInTerminal: "في طرفية.", + noProviders: "لم يُكتشف أي مزوِّد يدعم OAuth.", + login: "تسجيل الدخول", + disconnect: "قطع الاتصال", + managedExternally: "مدار خارجيًا", + copied: "تم النسخ ✓", + cli: "CLI", + copyCliCommand: "نسخ أمر CLI (لخارجي / احتياطي)", + connect: "اتصال", + sessionExpires: "تنتهي صلاحية الجلسة خلال {time}", + initiatingLogin: "بدء تدفق تسجيل الدخول…", + exchangingCode: "تبديل الرمز برموز الوصول…", + connectedClosing: "متصل! إغلاق…", + loginFailed: "فشل تسجيل الدخول.", + sessionExpired: "انتهت صلاحية الجلسة. انقر إعادة المحاولة لبدء تسجيل دخول جديد.", + reOpenAuth: "إعادة فتح صفحة المصادقة", + reOpenVerification: "إعادة فتح صفحة التحقق", + submitCode: "تقديم الرمز", + pasteCode: "ألصق رمز التفويض (مع لاحقة #state مقبولة)", + waitingAuth: "في انتظار تفويضك في المتصفح…", + enterCodePrompt: "فتح تبويب جديد. أدخل هذا الرمز عند الطلب:", + pkceStep1: "فتح تبويب جديد إلى claude.ai. سجِّل الدخول وانقر تفويض.", + pkceStep2: "انسخ رمز التفويض المعروض بعد التفويض.", + pkceStep3: "ألصقه أدناه وقدِّم.", + flowLabels: { + pkce: "تسجيل دخول المتصفح (PKCE)", + device_code: "رمز الجهاز", + external: "CLI خارجي", + }, + expiresIn: "تنتهي خلال {time}", + }, + + language: { + switchTo: "التبديل إلى الإنجليزية", + }, + + theme: { + title: "السمة", + switchTheme: "تبديل السمة", + }, + + achievements: { + hero: { + kicker: "Agentic Gamerscore", + title: "إنجازات Hermes", + subtitle: + "شارات Hermes قابلة للجمع مكتسبة من سجل الجلسات الفعلي. الإنجازات غير المكتملة المعروفة تُعرض كـ Discovered؛ تبقى الإنجازات السرية مخفية حتى يظهر السلوك المطابق لأول مرة.", + scan_subtitle: + "فحص سجل جلسات Hermes. يمكن أن يستغرق الفحص الأول 5–10 ثوانٍ على السجلات الكبيرة.", + }, + actions: { + rescan: "إعادة الفحص", + }, + stats: { + unlocked: "مفتوحة", + unlocked_hint: "شارات مكتسبة", + discovered: "مكتشفة", + discovered_hint: "معروفة، لم تُكتسب بعد", + secrets: "أسرار", + secrets_hint: "مخفية حتى أول إشارة", + highest_tier: "أعلى مستوى", + highest_tier_hint: "نحاس → فضة → ذهب ← ماس → أوليمبي", + latest: "الأحدث", + latest_hint_empty: "شغِّل Hermes أكثر", + none_yet: "لا توجد بعد", + }, + state: { + unlocked: "مفتوح", + discovered: "مكتشف", + secret: "سري", + }, + tier: { + target: "الهدف {tier}", + hidden: "مخفي", + complete: "مكتمل", + objective: "الهدف", + }, + progress: { + hidden: "مخفي", + }, + scan: { + building_headline: "إنشاء ملف الإنجاز…", + building_detail: + "قراءة الجلسات، استدعاءات الأدوات، بيانات تعريف النموذج، وحالة الفتح.", + starting_headline: "بدء فحص الإنجازات…", + progress_detail: + "تم فحص {scanned} من {total} جلسة · {pct}%. تنفتح الشارات مع تدفق المزيد من السجل.", + idle_detail: + "قراءة الجلسات، استدعاءات الأدوات، بيانات تعريف النموذج، وحالة الفتح. تظهر الشارات هنا عند فتحها.", + }, + guide: { + tiers_header: "المستويات", + secret_header: "الإنجازات السرية", + secret_body: + "تخفى الأسرار محددها الدقيق. بمجرد أن ترى Hermes إشارة ذات صلة، تصبح البطاقة مكتشفة وتعرض متطلباتها.", + scan_status_header: "حالة الفحص", + scan_status_body: + "تُفحص Hermes السجل المحلي مرة واحدة، ثم تظهر البطاقات تلقائيًا. لا يوجد توقف إذا استغرق هذا بضع ثوانٍ.", + what_scanned_header: "ما يتم فحصه", + what_scanned_body: + "الجلسات، استدعاءات الأدوات، بيانات تعريف النموذج، الأخطاء، الإنجازات، وحالة الفتح المحلية.", + }, + card: { + share_title: "مشاركة هذا الإنجاز", + share_label: "مشاركة {name}", + share_text: "مشاركة", + how_to_reveal: "كيفية الإظهار", + what_counts: "ما يُحتسب", + evidence_label: "دليل", + evidence_session_fallback: "جلسة", + no_evidence: "لا دليل بعد", + }, + latest: { + header: "آخر الفتوحات", + }, + empty: { + no_secrets_header: "لا توجد أسرار مخفية متبقية في هذا الفحص.", + no_secrets_body: + "تلميح: تبدأ الأسرار عادة من أنماط الفشل غير العادية أو أنماط المستخدم المتقدم — تعارضات المنافذ، حواجز الأذونات، متغيرات بيئة مفقودة، أخطاء YAML، تصادمات Docker، استخدام الإرجاع أو التبقي، ضربات التخزين المؤقت، أو إصلاحات صغيرة بعد الكثير من الأخطاء الحمراء." + }, + filters: { + all_categories: "الكل", + visibility_all: "الكل", + visibility_unlocked: "مفتوح", + visibility_discovered: "مكتشف", + visibility_secret: "سري", + }, + share: { + dialog_label: "مشاركة الإنجاز", + header: "مشاركة: {name}", + close: "إغلاق", + rendering: "جاري العرض…", + card_alt: "بطاقة مشاركة {name}", + error_generic: "حدث خطأ ما.", + x_title: "يفتح X مع منشومعد مسبقًا", + x_button: "مشاركة على X", + copy_title: "نسخ الصورة للصقها في منشورك", + copy_button: "نسخ الصورة", + copied: "تم النسخ ✓", + download_button: "تنزيل PNG", + hint: + "المشاركة على X تفتح منشورًا معدَّلاً مسبقًا في تبويب جديد. انقر نسخ الصورة أولاً إذا أردت شارة الإنجاز 1200×630 مرفقة — يسمح X باللصق مباشرة في مؤلف التغريد. تنزيل PNG يحفظ الملف للاستخدام anywhere.", + clipboard_unsupported: + "نسخ صورة الحافظة غير مدعوم في هذا المتصفح — استخدم التنزيل بدلاً من ذلك.", + tweet_text: "فتحت للتو {tier_part}\"{name}\" في Hermes Agent ☤" + }, + }, + + kanban: { + loading: "جاري تحميل لوحة Kanban…", + loadFailed: "فشل تحميل لوحة Kanban: ", + loadFailedHint: + "يُنشئ الواجهة الخلفية kanban.db تلقائيًا عند أول قراءة. إذا استمرت المشكلة، تفقَّد سجلات لوحة التحكم.", + board: "لوحة", + newBoard: "+ لوحة جديدة", + newBoardTitle: "لوحة جديدة", + newBoardDescription: + "تتيح اللوحات فصل تدفقات العمل غير المرتبطة — واحدة لكل مشروع أو مستودع أو مجال.", + slug: "المعرِّف", + slugHint: "— أحرف صغيرة، واصلات، مثال atm10-server", + displayName: "الاسم المعروض", + displayNameHint: "(اختياري)", + description: "الوصف", + descriptionHint: "(اختياري)", + icon: "أيقونة", + iconHint: "(حرف واحد أو رمز تعبيري)", + switchAfterCreate: "التبديل إلى هذه اللوحة بعد إنشائها", + cancel: "إلغاء", + creating: "جاري الإنشاء…", + createBoard: "إنشاء لوحة", + search: "بحث", + filterCards: "تصفية البطاقات…", + tenant: "المستأجر", + allTenants: "كل المستأجرين", + assignee: "المكلَّف", + allProfiles: "كل الملفات الشخصية", + showArchived: "إظهار المؤرشفة", + lanesByProfile: "مسارات حسب الملف الشخصي", + nudgeDispatcher: "دفع المُجلِّب", + refresh: "تحديث", + selected: "محدد", + complete: "إكمال", + archive: "أرشفة", + apply: "تطبيق", + clear: "مسح", + createTask: "إنشاء مهمة في هذا العمود", + noTasks: "— لا توجد مهام —", + unassigned: "غير مكلَّف", + needsAssignee: "يحتاج مكلِّف", + needsAssigneeHint: + "المتطلبات مُلباة، لكن المُجلِّب يتخطى هذه المهمة حتى تعيّن ملفًا شخصيًا.", + untitled: "(بدون عنوان)", + loadingDetail: "جاري التحميل…", + addComment: "إضافة تعليق… (Enter للتقديم)", + comment: "تعليق", + status: "الحالة", + workspace: "مساحة العمل", + skills: "المهارات", + createdBy: "أنشئ بواسطة", + result: "النتيجة", + comments: "التعليقات", + events: "الأحداث", + runHistory: "سجل التنفيذ", + workerLog: "سجل العامل", + loadingLog: "جاري تحميل السجل…", + noWorkerLog: + "— لا يوجد سجل عامل بعد (لم تُنشَ المهمة أو تم تدوير السجل) —", + noDescription: "— لا يوجد وصف —", + noComments: "— لا توجد تعليقات —", + edit: "تعديل", + save: "حفظ", + dependencies: "الاعتماديات", + parents: "الآباء:", + children: "الأبناء:", + none: "لا شيء", + addParent: "— إضافة أب —", + addChild: "— إضافة ابن —", + removeDependency: "إزالة الاعتماد", + block: "حظر", + unblock: "إلغاء الحظر", + notifyHomeChannels: "إشعار القنوات الرئيسية", + diagnostics: "التشخيصات", + hide: "إخفاء", + show: "إظهار", + attention: "انتباه", + tasksNeedAttention: "مهام تحتاج انتباه", + taskNeedsAttention: "مهمة واحدة تحتاج انتباه", + diagnostic: "تشخيصي", + open: "فتح", + close: "إغلاق (Esc)", + reassignTo: "إعادة تعيين إلى:", + copied: "تم النسخ", + copyCommand: "نسخ الأمر إلى الحافظة", + reclaim: "استعادة", + reassign: "إعادة تعيين", + renderingError: "اصطدم تبويب Kanban بخطأ عرض", + reloadView: "إعادة تحميل العرض", + wsAuthFailed: + "فشلت مصادقة WebSocket — أعد تحميل الصفحة لتحديث رمز الجلسة.", + markDone: "تحديد {n} مهمة كمكتملة؟", + markArchived: "أرشفة {n} مهمة؟", + warning: "تحذير", + phantomIds: "معرفات وهمية:", + active: "نشط", + ended: "منتهٍ", + noProfile: "(لا يوجد ملف شخصي)", + showAllAttempts: "إظهار كل المحاولات", + sendingUpdates: "إرسال التحديثات إلى", + sendNotifications: "إرسال إشعارات مكتمل / محظور / متروك لـ", + archiveBoardConfirm: + "أرشفة لوحة '{name}'؟ ستُنقَل إلى boards/_archived/ لتتمكن من استرجاعها لاحقًا. المهام على هذه اللوحة لن تظهر بعد الآن في أي مكان في الواجهة.", + archiveBoardTitle: "أرشفة هذه اللوحة", + boardSwitcherHint: "تتيح اللوحات فصل تدفقات عمل غير مرتبطة", + taskCreatedWarning: "تم إنشاء المهمة، لكن: ", + moveFailed: "فشل النقل: ", + bulkFailed: "مجموعة: ", + completionBlockedHallucination: "⚠ الحظر — معرّفات بطاقات وهمية", + suspectedHallucinatedReferences: "⚠ النص يشير إلى معرّفات بطاقات وهمية", + pickProfileFirst: "اختر ملفًا شخصيًا أولاً.", + unblockedMessage: "تم إلغاء حظر {id}. المهمة جاهزة للدورة التالية.", + unblockFailed: "فشل إلغاء الحظر: ", + reclaimedMessage: "تم استعادة {id}. المهمة عادت إلى جاهزة.", + reclaimFailed: "فشل الاستعادة: ", + reassignedMessage: "تم إعادة تعيين {id} إلى {profile}.", + reassignFailed: "فشل إعادة التعيين: ", + selectForBulk: "تحديد لإجراءات جماعية", + clickToEdit: "انقر للتعديل", + clickToEditAssignee: "انقر لتعديل المكلَّف", + emptyAssignee: "(فارغ = إزالة التكليف)", + columnLabels: { + triage: "فرز", + todo: "مهام", + ready: "جاهز", + running: "قيد التنفيذ", + blocked: "محظور", + done: "منجز", + archived: "مؤرشف", + }, + columnHelp: { + triage: "أفكار أولية — سيصِغ المُحدِّد المواصفات", + todo: "في انتظار الاعتماديات أو غير مكلَّف", + ready: "الاعتماديات مُلباة؛ عيّن ملفًا شخصيًا للتشغيل", + running: "مطالب بعامل — قيد التنفيذ", + blocked: "طلب العامل مدخلات من الإنسان", + done: "مكتمل", + archived: "مؤرشف", + }, + confirmDone: + "تحديد هذه المهمة كمكتملة؟ سيتم تحرير مطالبة العامل ويصبح الأبناء المعتمدون جاهزين.", + confirmArchive: + "أرشفة هذه المهمة؟ ستختفي من عرض اللوحة الافتراضي.", + confirmBlocked: + "تحديد هذه المهمة كمحظورة؟ سيتم تحرير مطالبة العامل.", + completionSummary: + "ملخص الإنجاز لـ {label}. يُخزَّن كنتيجة للمهمة.", + completionSummaryRequired: + "ملخص الإنجاز مطلوب قبل تحديد المهمة كمكتملة.", + triagePlaceholder: "فكرةrough — سيقوم الذكاء الاصطناعي بصياغة المواصفات…", + taskTitlePlaceholder: "عنوان المهمة الجديد…", + specifier: "محدِّد المواصفات", + assigneePlaceholder: "المكلَّف", + priority: "الأولوية", + skillsPlaceholder: + "مهارات (اختياري، مفصولة بفواصل): translation, github-code-review", + noParent: "— لا يوجد أب —", + workspacePathDir: "مسار مساحة العمل (مطلوب، مثال ~/projects/my-app)", + workspacePathOptional: + "مسار مساحة العمل (اختياري، مستنتج من المكلَّف إذا كان فارغًا)", + logTruncated: "(عرض آخر 100 كيلوبايت — السجل الكامل في ", + logAt: ")", + }, +}); diff --git a/web/src/i18n/context.tsx b/web/src/i18n/context.tsx index aa7a219b924f..e25b0cc4b50b 100644 --- a/web/src/i18n/context.tsx +++ b/web/src/i18n/context.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useState, useCallback, type ReactNode } from "react"; +import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from "react"; import type { Locale, Translations } from "./types"; import { en } from "./en"; import { zh } from "./zh"; @@ -16,6 +16,7 @@ import { ga } from "./ga"; import { pt } from "./pt"; import { ru } from "./ru"; import { hu } from "./hu"; +import { ar } from "./ar"; const TRANSLATIONS: Record = { en, @@ -34,8 +35,13 @@ const TRANSLATIONS: Record = { pt, ru, hu, + ar, }; +// Locales whose script flows right-to-left. Consumed by the provider to set the +// document direction so Tailwind's logical utilities (ms-/me-, ps-/pe-) flip. +const RTL_LOCALES = new Set(["ar"]); + // Display metadata for the language picker — endonym (native name) so users // recognize their language even if they don't speak the current UI language. // Exposed as a constant so the LanguageSwitcher and any future settings page @@ -62,6 +68,7 @@ export const LOCALE_META: Record = { pt: { name: "Português" }, ru: { name: "Русский" }, hu: { name: "Magyar" }, + ar: { name: "العربية" }, }; const SUPPORTED_LOCALES = Object.keys(TRANSLATIONS) as Locale[]; @@ -105,6 +112,12 @@ export function I18nProvider({ children }: { children: ReactNode }) { } }, []); + useEffect(() => { + if (typeof document === "undefined") return; + document.documentElement.lang = locale; + document.documentElement.dir = RTL_LOCALES.has(locale) ? "rtl" : "ltr"; + }, [locale]); + const value: I18nContextValue = { locale, setLocale, diff --git a/web/src/i18n/define-locale.ts b/web/src/i18n/define-locale.ts new file mode 100644 index 000000000000..401a0cccb563 --- /dev/null +++ b/web/src/i18n/define-locale.ts @@ -0,0 +1,46 @@ +import { en } from "./en"; +import type { Translations } from "./types"; + +// Partial-locale helper: a translation file supplies only the strings it has +// translated and every missing key falls back to English, while unknown keys +// still fail the type-check. Mirrors the desktop app's `defineLocale` so a new +// locale (e.g. Arabic) can land without hand-porting every future English key. + +type TranslationOverride = T extends (...args: never[]) => string + ? T + : T extends readonly unknown[] + ? T + : T extends string + ? string + : T extends object + ? { [K in keyof T]?: TranslationOverride } + : T; + +export type TranslationOverrides = TranslationOverride; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function mergeTranslations(base: T, overrides: TranslationOverride | undefined): T { + if (!isRecord(base) || !isRecord(overrides)) { + return (overrides ?? base) as T; + } + + const result: Record = { ...base }; + + for (const [key, value] of Object.entries(overrides)) { + if (value === undefined) { + continue; + } + + const baseValue = result[key]; + result[key] = isRecord(baseValue) && isRecord(value) ? mergeTranslations(baseValue, value) : value; + } + + return result as T; +} + +export function defineLocale(overrides: TranslationOverrides): Translations { + return mergeTranslations(en, overrides); +} diff --git a/web/src/i18n/types.ts b/web/src/i18n/types.ts index 52fd86acf6a4..1991a20f51da 100644 --- a/web/src/i18n/types.ts +++ b/web/src/i18n/types.ts @@ -14,7 +14,8 @@ export type Locale = | "ga" | "pt" | "ru" - | "hu"; + | "hu" + | "ar"; export interface Translations { // ── Common ── diff --git a/web/src/index.css b/web/src/index.css index 30b601e6c57b..e4b5e2777f96 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -245,3 +245,11 @@ code { font-size: 0.875rem; } 2px 2px; } +/* RTL support — Arabic and any future right-to-left locale. The i18n provider + sets `dir` on ; Tailwind v4's logical spacing utilities (ms-/me-, + ps-/pe-) and logical properties then flip automatically. Scoped so the + default LTR layout is untouched. */ +html[dir="rtl"] { + direction: rtl; +} + From a96f7c805bc0433ff6e2d952807de03d6d006b22 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Fri, 24 Jul 2026 12:10:03 -0500 Subject: [PATCH 3/3] feat(i18n): add Arabic (ar) catalog for agent/CLI messages Registers `ar` in the supported-language set and alias table and ships locales/ar.yaml at full key and placeholder parity with en.yaml, covering approval prompts and gateway slash-command replies. Identifiers, commands, paths, config keys, model/provider names, and {placeholder} tokens are kept verbatim. Co-authored-by: Da7-Tech <286182457+Da7-Tech@users.noreply.github.com> --- agent/i18n.py | 8 +- locales/ar.yaml | 418 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 locales/ar.yaml diff --git a/agent/i18n.py b/agent/i18n.py index b55b8128c92d..24f8ab0a0237 100644 --- a/agent/i18n.py +++ b/agent/i18n.py @@ -25,7 +25,8 @@ Language resolution order: 3. ``display.language`` from config.yaml 4. ``"en"`` (baseline) -Supported languages: en, zh, ja, de, es, fr, tr, uk. Unknown values fall back to en. +Supported languages: en, zh, zh-hant, ja, de, es, fr, tr, uk, af, ko, it, ga, +pt, ru, hu, ar. Unknown values fall back to en. """ from __future__ import annotations @@ -41,7 +42,7 @@ logger = logging.getLogger(__name__) SUPPORTED_LANGUAGES: tuple[str, ...] = ( "en", "zh", "zh-hant", "ja", "de", "es", "fr", "tr", "uk", - "af", "ko", "it", "ga", "pt", "ru", "hu", + "af", "ko", "it", "ga", "pt", "ru", "hu", "ar", ) DEFAULT_LANGUAGE = "en" @@ -78,6 +79,9 @@ _LANGUAGE_ALIASES: dict[str, str] = { "russian": "ru", "русский": "ru", "ru-ru": "ru", # Hungarian "hungarian": "hu", "magyar": "hu", "hu-hu": "hu", + # Arabic — bare "arabic"/endonym plus the common regional BCP-47 tags. + "arabic": "ar", "العربية": "ar", + "ar-sa": "ar", "ar-eg": "ar", "ar-ae": "ar", "ar-ma": "ar", "ar-dz": "ar", } _catalog_cache: dict[str, dict[str, str]] = {} diff --git a/locales/ar.yaml b/locales/ar.yaml new file mode 100644 index 000000000000..f2c78e8c6540 --- /dev/null +++ b/locales/ar.yaml @@ -0,0 +1,418 @@ +# Hermes static-message catalog -- Arabic (العربية) +# +# Mirrors locales/en.yaml key-for-key. Only user-facing static prose is +# translated: command names, /slash commands, config keys, paths, URLs, model +# IDs, provider names, emojis and {placeholder} tokens are kept verbatim so +# interpolation and identifiers keep working. tests/agent/test_i18n.py asserts +# key parity and placeholder parity against en.yaml. + +approval: + dangerous_header: "⚠️ أمر خطير: {description}" + choose_long: " [o] مرة | [s] الجلسة | [a] دائمًا | [d] رفض" + choose_short: " [o] مرة | [s] الجلسة | [d] رفض" + prompt_long: " الاختيار [o/s/a/D]: " + prompt_short: " الاختيار [o/s/D]: " + choose_smart_deny: " [o] مرة | [d] رفض" + prompt_smart_deny: " الاختيار [o/D]: " + smart_deny_once_inputs: "o,once" + smart_deny_deny_inputs: "d,deny" + timeout: " ⏱ انتهت المهلة - يتم رفض الأمر" + allowed_once: " ✓ مسموح مرة واحدة" + allowed_session: " ✓ مسموح لهذه الجلسة" + allowed_always: " ✓ أُضيف إلى قائمة السماح الدائمة" + denied: " ✗ مرفوض" + cancelled: " ✗ أُلغي" + blocklist_message: "هذا الأمر مدرج في قائمة الحظر غير المشروطة ولا يمكن الموافقة عليه." + +gateway: + approval_expired: "⚠️ انتهت صلاحية الموافقة (لم يعد الوكيل ينتظر). اطلب من الوكيل المحاولة مرة أخرى." + draining: "⏳ جارٍ إنهاء {count} وكيل نشط قبل إعادة التشغيل..." + goal_cleared: "✓ تم مسح الهدف." + no_active_goal: "لا يوجد هدف نشط." + config_read_failed: "⚠️ تعذّر قراءة config.yaml: {error}" + config_save_failed: "⚠️ تعذّر حفظ الإعدادات: {error}" + + model: + error_prefix: "خطأ: {error}" + switched: "تم التبديل إلى النموذج `{model}`" + provider_label: "المزوّد: {provider}" + context_label: "السياق: {tokens} رمزًا" + max_output_label: "أقصى خرج: {tokens} رمزًا" + cost_label: "التكلفة: {cost}" + capabilities_label: "القدرات: {capabilities}" + prompt_caching_enabled: "التخزين المؤقت للموجِّه: مُفعّل" + warning_prefix: "تحذير: {warning}" + saved_global: "حُفظ في config.yaml (‏`--global`)" + session_only_hint: "_(هذه الجلسة فقط — أضف `--global` للحفظ الدائم)_" + current_label: "الحالي: `{model}` على {provider}" + current_tag: " (الحالي)" + more_models_suffix: " (+{count} آخر)" + usage_switch_model: "`/model ` — تبديل النموذج" + usage_switch_provider: "`/model --provider ` — تبديل المزوّد" + usage_persist: "`/model --global` — حفظ دائم" + + agents: + header: "🤖 **الوكلاء والمهام النشطة**" + active_agents: "**الوكلاء النشطون:** {count}" + this_chat: " · هذه المحادثة" + more: "... و{count} آخر" + running_processes: "**العمليات الخلفية الجارية:** {count}" + async_jobs: "**مهام البوابة غير المتزامنة:** {count}" + none: "لا يوجد وكلاء نشطون أو مهام جارية." + state_starting: "قيد البدء" + state_running: "قيد التشغيل" + + approve: + no_pending: "لا يوجد أمر معلّق للموافقة عليه." + once_singular: "✅ تمت الموافقة على الأمر. الوكيل يستأنف..." + once_plural: "✅ تمت الموافقة على الأوامر ({count} أوامر). الوكيل يستأنف..." + session_singular: "✅ تمت الموافقة على الأمر (النمط مُوافق عليه لهذه الجلسة). الوكيل يستأنف..." + session_plural: "✅ تمت الموافقة على الأوامر (النمط مُوافق عليه لهذه الجلسة) ({count} أوامر). الوكيل يستأنف..." + always_singular: "✅ تمت الموافقة على الأمر (النمط مُوافق عليه دائمًا). الوكيل يستأنف..." + always_plural: "✅ تمت الموافقة على الأوامر (النمط مُوافق عليه دائمًا) ({count} أوامر). الوكيل يستأنف..." + + background: + usage: "الاستخدام: /background \nمثال: /background لخّص أهم قصص HN اليوم\n\nيشغّل الموجِّه في جلسة منفصلة. يمكنك متابعة المحادثة — ستظهر النتيجة هنا عند الانتهاء." + started: "🔄 بدأت مهمة خلفية: \"{preview}\"\nمعرّف المهمة: {task_id}\nيمكنك متابعة المحادثة — ستظهر النتائج عند الانتهاء." + + branch: + db_unavailable: "قاعدة بيانات الجلسات غير متاحة." + no_conversation: "لا توجد محادثة للتفريع — أرسل رسالة أولًا." + create_failed: "فشل إنشاء الفرع: {error}" + switch_failed: "أُنشئ الفرع لكن تعذّر التبديل إليه." + branched_one: "⑂ تم التفريع إلى **{title}** (نُسخت {count} رسالة)\nالأصل: `{parent}`\nالفرع: `{new}`\nاستخدم `/resume` للعودة إلى الأصل." + branched_many: "⑂ تم التفريع إلى **{title}** (نُسخت {count} رسالة)\nالأصل: `{parent}`\nالفرع: `{new}`\nاستخدم `/resume` للعودة إلى الأصل." + + commands: + usage: "الاستخدام: `/commands [page]`" + skill_header: "⚡ **أوامر المهارات**:" + default_desc: "أمر مهارة" + none: "لا توجد أوامر متاحة." + header: "📚 **الأوامر** ({total} إجمالًا، صفحة {page}/{total_pages})" + nav_prev: "`/commands {page}` ← السابق" + nav_next: "التالي → `/commands {page}`" + out_of_range: "_(الصفحة المطلوبة {requested} خارج النطاق، يتم عرض الصفحة {page}.)_" + + compress: + not_enough: "لا توجد محادثة كافية للضغط (يلزم 4 رسائل على الأقل)." + no_provider: "لا يوجد مزوّد مُهيّأ -- لا يمكن الضغط." + nothing_to_do: "لا شيء للضغط بعد (النص كله لا يزال سياقًا محميًا)." + aggressive_unsupported: "الخيار --aggressive غير مدعوم؛ استخدم '/compress here [N]' للإبقاء على أحدث التبادلات فقط، أو /undo لإسقاط الأدوار." + focus_line: "التركيز: \"{topic}\"" + summary_failed: "⚠️ فشل توليد الملخّص ({error}). أُزيلت {count} رسالة تاريخية واستُبدلت بعنصر نائب؛ لم يعد السياق الأقدم قابلًا للاسترجاع. فكّر في مراجعة إعداد نموذج auxiliary.compression." + aborted: "⚠️ أُلغي الضغط ({error}). لم تُسقط أي رسائل — المحادثة كما هي. شغّل /compress للمحاولة مجددًا، أو /reset لجلسة نظيفة، أو راجع إعداد نموذج auxiliary.compression." + aux_failed: "ℹ️ فشل نموذج الضغط المُهيّأ `{model}` ({error}). تم التعافي باستخدام نموذجك الرئيسي — السياق سليم — لكن قد ترغب في مراجعة `auxiliary.compression.model` في config.yaml." + failed: "فشل الضغط: {error}" + + debug: + upload_failed: "✗ فشل رفع تقرير التصحيح: {error}" + header: "**تم رفع تقرير التصحيح:**" + auto_delete: "⏱ ستُحذف اللصقات تلقائيًا خلال 6 ساعات." + full_logs_hint: "لرفع السجلات الكاملة، استخدم `hermes debug share` من واجهة الأوامر." + share_hint: "شارك هذه الروابط مع فريق Hermes للحصول على الدعم." + + deny: + stale: "❌ رُفض الأمر (الموافقة كانت قديمة)." + no_pending: "لا يوجد أمر معلّق لرفضه." + denied_singular: "❌ رُفض الأمر." + denied_plural: "❌ رُفضت الأوامر ({count} أوامر)." + denied_reason_singular: "❌ رُفض الأمر. السبب المُبلّغ للوكيل: \"{reason}\"" + denied_reason_plural: "❌ رُفضت الأوامر ({count} أوامر). السبب المُبلّغ للوكيل: \"{reason}\"" + + fast: + not_supported: "⚡ /fast متاح فقط لنماذج OpenAI التي تدعم المعالجة ذات الأولوية." + status: "⚡ المعالجة ذات الأولوية\n\nالوضع الحالي: `{mode}`\n\n_الاستخدام:_ `/fast `" + unknown_arg: "⚠️ وسيط غير معروف: `{arg}`\n\n**الخيارات الصالحة:** normal, fast, status" + saved: "⚡ ✓ المعالجة ذات الأولوية: **{label}** (حُفظت في الإعدادات)\n_(تسري على الرسالة التالية)_" + session_only: "⚡ ✓ المعالجة ذات الأولوية: **{label}** (هذه الجلسة فقط)" + label_fast: "سريع" + label_normal: "عادي" + status_fast: "fast" + status_normal: "normal" + picker_title: "⚡ **المعالجة ذات الأولوية**\n\nالوضع الحالي: `{mode}`\n\nاختر خيارًا:" + choice_fast: "fast — المعالجة ذات الأولوية مُفعّلة" + choice_normal: "normal — المعالجة القياسية" + + footer: + status: "📎 تذييل التشغيل: **{state}**\nالحقول: `{fields}`\nالمنصّة: `{platform}`" + usage: "الاستخدام: `/footer [on|off|status]`" + saved: "📎 تذييل التشغيل: **{state}**{example}\n_(حُفظ عالميًا — يسري على الرسالة التالية)_" + example_line: "\nمثال: `{preview}`" + state_on: "مُفعّل" + state_off: "مُعطّل" + + goal: + unavailable: "الأهداف غير متاحة في هذه الجلسة." + no_goal_set: "لم يُحدَّد هدف." + paused: "⏸ أُوقف الهدف مؤقتًا: {goal}" + no_resume: "لا يوجد هدف لاستئنافه." + resumed: "▶ استُؤنف الهدف: {goal}\nأرسل أي رسالة للمتابعة، أو انتظر — سأتخذ الخطوة التالية في الدور القادم." + invalid: "هدف غير صالح: {error}" + set: "⊙ حُدِّد الهدف (ميزانية {budget} دور): {goal}\nسأواصل العمل حتى يكتمل الهدف، أو توقفه/تمسحه، أو تنفد الميزانية.\nالتحكم: /goal status · /goal pause · /goal resume · /goal clear" + + help: + header: "📖 **أوامر Hermes**\n" + skill_header: "\n⚡ **أوامر المهارات** ({count} نشطة):" + more_use_commands: "\n... و{count} آخر. استخدم `/commands` للقائمة الكاملة المُقسّمة إلى صفحات." + + insights: + invalid_days: "قيمة --days غير صالحة: {value}" + error: "خطأ في توليد الرؤى: {error}" + + kanban: + error_prefix: "⚠ خطأ kanban: {error}" + subscribed_suffix: "(مُشترك — ستُخطَر عند اكتمال {task_id} أو حجبها)" + truncated_suffix: "… (مقتطع؛ استخدم `hermes kanban …` في الطرفية للخرج الكامل)" + no_output: "(لا يوجد خرج)" + wake: + completed: "اكتملت" + gave_up: "استسلم (نفدت المحاولات)" + crashed: "تعطّل (خرج العامل)؛ سيعيد المُوزّع المحاولة" + timed_out: "انتهت المهلة؛ سيعيد المُوزّع المحاولة" + blocked: "محجوبة؛ تحتاج إلى انتباه" + status_default: "تغيّرت الحالة" + status_joiner: "، " + message: "[kanban] المهمة {task_id} {status}.\nالعنوان: {title}\nالمُكلَّف: @{assignee}\nاللوحة: {board}\n\nراجع النتيجة أو قرّر الخطوة التالية." + + personality: + none_configured: "لا توجد شخصيات مُهيّأة في `{path}/config.yaml`" + header: "🎭 **الشخصيات المتاحة**\n" + none_option: "• `none` — (بدون طبقة شخصية)" + item: "• `{name}` — {preview}" + usage: "\nالاستخدام: `/personality `" + save_failed: "⚠️ فشل حفظ تغيير الشخصية: {error}" + cleared: "🎭 مُسحت الشخصية — يتم استخدام سلوك الوكيل الأساسي.\n_(يسري على الرسالة التالية)_" + set_to: "🎭 ضُبطت الشخصية إلى **{name}**\n_(يسري على الرسالة التالية)_" + unknown: "شخصية غير معروفة: `{name}`\n\nالمتاح: {available}" + + profile: + header: "👤 **الملف الشخصي:** `{profile}`" + home: "📂 **المجلد الرئيسي:** `{home}`" + + reasoning: + level_default: "medium (افتراضي)" + level_disabled: "none (مُعطّل)" + scope_session: "تجاوز الجلسة" + scope_global: "الإعدادات العامة" + status: "🧠 **إعدادات التفكير**\n\n**الجهد:** `{level}`\n**النطاق:** {scope}\n**العرض:** {display}\n\n_الاستخدام:_ `/reasoning [--global]`" + display_on: "مُفعّل ✓" + display_off: "مُعطّل" + display_set_on: "🧠 ✓ عرض التفكير: **مُفعّل**\nسيُعرض تفكير النموذج قبل كل رد على **{platform}**." + display_set_off: "🧠 ✓ عرض التفكير: **مُعطّل** لـ **{platform}**" + reset_global_unsupported: "⚠️ `/reasoning reset --global` غير مدعوم. استخدم `/reasoning --global` لتغيير الافتراضي العام." + reset_done: "🧠 ✓ مُسح تجاوز التفكير للجلسة؛ العودة إلى الإعدادات العامة." + unknown_arg: "⚠️ وسيط غير معروف: `{arg}`\n\n**المستويات الصالحة:** none, minimal, low, medium, high, xhigh, max, ultra\n**العرض:** show, hide\n**الحفظ:** أضف `--global` للحفظ بعد هذه الجلسة" + picker_title: "🧠 **إعدادات التفكير**\n\n**الجهد:** `{level}`\n**النطاق:** {scope}\n**العرض:** {display}\n\nاختر خيارًا:" + choice_none: "none — تعطيل التفكير" + choice_reset: "reset — مسح تجاوز الجلسة" + choice_show: "عرض التفكير في الردود" + choice_hide: "إخفاء التفكير من الردود" + set_global: "🧠 ✓ ضُبط جهد التفكير إلى `{effort}` (حُفظ في الإعدادات)\n_(يسري على الرسالة التالية)_" + set_global_save_failed: "🧠 ✓ ضُبط جهد التفكير إلى `{effort}` (الجلسة فقط — فشل حفظ الإعدادات)\n_(يسري على الرسالة التالية)_" + set_session: "🧠 ✓ ضُبط جهد التفكير إلى `{effort}` (الجلسة فقط — أضف `--global` للحفظ الدائم)\n_(يسري على الرسالة التالية)_" + + reload_mcp: + cancelled: "🟡 أُلغي /reload-mcp. أدوات MCP دون تغيير." + always_followup: "ℹ️ ستُشغَّل استدعاءات `/reload-mcp` المستقبلية دون تأكيد. أعِد التفعيل عبر `approvals.mcp_reload_confirm: true` في config.yaml." + confirm_prompt: "⚠️ **تأكيد /reload-mcp**\n\nإعادة تحميل خوادم MCP تعيد بناء مجموعة الأدوات لهذه الجلسة و**تُبطل التخزين المؤقت لموجِّه المزوّد** — ستُعاد الرسالة التالية بكامل رموز الإدخال. على النماذج ذات السياق الطويل أو التفكير العالي قد يكون ذلك مكلفًا.\n\nاختر:\n• **الموافقة مرة** — أعِد التحميل الآن\n• **الموافقة دائمًا** — أعِد التحميل الآن واكتم هذا التنبيه نهائيًا\n• **إلغاء** — اترك أدوات MCP دون تغيير\n\n_بديل نصي: رُدّ بـ `/approve` أو `/always` أو `/cancel`._" + header: "🔄 **أُعيد تحميل خوادم MCP**\n" + reconnected: "♻️ أُعيد الاتصال: {names}" + added: "➕ أُضيف: {names}" + removed: "➖ أُزيل: {names}" + none_connected: "لا توجد خوادم MCP متصلة." + tools_available: "\n🔧 {tools} أداة متاحة من {servers} خادم" + failed: "❌ فشلت إعادة تحميل MCP: {error}" + + reload_skills: + header: "🔄 **أُعيد تحميل المهارات**\n" + no_new: "لم تُكتشف مهارات جديدة." + total: "\n📚 {count} مهارة متاحة" + added_header: "➕ **مهارات مُضافة:**" + removed_header: "➖ **مهارات مُزالة:**" + item_with_desc: " - {name}: {desc}" + item_no_desc: " - {name}" + failed: "❌ فشلت إعادة تحميل المهارات: {error}" + + reset: + header_default: "✨ أُعيد ضبط الجلسة! بداية جديدة." + header_new: "✨ بدأت جلسة جديدة!" + header_titled: "✨ بدأت جلسة جديدة: {title}" + title_rejected: "\n⚠️ رُفض العنوان: {error}" + title_error_untitled: "\n⚠️ {error} — بدأت الجلسة دون عنوان." + title_empty_untitled: "\n⚠️ العنوان فارغ بعد التنظيف — بدأت الجلسة دون عنوان." + tip: "\n✦ نصيحة: {tip}" + + restart: + in_progress: "⏳ إعادة تشغيل البوابة قيد التنفيذ بالفعل..." + restarting: "♻ جارٍ إعادة تشغيل البوابة. إن لم تُخطَر خلال 60 ثانية، أعِد التشغيل من وحدة التحكّم باستخدام `hermes gateway restart`." + + resume: + db_unavailable: "قاعدة بيانات الجلسات غير متاحة." + parse_error: "⚠️ تعذّر تحليل وسائط `/resume`: {error}.\nاستخدم علامات اقتباس حول العناوين التي تحتوي مسافات، مثال: `/resume \"Project A Plan\"`." + matrix_no_named_sessions: "لم يُعثر على جلسات مُسمّاة لغرفة Matrix هذه.\nاستخدم `/title My Session` لتسمية جلسة الغرفة الحالية، أو `/resume --all` لسرد كل جلسات Matrix، أو `/resume --cross-room ` لتجاوز حدود الغرفة صراحةً." + matrix_blocked_no_origin: "⚠️ حُجب /resume في Matrix: هذه الجلسة المُسمّاة لا تملك أصل غرفة مُسجّلًا، لذا لن يستأنفها Hermes داخل الغرفة الحالية افتراضيًا. استخدم `/resume --cross-room {name}` إن كنت تريد عمدًا تجاوز حدود الغرفة." + matrix_blocked_other_room: "⚠️ حُجب /resume في Matrix: تلك الجلسة تخصّ غرفة Matrix مختلفة ({room}). استخدم `/resume --cross-room {name}` إن كنت تريد عمدًا استئنافها هنا." + matrix_cross_room_success: "⚠️ استئناف عبر الغرف: استُؤنفت **{title}** داخل غرفة Matrix ‏**{room}**.\nستستخدم الرسائل المستقبلية في هذه الغرفة ذلك النص حتى `/reset` أو `/resume` آخر.{msg_part}" + blocked_not_owner: "⚠️ حُجب /resume: '**{name}**' تخصّ مستخدمًا أو محادثة مختلفة. يمكنك فقط استئناف جلسات هذه المحادثة." + no_named_sessions: "لم يُعثر على جلسات مُسمّاة.\nاستخدم `/title My Session` لتسمية جلستك الحالية، ثم `/resume My Session` للعودة إليها لاحقًا." + list_header: "📋 **الجلسات المُسمّاة**\n" + list_item: "• **{title}**{preview_part}" + list_item_numbered: "{index}. **{title}**{preview_part}" + list_preview_suffix: " — _{preview}_" + list_footer: "\nالاستخدام: `/resume `" + list_footer_numbered: "\nالاستخدام: `/resume ` أو `/resume ` (مثال: `/resume 1` للأحدث)" + list_failed: "تعذّر سرد الجلسات: {error}" + out_of_range: "مؤشر الاستئناف {index} خارج النطاق.\nاستخدم `/resume` دون وسائط لعرض الجلسات المتاحة." + not_found: "لم يُعثر على جلسة تطابق '**{name}**'.\nاستخدم `/resume` دون وسائط لعرض الجلسات المتاحة." + already_on: "📌 أنت بالفعل على الجلسة **{name}**." + switch_failed: "فشل تبديل الجلسة." + resumed_one: "↻ استُؤنفت الجلسة **{title}** ({count} رسالة). استُعيدت المحادثة." + resumed_many: "↻ استُؤنفت الجلسة **{title}** ({count} رسالة). استُعيدت المحادثة." + resumed_no_count: "↻ استُؤنفت الجلسة **{title}**. استُعيدت المحادثة." + + retry: + no_previous: "لا توجد رسالة سابقة لإعادة المحاولة." + + rollback: + not_enabled: "نقاط التحقّق غير مُفعّلة.\nفعّلها في config.yaml:\n```\ncheckpoints:\n enabled: true\n```" + none_found: "لم يُعثر على نقاط تحقّق لـ {cwd}" + invalid_number: "رقم نقطة تحقّق غير صالح. استخدم 1-{max}." + restored: "✅ استُعيد إلى نقطة التحقّق {hash}: {reason}\nحُفظت لقطة ما قبل التراجع تلقائيًا." + restore_failed: "❌ {error}" + + set_home: + save_failed: "فشل حفظ القناة الرئيسية: {error}" + success: "✅ ضُبطت القناة الرئيسية إلى **{name}** (المعرّف: {chat_id}).\nستُسلَّم مهام cron والرسائل عبر المنصّات هنا." + + status: + header: "📊 **حالة بوابة Hermes**" + matrix_scope_header: "**نطاق Matrix:**" + matrix_scope_room: " الغرفة: {room}" + matrix_scope_room_id: " معرّف الغرفة: {room_id}" + matrix_scope_thread: " معرّف الخيط: {thread_id}" + matrix_scope_mode: " نطاق الجلسة: {scope}" + matrix_scope_key: " مفتاح الجلسة: {session_key}" + session_id: "**معرّف الجلسة:** `{session_id}`" + title: "**العنوان:** {title}" + created: "**أُنشئت:** {timestamp}" + last_activity: "**آخر نشاط:** {timestamp}" + model: "**النموذج:** `{model}`" + model_provider: "**النموذج:** `{model}` ({provider})" + context: "**السياق:** {used} / {total} ({pct}%)" + context_used: "**السياق:** ~{used} رمزًا" + tokens: "**رموز API التراكمية (تُعاد كل استدعاء):** {tokens}" + agent_running: "**الوكيل قيد التشغيل:** {state}" + state_yes: "نعم ⚡" + state_no: "لا" + queued: "**المتابعات في الطابور:** {count}" + platforms: "**المنصّات المتصلة:** {platforms}" + + stop: + stopped_pending: "⚡ تم الإيقاف. لم يكن الوكيل قد بدأ بعد — يمكنك متابعة هذه الجلسة." + stopped: "⚡ تم الإيقاف. يمكنك متابعة هذه الجلسة." + no_active: "لا توجد مهمة نشطة لإيقافها." + + title: + db_unavailable: "قاعدة بيانات الجلسات غير متاحة." + warn_prefix: "⚠️ {error}" + empty_after_clean: "⚠️ العنوان فارغ بعد التنظيف. من فضلك استخدم أحرفًا قابلة للطباعة." + set_to: "✏️ ضُبط عنوان الجلسة: **{title}**" + not_found: "لم يُعثر على الجلسة في قاعدة البيانات." + current_with_title: "📌 الجلسة: `{session_id}`\nالعنوان: **{title}**" + current_no_title: "📌 الجلسة: `{session_id}`\nلم يُضبط عنوان. الاستخدام: `/title My Session Name`" + + topic: + not_telegram_dm: "الأمر /topic متاح فقط في محادثات Telegram الخاصة." + no_session_db: "قاعدة بيانات الجلسات غير متاحة." + unauthorized: "غير مصرّح لك باستخدام /topic على هذا البوت." + restore_needs_topic: "لاستعادة جلسة، أنشئ أو افتح موضوع Telegram أولًا، ثم أرسل /topic داخل ذلك الموضوع. لإنشاء موضوع جديد، افتح All Messages وأرسل أي رسالة هناك." + topics_disabled: "مواضيع Telegram غير مُفعّلة لهذا البوت بعد.\n\nكيفية تفعيلها:\n1. افتح @BotFather.\n2. اختر البوت الخاص بك.\n3. افتح Bot Settings → Threads Settings.\n4. فعّل Threaded Mode وتأكّد من السماح للمستخدمين بإنشاء خيوط جديدة.\n\nثم أرسل /topic مرة أخرى." + topics_user_disallowed: "مواضيع Telegram مُفعّلة، لكن غير مسموح للمستخدمين بإنشاء مواضيع.\n\nافتح @BotFather → اختر البوت → Bot Settings → Threads Settings، ثم أوقف 'Disallow users to create new threads'.\n\nثم أرسل /topic مرة أخرى." + enable_failed: "فشل تفعيل وضع مواضيع Telegram: {error}" + bound_status: "هذا الموضوع مرتبط بـ:\nالجلسة: {label}\nالمعرّف: {session_id}\n\nاستخدم /new لاستبدال هذا الموضوع بجلسة جديدة.\nللعمل المتوازي، افتح All Messages وأرسل رسالة هناك لإنشاء موضوع آخر." + thread_ready: "مواضيع Telegram متعددة الجلسات مُفعّلة.\n\nسيُستخدم هذا الموضوع كجلسة Hermes مستقلة. استخدم /new لاستبدال جلسة هذا الموضوع الحالية. للعمل المتوازي، افتح All Messages وأرسل رسالة هناك لإنشاء موضوع آخر." + untitled_session: "جلسة دون عنوان" + + undo: + nothing: "لا شيء للتراجع عنه." + removed: "↩️ تم التراجع عن {turns} دور ({count} رسالة).\nنُسخ احتياطيًا إلى: \"{preview}\"\nانسخ/عدّل النص أعلاه وأرسله لإعادة التوجيه من هنا." + invalid_count: "عدد غير صالح \"{arg}\" — استخدم /undo أو /undo N." + + update: + platform_not_messaging: "✗ /update متاح فقط من منصّات المراسلة. شغّل `hermes update` من الطرفية." + not_git_repo: "✗ ليس مستودع git — لا يمكن التحديث." + hermes_cmd_not_found: "✗ تعذّر تحديد موقع أمر `hermes`. Hermes يعمل، لكن أمر التحديث لم يجد الملف التنفيذي على PATH أو عبر مُفسّر Python الحالي. جرّب تشغيل `hermes update` يدويًا في طرفيتك." + start_failed: "✗ فشل بدء التحديث: {error}" + starting: "⚕ جارٍ بدء تحديث Hermes… سأبثّ التقدّم هنا." + + usage: + rate_limits: "⏱️ **حدود المعدّل:** {state}" + header_session: "📊 **استخدام رموز الجلسة**" + label_model: "النموذج: `{model}`" + label_input_tokens: "رموز الإدخال: {count}" + label_cache_read: "رموز قراءة التخزين المؤقت: {count}" + label_cache_write: "رموز كتابة التخزين المؤقت: {count}" + label_output_tokens: "رموز الإخراج: {count}" + label_total: "الإجمالي: {count}" + label_api_calls: "استدعاءات API: {count}" + label_cost: "التكلفة: {prefix}${amount}" + label_cost_included: "التكلفة: مشمولة" + label_context: "السياق: {used} / {total} ({pct}%)" + label_compressions: "عمليات الضغط: {count}" + breakdown_header: "🧩 **تفصيل السياق** _(تقديري)_" + breakdown_line: "• {label}: ~{count} ({pct}%)" + breakdown_cat_system_prompt: "موجّه النظام" + breakdown_cat_tool_definitions: "تعريفات الأدوات" + breakdown_cat_rules: "القواعد" + breakdown_cat_skills: "المهارات" + breakdown_cat_mcp: "MCP" + breakdown_cat_subagent_definitions: "تعريفات الوكلاء الفرعيين" + breakdown_cat_memory: "الذاكرة" + breakdown_cat_conversation: "المحادثة" + header_session_info: "📊 **معلومات الجلسة**" + label_messages: "الرسائل: {count}" + label_estimated_context: "السياق المُقدّر: ~{count} رمزًا" + detailed_after_first: "_(الاستخدام المُفصّل متاح بعد أول رد من الوكيل)_" + no_data: "لا تتوفّر بيانات استخدام لهذه الجلسة." + unknown_subcommand: "أمر فرعي غير معروف لـ /usage: `{args}`. جرّب `/usage` أو `/usage reset [--force]`." + reset_wrong_provider: "إعادة تعيين الاستخدام المُدّخر متاحة فقط على مزوّد openai-codex. بدّل عبر `/model` أولًا." + + credits: + not_logged_in: "لم تُسجّل الدخول إلى Nous Portal. سجّل الدخول لرؤية رصيد أرصدتك وإعادة الشحن." + + verbose: + not_enabled: "الأمر `/verbose` غير مُفعّل لمنصّات المراسلة.\n\nفعّله في `config.yaml`:\n```yaml\ndisplay:\n tool_progress_command: true\n```" + mode_off: "⚙️ تقدّم الأدوات: **مُعطّل** — لا يُعرض نشاط الأدوات." + mode_new: "⚙️ تقدّم الأدوات: **NEW** — يُعرض عند تغيّر الأداة (طول المعاينة: `display.tool_preview_length`، افتراضي 40)." + mode_all: "⚙️ تقدّم الأدوات: **ALL** — يُعرض كل استدعاء أداة (طول المعاينة: `display.tool_preview_length`، افتراضي 40)." + mode_verbose: "⚙️ تقدّم الأدوات: **VERBOSE** — كل استدعاء أداة مع وسائطه الكاملة." + mode_log: "⚙️ تقدّم الأدوات: **LOG** — صامت في المحادثة؛ تُكتب استدعاءات الأدوات إلى ~/.hermes/logs/tool_calls.log." + saved_suffix: "_(حُفظ لـ **{platform}** — يسري على الرسالة التالية)_" + save_failed: "_(تعذّر الحفظ في الإعدادات: {error})_" + + voice: + enabled_voice_only: "وضع الصوت مُفعّل.\nسأردّ بالصوت عندما ترسل رسائل صوتية.\nاستخدم /voice tts للحصول على ردود صوتية لكل الرسائل." + disabled_text: "وضع الصوت مُعطّل. ردود نصّية فقط." + tts_enabled: "TTS التلقائي مُفعّل.\nستتضمّن كل الردود رسالة صوتية." + status_mode: "وضع الصوت: {label}" + status_channel: "قناة الصوت: #{channel}" + status_participants: "المشاركون: {count}" + status_member: " - {name}{status}" + speaking: " (يتحدّث)" + enabled_short: "وضع الصوت مُفعّل." + disabled_short: "وضع الصوت مُعطّل." + label_off: "مُعطّل (نص فقط)" + label_voice_only: "مُفعّل (رد صوتي على الرسائل الصوتية)" + label_all: "TTS (رد صوتي على كل الرسائل)" + help: "{toggle}\n\n**كيف يعمل /voice**\n• `/voice on` — رد صوتي عند إرسال رسالة صوتية\n• `/voice tts` — رد صوتي على *كل* رسالة\n• `/voice off` — العودة إلى ردود نصّية فقط\n• `/voice status` — عرض الوضع الحالي\n• `/voice` (بدون وسيط) — تبديل سريع بين التفعيل والتعطيل{channels}" + help_channels: "\n\n**قنوات الصوت الحيّة (Discord)**\n• انضمّ إلى قناة صوتية أولًا، ثم `/voice channel` — سأنضمّ وأستمع وأنطق ردودي\n• `/voice leave` — قطع الاتصال بالقناة الصوتية" + + yolo: + disabled: "⚠️ وضع YOLO **مُعطّل** لهذه الجلسة — ستتطلّب الأوامر الخطيرة موافقة." + enabled: "⚡ وضع YOLO **مُفعّل** لهذه الجلسة — كل الأوامر مُوافق عليها تلقائيًا. استخدمه بحذر." + + shared: + session_db_unavailable: "قاعدة بيانات الجلسات غير متاحة." + session_db_unavailable_prefix: "قاعدة بيانات الجلسات غير متاحة" + session_not_found: "لم يُعثر على الجلسة في قاعدة البيانات." + warn_passthrough: "⚠️ {error}"