diff --git a/client/src/components/Admin/GitHubPanel.tsx b/client/src/components/Admin/GitHubPanel.tsx index 141b701..398f9f6 100644 --- a/client/src/components/Admin/GitHubPanel.tsx +++ b/client/src/components/Admin/GitHubPanel.tsx @@ -130,7 +130,7 @@ export default function GitHubPanel() {
Ko-fi
-
{language === 'de' ? 'Hilft mir, TREK weiterzuentwickeln' : 'Helps me keep building TREK'}
+
{t('admin.github.support')}
@@ -148,7 +148,7 @@ export default function GitHubPanel() {
Buy Me a Coffee
-
{language === 'de' ? 'Hilft mir, TREK weiterzuentwickeln' : 'Helps me keep building TREK'}
+
{t('admin.github.support')}
diff --git a/client/src/components/Layout/DemoBanner.tsx b/client/src/components/Layout/DemoBanner.tsx index 9d0bc20..fff46be 100644 --- a/client/src/components/Layout/DemoBanner.tsx +++ b/client/src/components/Layout/DemoBanner.tsx @@ -118,6 +118,38 @@ const texts: Record = { selfHostLink: 'alójalo tú mismo', close: 'Entendido', }, + ar: { + titleBefore: 'مرحبًا بك في ', + titleAfter: '', + title: 'مرحبًا بك في النسخة التجريبية من TREK', + description: 'يمكنك عرض الرحلات وتعديلها وإنشاء رحلات جديدة. تتم إعادة ضبط جميع التغييرات تلقائيًا كل ساعة.', + resetIn: 'إعادة الضبط التالية خلال', + minutes: 'دقيقة', + uploadNote: 'رفع الملفات (الصور والمستندات وصور الغلاف) معطّل في وضع العرض التجريبي.', + fullVersionTitle: 'وفي النسخة الكاملة أيضًا:', + features: [ + 'رفع الملفات (الصور والمستندات وصور الغلاف)', + 'إدارة مفاتيح API (خرائط Google والطقس)', + 'إدارة المستخدمين والصلاحيات', + 'نسخ احتياطية تلقائية', + 'إدارة الإضافات (تفعيل/تعطيل)', + 'تسجيل دخول موحد OIDC / SSO', + ], + addonsTitle: 'إضافات مرنة (يمكن تعطيلها في النسخة الكاملة)', + addons: [ + ['Vacay', 'مخطط إجازات مع تقويم وعطل ودمج مستخدمين'], + ['Atlas', 'خريطة عالمية مع الدول التي تمت زيارتها وإحصاءات السفر'], + ['Packing', 'قوائم تجهيز لكل رحلة'], + ['Budget', 'تتبع المصروفات مع التقسيم'], + ['Documents', 'إرفاق الملفات بالرحلات'], + ['Widgets', 'محول عملات ومناطق زمنية'], + ], + whatIs: 'ما هو TREK؟', + whatIsDesc: 'مخطط رحلات مستضاف ذاتيًا مع تعاون لحظي وخرائط تفاعلية وتسجيل دخول OIDC ووضع داكن.', + selfHost: 'مفتوح المصدر — ', + selfHostLink: 'استضفه بنفسك', + close: 'فهمت', + }, } const featureIcons = [Upload, Key, Users, Database, Puzzle, Shield] diff --git a/client/src/components/Vacay/VacayMonthCard.tsx b/client/src/components/Vacay/VacayMonthCard.tsx index d47de05..d28bce4 100644 --- a/client/src/components/Vacay/VacayMonthCard.tsx +++ b/client/src/components/Vacay/VacayMonthCard.tsx @@ -6,9 +6,11 @@ import type { HolidaysMap, VacayEntry } from '../../types' const WEEKDAYS_EN = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'] const WEEKDAYS_DE = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'] const WEEKDAYS_ES = ['Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa', 'Do'] +const WEEKDAYS_AR = ['اث', 'ثل', 'أر', 'خم', 'جم', 'سب', 'أح'] const MONTHS_EN = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] const MONTHS_DE = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'] const MONTHS_ES = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'] +const MONTHS_AR = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'] function hexToRgba(hex: string, alpha: number): string { const r = parseInt(hex.slice(1, 3), 16) @@ -34,8 +36,8 @@ export default function VacayMonthCard({ onCellClick, companyMode, blockWeekends }: VacayMonthCardProps) { const { language } = useTranslation() - const weekdays = language === 'de' ? WEEKDAYS_DE : language === 'es' ? WEEKDAYS_ES : WEEKDAYS_EN - const monthNames = language === 'de' ? MONTHS_DE : language === 'es' ? MONTHS_ES : MONTHS_EN + const weekdays = language === 'de' ? WEEKDAYS_DE : language === 'es' ? WEEKDAYS_ES : language === 'ar' ? WEEKDAYS_AR : WEEKDAYS_EN + const monthNames = language === 'de' ? MONTHS_DE : language === 'es' ? MONTHS_ES : language === 'ar' ? MONTHS_AR : MONTHS_EN const weeks = useMemo(() => { const firstDay = new Date(year, month, 1) diff --git a/client/src/i18n/TranslationContext.tsx b/client/src/i18n/TranslationContext.tsx index 51e5c4f..97f3df1 100644 --- a/client/src/i18n/TranslationContext.tsx +++ b/client/src/i18n/TranslationContext.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useMemo, ReactNode } from 'react' +import React, { createContext, useContext, useEffect, useMemo, ReactNode } from 'react' import { useSettingsStore } from '../store/settingsStore' import de from './translations/de' import en from './translations/en' @@ -7,18 +7,35 @@ import fr from './translations/fr' import ru from './translations/ru' import zh from './translations/zh' import nl from './translations/nl' +import ar from './translations/ar' type TranslationStrings = Record -const translations: Record = { de, en, es, fr, ru, zh, nl } -const LOCALES: Record = { de: 'de-DE', en: 'en-US', es: 'es-ES', fr: 'fr-FR', ru: 'ru-RU', zh: 'zh-CN', nl: 'nl-NL' } +export const SUPPORTED_LANGUAGES = [ + { value: 'de', label: 'Deutsch' }, + { value: 'en', label: 'English' }, + { value: 'es', label: 'Español' }, + { value: 'fr', label: 'Français' }, + { value: 'nl', label: 'Nederlands' }, + { value: 'ru', label: 'Русский' }, + { value: 'zh', label: '中文' }, + { value: 'ar', label: 'العربية' }, +] as const + +const translations: Record = { de, en, es, fr, ru, zh, nl, ar } +const LOCALES: Record = { de: 'de-DE', en: 'en-US', es: 'es-ES', fr: 'fr-FR', ru: 'ru-RU', zh: 'zh-CN', nl: 'nl-NL', ar: 'ar-SA' } +const RTL_LANGUAGES = new Set(['ar']) export function getLocaleForLanguage(language: string): string { return LOCALES[language] || LOCALES.en } export function getIntlLanguage(language: string): string { - return ['de', 'es', 'fr', 'ru', 'zh', 'nl'].includes(language) ? language : 'en' + return ['de', 'es', 'fr', 'ru', 'zh', 'nl', 'ar'].includes(language) ? language : 'en' +} + +export function isRtlLanguage(language: string): boolean { + return RTL_LANGUAGES.has(language) } interface TranslationContextValue { @@ -36,6 +53,11 @@ interface TranslationProviderProps { export function TranslationProvider({ children }: TranslationProviderProps) { const language = useSettingsStore((s) => s.settings.language) || 'en' + useEffect(() => { + document.documentElement.lang = language + document.documentElement.dir = isRtlLanguage(language) ? 'rtl' : 'ltr' + }, [language]) + const value = useMemo((): TranslationContextValue => { const strings = translations[language] || translations.en const fallback = translations.en diff --git a/client/src/i18n/index.ts b/client/src/i18n/index.ts index 31d9369..4d221cd 100644 --- a/client/src/i18n/index.ts +++ b/client/src/i18n/index.ts @@ -1 +1,8 @@ -export { TranslationProvider, useTranslation, getLocaleForLanguage, getIntlLanguage } from './TranslationContext' +export { + TranslationProvider, + useTranslation, + getLocaleForLanguage, + getIntlLanguage, + isRtlLanguage, + SUPPORTED_LANGUAGES, +} from './TranslationContext' diff --git a/client/src/i18n/translations/ar.ts b/client/src/i18n/translations/ar.ts new file mode 100644 index 0000000..f20c010 --- /dev/null +++ b/client/src/i18n/translations/ar.ts @@ -0,0 +1,703 @@ +import en from './en' + +const ar: Record = { + ...en, + + // Common + 'common.save': 'حفظ', + 'common.cancel': 'إلغاء', + 'common.delete': 'حذف', + 'common.edit': 'تعديل', + 'common.add': 'إضافة', + 'common.loading': 'جارٍ التحميل...', + 'common.error': 'خطأ', + 'common.back': 'رجوع', + 'common.all': 'الكل', + 'common.close': 'إغلاق', + 'common.open': 'فتح', + 'common.upload': 'رفع', + 'common.search': 'بحث', + 'common.confirm': 'تأكيد', + 'common.ok': 'حسنًا', + 'common.yes': 'نعم', + 'common.no': 'لا', + 'common.or': 'أو', + 'common.none': 'لا شيء', + 'common.date': 'التاريخ', + 'common.rename': 'إعادة تسمية', + 'common.name': 'الاسم', + 'common.email': 'البريد الإلكتروني', + 'common.password': 'كلمة المرور', + 'common.saving': 'جارٍ الحفظ...', + 'common.update': 'تحديث', + 'common.change': 'تغيير', + 'common.uploading': 'جارٍ الرفع...', + 'common.backToPlanning': 'العودة إلى التخطيط', + 'common.reset': 'إعادة تعيين', + + // Navbar + 'nav.trip': 'الرحلة', + 'nav.share': 'مشاركة', + 'nav.settings': 'الإعدادات', + 'nav.admin': 'الإدارة', + 'nav.logout': 'تسجيل الخروج', + 'nav.lightMode': 'الوضع الفاتح', + 'nav.darkMode': 'الوضع الداكن', + 'nav.autoMode': 'الوضع التلقائي', + 'nav.administrator': 'المسؤول', + 'nav.myTrips': 'رحلاتي', + + // Dashboard + 'dashboard.title': 'رحلاتي', + 'dashboard.subtitle.loading': 'جارٍ تحميل الرحلات...', + 'dashboard.subtitle.trips': '{count} رحلة ({archived} مؤرشفة)', + 'dashboard.subtitle.empty': 'ابدأ رحلتك الأولى', + 'dashboard.subtitle.activeOne': '{count} رحلة نشطة', + 'dashboard.subtitle.activeMany': '{count} رحلات نشطة', + 'dashboard.subtitle.archivedSuffix': ' · {count} مؤرشفة', + 'dashboard.newTrip': 'رحلة جديدة', + 'dashboard.currency': 'العملة', + 'dashboard.timezone': 'المناطق الزمنية', + 'dashboard.localTime': 'المحلي', + 'dashboard.timezoneCustomTitle': 'منطقة زمنية مخصصة', + 'dashboard.timezoneCustomLabelPlaceholder': 'الاسم (اختياري)', + 'dashboard.timezoneCustomTzPlaceholder': 'مثال: Asia/Riyadh', + 'dashboard.timezoneCustomAdd': 'إضافة', + 'dashboard.timezoneCustomErrorEmpty': 'أدخل معرّف منطقة زمنية', + 'dashboard.timezoneCustomErrorInvalid': 'منطقة زمنية غير صالحة. استخدم صيغة مثل Asia/Riyadh', + 'dashboard.timezoneCustomErrorDuplicate': 'مضافة بالفعل', + 'dashboard.emptyTitle': 'لا توجد رحلات بعد', + 'dashboard.emptyText': 'أنشئ رحلتك الأولى وابدأ التخطيط', + 'dashboard.emptyButton': 'إنشاء أول رحلة', + 'dashboard.nextTrip': 'الرحلة القادمة', + 'dashboard.shared': 'مشتركة', + 'dashboard.sharedBy': 'شاركها {name}', + 'dashboard.days': 'الأيام', + 'dashboard.places': 'الأماكن', + 'dashboard.archive': 'أرشفة', + 'dashboard.restore': 'استعادة', + 'dashboard.archived': 'مؤرشفة', + 'dashboard.status.ongoing': 'جارية', + 'dashboard.status.today': 'اليوم', + 'dashboard.status.tomorrow': 'غدًا', + 'dashboard.status.past': 'منتهية', + 'dashboard.status.daysLeft': 'متبقي {count} يوم', + 'dashboard.toast.loadError': 'فشل تحميل الرحلات', + 'dashboard.toast.created': 'تم إنشاء الرحلة بنجاح', + 'dashboard.toast.createError': 'فشل إنشاء الرحلة', + 'dashboard.toast.updated': 'تم تحديث الرحلة', + 'dashboard.toast.updateError': 'فشل تحديث الرحلة', + 'dashboard.toast.deleted': 'تم حذف الرحلة', + 'dashboard.toast.deleteError': 'فشل حذف الرحلة', + 'dashboard.toast.archived': 'تمت أرشفة الرحلة', + 'dashboard.toast.archiveError': 'فشل الأرشفة', + 'dashboard.toast.restored': 'تمت استعادة الرحلة', + 'dashboard.toast.restoreError': 'فشل الاستعادة', + 'dashboard.confirm.delete': 'حذف الرحلة "{title}"؟ سيتم حذف جميع الأماكن والخطط نهائيًا.', + 'dashboard.editTrip': 'تعديل الرحلة', + 'dashboard.createTrip': 'إنشاء رحلة جديدة', + 'dashboard.tripTitle': 'العنوان', + 'dashboard.tripTitlePlaceholder': 'مثال: صيف في اليابان', + 'dashboard.tripDescription': 'الوصف', + 'dashboard.tripDescriptionPlaceholder': 'عمّ تتحدث هذه الرحلة؟', + 'dashboard.startDate': 'تاريخ البداية', + 'dashboard.endDate': 'تاريخ النهاية', + 'dashboard.noDateHint': 'لا يوجد تاريخ محدد. سيتم إنشاء 7 أيام افتراضية ويمكنك تغيير ذلك لاحقًا.', + 'dashboard.coverImage': 'صورة الغلاف', + 'dashboard.addCoverImage': 'إضافة صورة غلاف', + 'dashboard.coverSaved': 'تم حفظ صورة الغلاف', + 'dashboard.coverUploadError': 'فشل الرفع', + 'dashboard.coverRemoveError': 'فشل الإزالة', + 'dashboard.titleRequired': 'العنوان مطلوب', + 'dashboard.endDateError': 'يجب أن يكون تاريخ النهاية بعد البداية', + + // Settings + 'settings.title': 'الإعدادات', + 'settings.subtitle': 'ضبط إعداداتك الشخصية', + 'settings.map': 'الخريطة', + 'settings.mapTemplate': 'قالب الخريطة', + 'settings.mapTemplatePlaceholder.select': 'اختر قالبًا...', + 'settings.mapDefaultHint': 'اتركه فارغًا لاستخدام OpenStreetMap افتراضيًا', + 'settings.mapHint': 'قالب URL لبلاطات الخريطة', + 'settings.latitude': 'خط العرض', + 'settings.longitude': 'خط الطول', + 'settings.saveMap': 'حفظ الخريطة', + 'settings.apiKeys': 'مفاتيح API', + 'settings.mapsKey': 'مفتاح Google Maps API', + 'settings.mapsKeyHint': 'للبحث عن الأماكن. يتطلب Places API (New).', + 'settings.weatherKey': 'مفتاح OpenWeatherMap API', + 'settings.weatherKeyHint': 'لبيانات الطقس.', + 'settings.keyPlaceholder': 'أدخل المفتاح...', + 'settings.configured': 'مُعدّ', + 'settings.saveKeys': 'حفظ المفاتيح', + 'settings.display': 'العرض', + 'settings.colorMode': 'نمط الألوان', + 'settings.light': 'فاتح', + 'settings.dark': 'داكن', + 'settings.auto': 'تلقائي', + 'settings.language': 'اللغة', + 'settings.temperature': 'وحدة الحرارة', + 'settings.timeFormat': 'تنسيق الوقت', + 'settings.routeCalculation': 'حساب المسار', + 'settings.on': 'تشغيل', + 'settings.off': 'إيقاف', + 'settings.account': 'الحساب', + 'settings.username': 'اسم المستخدم', + 'settings.role': 'الدور', + 'settings.roleAdmin': 'مسؤول', + 'settings.roleUser': 'مستخدم', + 'settings.oidcLinked': 'مرتبط مع', + 'settings.changePassword': 'تغيير كلمة المرور', + 'settings.currentPassword': 'كلمة المرور الحالية', + 'settings.currentPasswordRequired': 'كلمة المرور الحالية مطلوبة', + 'settings.newPassword': 'كلمة المرور الجديدة', + 'settings.confirmPassword': 'تأكيد كلمة المرور الجديدة', + 'settings.updatePassword': 'تحديث كلمة المرور', + 'settings.passwordRequired': 'أدخل كلمة المرور الحالية والجديدة', + 'settings.passwordTooShort': 'يجب أن تتكون كلمة المرور من 8 أحرف على الأقل', + 'settings.passwordMismatch': 'كلمتا المرور غير متطابقتين', + 'settings.passwordWeak': 'يجب أن تحتوي كلمة المرور على حرف كبير وحرف صغير ورقم', + 'settings.passwordChanged': 'تم تغيير كلمة المرور بنجاح', + 'settings.deleteAccount': 'حذف الحساب', + 'settings.deleteAccountTitle': 'هل تريد حذف حسابك؟', + 'settings.deleteAccountWarning': 'سيتم حذف حسابك وجميع رحلاتك وأماكنك وملفاتك نهائيًا. لا يمكن التراجع عن ذلك.', + 'settings.deleteAccountConfirm': 'حذف نهائي', + 'settings.deleteBlockedTitle': 'الحذف غير ممكن', + 'settings.deleteBlockedMessage': 'أنت المسؤول الوحيد. قم بترقية مستخدم آخر إلى مسؤول قبل حذف حسابك.', + 'settings.saveProfile': 'حفظ الملف الشخصي', + 'settings.toast.mapSaved': 'تم حفظ إعدادات الخريطة', + 'settings.toast.keysSaved': 'تم حفظ مفاتيح API', + 'settings.toast.displaySaved': 'تم حفظ إعدادات العرض', + 'settings.toast.profileSaved': 'تم حفظ الملف الشخصي', + 'settings.uploadAvatar': 'رفع صورة الملف الشخصي', + 'settings.removeAvatar': 'إزالة صورة الملف الشخصي', + 'settings.avatarUploaded': 'تم تحديث صورة الملف الشخصي', + 'settings.avatarRemoved': 'تمت إزالة صورة الملف الشخصي', + 'settings.avatarError': 'فشل الرفع', + + // Login + 'login.error': 'فشل تسجيل الدخول. يرجى التحقق من بياناتك.', + 'login.tagline': 'رحلاتك.\nخطتك.', + 'login.description': 'خطط لرحلاتك بشكل تعاوني مع خرائط تفاعلية وميزانيات ومزامنة لحظية.', + 'login.features.maps': 'خرائط تفاعلية', + 'login.features.mapsDesc': 'Google Places ومسارات وتجميع', + 'login.features.realtime': 'مزامنة فورية', + 'login.features.realtimeDesc': 'خططوا معًا عبر WebSocket', + 'login.features.budget': 'تتبع الميزانية', + 'login.features.budgetDesc': 'فئات ورسوم وتقسيم لكل شخص', + 'login.features.collab': 'تعاون', + 'login.features.collabDesc': 'عدة مستخدمين مع رحلات مشتركة', + 'login.features.packing': 'قوائم تجهيز', + 'login.features.packingDesc': 'فئات وتقدم واقتراحات', + 'login.features.bookings': 'الحجوزات', + 'login.features.bookingsDesc': 'رحلات وفنادق ومطاعم وغير ذلك', + 'login.features.files': 'المستندات', + 'login.features.filesDesc': 'رفع الملفات وإدارتها', + 'login.features.routes': 'مسارات ذكية', + 'login.features.routesDesc': 'تحسين تلقائي وتصدير إلى Google Maps', + 'login.selfHosted': 'استضافة ذاتية · مفتوح المصدر · بياناتك تبقى ملكك', + 'login.title': 'تسجيل الدخول', + 'login.subtitle': 'مرحبًا بعودتك', + 'login.signingIn': 'جارٍ تسجيل الدخول…', + 'login.signIn': 'دخول', + 'login.createAdmin': 'إنشاء حساب مسؤول', + 'login.createAdminHint': 'أعد إعداد أول حساب مسؤول لـ TREK.', + 'login.createAccount': 'إنشاء حساب', + 'login.createAccountHint': 'سجّل حسابًا جديدًا.', + 'login.creating': 'جارٍ الإنشاء…', + 'login.noAccount': 'ليس لديك حساب؟', + 'login.hasAccount': 'لديك حساب بالفعل؟', + 'login.register': 'تسجيل', + 'login.username': 'اسم المستخدم', + 'login.oidc.registrationDisabled': 'التسجيل معطّل. تواصل مع المسؤول.', + 'login.oidc.noEmail': 'لم يتم استلام بريد إلكتروني من المزوّد.', + 'login.oidc.tokenFailed': 'فشلت المصادقة.', + 'login.oidc.invalidState': 'جلسة غير صالحة. حاول مرة أخرى.', + 'login.demoFailed': 'فشل الدخول إلى العرض التجريبي', + 'login.oidcSignIn': 'تسجيل الدخول عبر {name}', + 'login.oidcOnly': 'تم تعطيل المصادقة بكلمة المرور. يرجى تسجيل الدخول عبر مزود SSO.', + 'login.demoHint': 'جرّب العرض التجريبي دون الحاجة للتسجيل', + + // Register + 'register.passwordMismatch': 'كلمتا المرور غير متطابقتين', + 'register.passwordTooShort': 'يجب أن تتكون كلمة المرور من 6 أحرف على الأقل', + 'register.failed': 'فشل التسجيل', + 'register.getStarted': 'ابدأ الآن', + 'register.subtitle': 'أنشئ حسابًا وابدأ التخطيط لرحلات أحلامك.', + 'register.feature1': 'خطط رحلات غير محدودة', + 'register.feature2': 'عرض خريطة تفاعلي', + 'register.feature3': 'إدارة الأماكن والفئات', + 'register.feature4': 'تتبع الحجوزات', + 'register.feature5': 'إنشاء قوائم تجهيز', + 'register.feature6': 'حفظ الصور والملفات', + 'register.createAccount': 'إنشاء حساب', + 'register.startPlanning': 'ابدأ تخطيط رحلتك', + 'register.minChars': '6 أحرف على الأقل', + 'register.confirmPassword': 'تأكيد كلمة المرور', + 'register.repeatPassword': 'إعادة كلمة المرور', + 'register.registering': 'جارٍ التسجيل...', + 'register.register': 'تسجيل', + 'register.hasAccount': 'لديك حساب بالفعل؟', + 'register.signIn': 'تسجيل الدخول', + + // Admin + 'admin.title': 'الإدارة', + 'admin.subtitle': 'إدارة المستخدمين وإعدادات النظام', + 'admin.tabs.users': 'المستخدمون', + 'admin.tabs.categories': 'الفئات', + 'admin.tabs.backup': 'النسخ الاحتياطي', + 'admin.tabs.settings': 'الإعدادات', + 'admin.tabs.addons': 'الإضافات', + 'admin.tabs.github': 'GitHub', + 'admin.stats.users': 'المستخدمون', + 'admin.stats.trips': 'الرحلات', + 'admin.stats.places': 'الأماكن', + 'admin.stats.photos': 'الصور', + 'admin.stats.files': 'الملفات', + 'admin.table.user': 'المستخدم', + 'admin.table.email': 'البريد الإلكتروني', + 'admin.table.role': 'الدور', + 'admin.table.created': 'تم الإنشاء', + 'admin.table.lastLogin': 'آخر تسجيل دخول', + 'admin.table.actions': 'الإجراءات', + 'admin.you': '(أنت)', + 'admin.createUser': 'إنشاء مستخدم', + 'admin.editUser': 'تعديل المستخدم', + 'admin.newPassword': 'كلمة مرور جديدة', + 'admin.newPasswordHint': 'اتركه فارغًا للاحتفاظ بالحالية', + 'admin.deleteUserTitle': 'حذف المستخدم', + 'admin.toast.loadError': 'فشل تحميل بيانات الإدارة', + 'admin.toast.userUpdated': 'تم تحديث المستخدم', + 'admin.toast.updateError': 'فشل التحديث', + 'admin.toast.userDeleted': 'تم حذف المستخدم', + 'admin.toast.deleteError': 'فشل الحذف', + 'admin.toast.userCreated': 'تم إنشاء المستخدم', + 'admin.toast.createError': 'فشل إنشاء المستخدم', + 'admin.allowRegistration': 'السماح بالتسجيل', + 'admin.apiKeys': 'مفاتيح API', + 'admin.validateKey': 'اختبار', + 'admin.keyValid': 'متصل', + 'admin.keyInvalid': 'غير صالح', + 'admin.keySaved': 'تم حفظ مفاتيح API', + 'admin.addons.title': 'الإضافات', + 'admin.addons.subtitle': 'فعّل أو عطّل الميزات لتخصيص تجربة TREK.', + 'admin.addons.enabled': 'مفعّل', + 'admin.addons.disabled': 'معطّل', + 'admin.addons.type.trip': 'رحلة', + 'admin.addons.type.global': 'عام', + 'admin.addons.tripHint': 'متاح كعلامة تبويب داخل كل رحلة', + 'admin.addons.globalHint': 'متاح كقسم مستقل في التنقل الرئيسي', + 'admin.addons.toast.updated': 'تم تحديث الإضافة', + 'admin.addons.toast.error': 'فشل تحديث الإضافة', + 'admin.weather.title': 'بيانات الطقس', + 'admin.weather.description': 'يستخدم TREK خدمة Open-Meteo كمصدر لبيانات الطقس. وهي خدمة مجانية ومفتوحة المصدر ولا تتطلب مفتاح API.', + 'admin.github.title': 'سجل الإصدارات', + 'admin.github.subtitle': 'آخر التحديثات من {repo}', + 'admin.github.latest': 'الأحدث', + 'admin.github.prerelease': 'إصدار تجريبي', + 'admin.github.showDetails': 'إظهار التفاصيل', + 'admin.github.hideDetails': 'إخفاء التفاصيل', + 'admin.github.loadMore': 'تحميل المزيد', + 'admin.github.loading': 'جارٍ التحميل...', + 'admin.github.error': 'فشل تحميل الإصدارات', + 'admin.github.by': 'بواسطة', + 'admin.github.support': 'يساعدني على مواصلة تطوير TREK', + 'admin.update.available': 'يتوفر تحديث', + 'admin.update.button': 'عرض على GitHub', + 'admin.update.install': 'تثبيت التحديث', + 'admin.update.confirm': 'حدّث الآن', + 'admin.update.installing': 'جارٍ التحديث…', + 'admin.update.success': 'تم تثبيت التحديث. ستتم إعادة تشغيل الخادم…', + 'admin.update.failed': 'فشل التحديث', + 'admin.update.reloadHint': 'يرجى إعادة تحميل الصفحة بعد بضع ثوانٍ.', + + // Trip / planner + 'trip.tabs.plan': 'الخطة', + 'trip.tabs.reservations': 'الحجوزات', + 'trip.tabs.reservationsShort': 'حجز', + 'trip.tabs.packing': 'قائمة التجهيز', + 'trip.tabs.packingShort': 'تجهيز', + 'trip.tabs.budget': 'الميزانية', + 'trip.tabs.files': 'الملفات', + 'trip.loading': 'جارٍ تحميل الرحلة...', + 'trip.mobilePlan': 'الخطة', + 'trip.mobilePlaces': 'الأماكن', + 'trip.toast.placeUpdated': 'تم تحديث المكان', + 'trip.toast.placeAdded': 'تمت إضافة المكان', + 'trip.toast.placeDeleted': 'تم حذف المكان', + 'trip.toast.selectDay': 'يرجى اختيار يوم أولًا', + 'trip.toast.assignedToDay': 'تم إسناد المكان إلى اليوم', + 'trip.toast.reorderError': 'فشل إعادة الترتيب', + 'trip.toast.reservationUpdated': 'تم تحديث الحجز', + 'trip.toast.reservationAdded': 'تمت إضافة الحجز', + 'trip.toast.deleted': 'تم الحذف', + 'trip.confirm.deletePlace': 'هل تريد حذف هذا المكان؟', + + 'dayplan.emptyDay': 'لا توجد أماكن مخططة لهذا اليوم', + 'dayplan.addNote': 'إضافة ملاحظة', + 'dayplan.editNote': 'تعديل الملاحظة', + 'dayplan.noteTitle': 'ملاحظة', + 'dayplan.noteSubtitle': 'ملاحظة يومية', + 'dayplan.totalCost': 'إجمالي التكلفة', + 'dayplan.days': 'الأيام', + 'dayplan.dayN': 'اليوم {n}', + 'dayplan.calculating': 'جارٍ الحساب...', + 'dayplan.route': 'المسار', + 'dayplan.optimize': 'تحسين', + 'dayplan.optimized': 'تم تحسين المسار', + 'dayplan.routeError': 'فشل حساب المسار', + 'dayplan.toast.needTwoPlaces': 'يلزم مكانان على الأقل لتحسين المسار', + 'dayplan.toast.routeOptimized': 'تم تحسين المسار', + 'dayplan.toast.noGeoPlaces': 'لم يتم العثور على أماكن بإحداثيات لحساب المسار', + 'dayplan.confirmed': 'مؤكد', + 'dayplan.pendingRes': 'قيد الانتظار', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'تصدير خطة اليوم بصيغة PDF', + 'dayplan.pdfError': 'فشل تصدير PDF', + + 'planner.places': 'الأماكن', + 'planner.bookings': 'الحجوزات', + 'planner.packingList': 'قائمة التجهيز', + 'planner.documents': 'المستندات', + 'planner.dayPlan': 'خطة اليوم', + 'planner.reservations': 'الحجوزات', + 'planner.minTwoPlaces': 'يلزم مكانان على الأقل مع إحداثيات', + 'planner.noGeoPlaces': 'لا توجد أماكن بإحداثيات', + 'planner.routeCalculated': 'تم حساب المسار', + 'planner.routeCalcFailed': 'تعذر حساب المسار', + 'planner.routeError': 'خطأ أثناء حساب المسار', + 'planner.routeOptimized': 'تم تحسين المسار', + 'planner.reservationUpdated': 'تم تحديث الحجز', + 'planner.reservationAdded': 'تمت إضافة الحجز', + 'planner.confirmDeleteReservation': 'حذف الحجز؟', + 'planner.reservationDeleted': 'تم حذف الحجز', + 'planner.days': 'الأيام', + 'planner.allPlaces': 'كل الأماكن', + 'planner.totalPlaces': 'إجمالي {n} أماكن', + 'planner.noDaysPlanned': 'لا توجد أيام مخططة بعد', + 'planner.editTrip': 'تعديل الرحلة ←', + 'planner.placeOne': 'مكان واحد', + 'planner.placeN': '{n} أماكن', + 'planner.addNote': 'إضافة ملاحظة', + 'planner.noEntries': 'لا توجد عناصر لهذا اليوم', + 'planner.addPlace': 'إضافة مكان/نشاط', + 'planner.addPlaceShort': '+ إضافة مكان/نشاط', + 'planner.totalCost': 'إجمالي التكلفة', + 'planner.searchPlaces': 'ابحث عن أماكن…', + 'planner.allCategories': 'كل الفئات', + 'planner.noPlacesFound': 'لم يتم العثور على أماكن', + 'planner.addFirstPlace': 'أضف أول مكان', + 'planner.noReservations': 'لا توجد حجوزات', + 'planner.addFirstReservation': 'أضف أول حجز', + 'planner.new': 'جديد', + 'planner.addToDay': '+ يوم', + 'planner.calculating': 'جارٍ الحساب…', + 'planner.route': 'المسار', + 'planner.optimize': 'تحسين', + 'planner.openGoogleMaps': 'فتح في Google Maps', + 'planner.selectDayHint': 'اختر يومًا من القائمة اليسرى لعرض خطة اليوم', + 'planner.noPlacesForDay': 'لا توجد أماكن لهذا اليوم بعد', + 'planner.addPlacesLink': 'إضافة أماكن ←', + 'planner.noReservation': 'لا يوجد حجز', + 'planner.removeFromDay': 'إزالة من اليوم', + 'planner.addToThisDay': 'إضافة إلى اليوم', + 'planner.overview': 'نظرة عامة', + 'planner.noDays': 'لا توجد أيام بعد', + 'planner.editTripToAddDays': 'عدّل الرحلة لإضافة أيام', + 'planner.dayCount': '{n} أيام', + 'planner.clickToUnlock': 'انقر لفتح القفل', + 'planner.dayDetails': 'تفاصيل اليوم', + 'planner.dayN': 'اليوم {n}', + + // Places + 'places.addPlace': 'إضافة مكان/نشاط', + 'places.assignToDay': 'إلى أي يوم تريد الإضافة؟', + 'places.unplanned': 'غير مخطط', + 'places.search': 'ابحث عن أماكن...', + 'places.allCategories': 'كل الفئات', + 'places.count': '{count} أماكن', + 'places.countSingular': 'مكان واحد', + 'places.allPlanned': 'تم تخطيط جميع الأماكن', + 'places.noneFound': 'لم يتم العثور على أماكن', + 'places.editPlace': 'تعديل المكان', + 'places.formName': 'الاسم', + 'places.formDescription': 'الوصف', + 'places.formAddress': 'العنوان', + 'places.formCategory': 'الفئة', + 'places.noCategory': 'بلا فئة', + 'places.formTime': 'الوقت', + 'places.startTime': 'البداية', + 'places.endTime': 'النهاية', + 'places.endTimeBeforeStart': 'وقت النهاية قبل وقت البداية', + 'places.formWebsite': 'الموقع الإلكتروني', + 'places.formReservation': 'حجز', + 'places.mapsSearchPlaceholder': 'ابحث عن أماكن...', + 'places.mapsSearchError': 'فشل البحث عن المكان.', + 'places.categoryCreateError': 'فشل إنشاء الفئة', + 'places.nameRequired': 'يرجى إدخال اسم', + 'places.saveError': 'فشل الحفظ', + + // Reservations + 'reservations.title': 'الحجوزات', + 'reservations.empty': 'لا توجد حجوزات بعد', + 'reservations.emptyHint': 'أضف حجوزات للرحلات الجوية والفنادق وغير ذلك', + 'reservations.add': 'إضافة حجز', + 'reservations.addManual': 'حجز يدوي', + 'reservations.confirmed': 'مؤكد', + 'reservations.pending': 'قيد الانتظار', + 'reservations.fromPlan': 'من الخطة', + 'reservations.showFiles': 'عرض الملفات', + 'reservations.editTitle': 'تعديل الحجز', + 'reservations.status': 'الحالة', + 'reservations.datetime': 'التاريخ والوقت', + 'reservations.startTime': 'وقت البداية', + 'reservations.endTime': 'وقت النهاية', + 'reservations.notes': 'ملاحظات', + 'reservations.meta.airline': 'شركة الطيران', + 'reservations.meta.flightNumber': 'رقم الرحلة', + 'reservations.meta.from': 'من', + 'reservations.meta.to': 'إلى', + 'reservations.meta.platform': 'المنصة', + 'reservations.meta.seat': 'المقعد', + 'reservations.type.flight': 'رحلة جوية', + 'reservations.type.hotel': 'فندق', + 'reservations.type.restaurant': 'مطعم', + 'reservations.type.train': 'قطار', + 'reservations.type.car': 'سيارة مستأجرة', + 'reservations.type.cruise': 'رحلة بحرية', + 'reservations.type.event': 'فعالية', + 'reservations.type.tour': 'جولة', + 'reservations.type.other': 'أخرى', + 'reservations.confirm.delete': 'هل تريد حذف الحجز "{name}"؟', + 'reservations.toast.updated': 'تم تحديث الحجز', + 'reservations.toast.removed': 'تم حذف الحجز', + 'reservations.toast.fileUploaded': 'تم رفع الملف', + 'reservations.toast.uploadError': 'فشل الرفع', + 'reservations.newTitle': 'حجز جديد', + 'reservations.bookingType': 'نوع الحجز', + 'reservations.titleLabel': 'العنوان', + 'reservations.locationAddress': 'الموقع / العنوان', + 'reservations.confirmationCode': 'رمز الحجز', + 'reservations.day': 'اليوم', + 'reservations.noDay': 'بلا يوم', + 'reservations.place': 'المكان', + 'reservations.noPlace': 'بلا مكان', + 'reservations.uploading': 'جارٍ الرفع...', + 'reservations.attachFile': 'إرفاق ملف', + 'reservations.toast.saveError': 'فشل الحفظ', + 'reservations.toast.updateError': 'فشل التحديث', + 'reservations.toast.deleteError': 'فشل الحذف', + 'reservations.confirm.remove': 'إزالة الحجز "{name}"؟', + 'reservations.linkAssignment': 'ربط بخطة اليوم', + 'reservations.pickAssignment': 'اختر عنصرًا من خطتك...', + 'reservations.noAssignment': 'بلا ربط', + + // Files / photos + 'files.title': 'الملفات', + 'files.count': '{count} ملفات', + 'files.countSingular': 'ملف واحد', + 'files.uploaded': 'تم رفع {count}', + 'files.uploadError': 'فشل الرفع', + 'files.dropzone': 'أسقط الملفات هنا', + 'files.dropzoneHint': 'أو انقر للتصفح', + 'files.uploading': 'جارٍ الرفع...', + 'files.filterAll': 'الكل', + 'files.filterPdf': 'ملفات PDF', + 'files.filterImages': 'الصور', + 'files.filterDocs': 'المستندات', + 'files.filterCollab': 'ملاحظات Collab', + 'files.sourceCollab': 'من ملاحظات Collab', + 'files.empty': 'لا توجد ملفات بعد', + 'files.emptyHint': 'ارفع ملفات لإرفاقها برحلتك', + 'files.openTab': 'فتح في تبويب جديد', + 'files.confirm.delete': 'هل تريد حذف هذا الملف؟', + 'files.toast.deleted': 'تم حذف الملف', + 'files.toast.deleteError': 'فشل حذف الملف', + 'files.sourcePlan': 'خطة اليوم', + 'files.sourceBooking': 'الحجز', + 'files.attach': 'إرفاق', + 'files.trash': 'سلة المهملات', + 'files.trashEmpty': 'سلة المهملات فارغة', + 'files.emptyTrash': 'إفراغ السلة', + 'files.restore': 'استعادة', + 'files.assign': 'إسناد', + 'files.assignTitle': 'إسناد ملف', + 'files.assignPlace': 'المكان', + 'files.assignBooking': 'الحجز', + 'files.unassigned': 'غير مسند', + 'files.unlink': 'إزالة الرابط', + + 'photos.allDays': 'كل الأيام', + 'photos.noPhotos': 'لا توجد صور بعد', + 'photos.uploadHint': 'ارفع صور رحلتك', + 'photos.clickToSelect': 'أو انقر للاختيار', + 'photos.linkPlace': 'ربط بمكان', + 'photos.noPlace': 'بلا مكان', + 'photos.uploadN': 'رفع {n} صورة', + + // Vacay + 'vacay.subtitle': 'خطط وأدر أيام الإجازة', + 'vacay.settings': 'الإعدادات', + 'vacay.year': 'السنة', + 'vacay.addYear': 'إضافة سنة', + 'vacay.removeYear': 'إزالة السنة', + 'vacay.removeYearConfirm': 'إزالة {year}؟', + 'vacay.removeYearHint': 'سيتم حذف كل إدخالات الإجازات والعطل الخاصة بهذه السنة نهائيًا.', + 'vacay.remove': 'إزالة', + 'vacay.persons': 'الأشخاص', + 'vacay.noPersons': 'لم تتم إضافة أشخاص بعد', + 'vacay.addPerson': 'إضافة شخص', + 'vacay.editPerson': 'تعديل الشخص', + 'vacay.removePerson': 'إزالة الشخص', + 'vacay.removePersonConfirm': 'إزالة {name}؟', + 'vacay.personName': 'الاسم', + 'vacay.personNamePlaceholder': 'أدخل الاسم', + 'vacay.color': 'اللون', + 'vacay.add': 'إضافة', + 'vacay.legend': 'المفتاح', + 'vacay.publicHoliday': 'عطلة رسمية', + 'vacay.companyHoliday': 'عطلة شركة', + 'vacay.weekend': 'نهاية الأسبوع', + 'vacay.modeVacation': 'إجازة', + 'vacay.modeCompany': 'عطلة شركة', + 'vacay.entitlement': 'الاستحقاق', + 'vacay.entitlementDays': 'الأيام', + 'vacay.used': 'المستخدم', + 'vacay.remaining': 'المتبقي', + 'vacay.carriedOver': 'من {year}', + 'vacay.blockWeekends': 'حظر عطلة نهاية الأسبوع', + 'vacay.blockWeekendsHint': 'منع إدخالات الإجازة يومي السبت والأحد', + 'vacay.publicHolidays': 'العطل الرسمية', + 'vacay.publicHolidaysHint': 'وضع علامة على العطل الرسمية في التقويم', + 'vacay.selectCountry': 'اختر الدولة', + 'vacay.selectRegion': 'اختر المنطقة (اختياري)', + 'vacay.addCalendar': 'إضافة تقويم', + 'vacay.calendarLabel': 'الاسم (اختياري)', + 'vacay.calendarColor': 'لون التقويم', + 'vacay.noCalendars': 'لم تتم إضافة تقاويم عطلات بعد', + 'vacay.companyHolidays': 'عطل الشركة', + 'vacay.companyHolidaysHint': 'السماح بوضع علامة على أيام عطلات الشركة', + 'vacay.companyHolidaysNoDeduct': 'لا تُخصم عطل الشركة من أيام الإجازة.', + 'vacay.carryOver': 'الترحيل', + 'vacay.carryOverHint': 'ترحيل أيام الإجازة المتبقية تلقائيًا إلى السنة التالية', + 'vacay.sharing': 'المشاركة', + 'vacay.sharingHint': 'شارك خطة إجازاتك مع مستخدمي TREK الآخرين', + 'vacay.owner': 'المالك', + 'vacay.shareEmailPlaceholder': 'البريد الإلكتروني لمستخدم TREK', + 'vacay.shareSuccess': 'تمت مشاركة الخطة بنجاح', + 'vacay.shareError': 'تعذرت مشاركة الخطة', + 'vacay.dissolve': 'فك الدمج', + 'vacay.dissolveHint': 'افصل التقويمات مرة أخرى. سيتم الاحتفاظ بإدخالاتك.', + 'vacay.dissolveAction': 'فك', + 'vacay.dissolved': 'تم فصل التقويم', + 'vacay.fusedWith': 'مُدمج مع', + 'vacay.you': 'أنت', + 'vacay.noData': 'لا توجد بيانات', + 'vacay.changeColor': 'تغيير اللون', + 'vacay.inviteUser': 'دعوة مستخدم', + 'vacay.inviteHint': 'ادعُ مستخدم TREK آخرًا لمشاركة تقويم إجازة مشترك.', + 'vacay.selectUser': 'اختر مستخدمًا', + 'vacay.sendInvite': 'إرسال الدعوة', + 'vacay.inviteSent': 'تم إرسال الدعوة', + 'vacay.inviteError': 'تعذر إرسال الدعوة', + 'vacay.pending': 'قيد الانتظار', + 'vacay.noUsersAvailable': 'لا يوجد مستخدمون متاحون', + 'vacay.accept': 'قبول', + 'vacay.decline': 'رفض', + 'vacay.acceptFusion': 'قبول ودمج', + 'vacay.inviteTitle': 'طلب دمج', + 'vacay.inviteWantsToFuse': 'يريد مشاركة تقويم إجازة معك.', + + // Atlas + 'atlas.subtitle': 'بصمتك السفرية حول العالم', + 'atlas.countries': 'الدول', + 'atlas.trips': 'الرحلات', + 'atlas.places': 'الأماكن', + 'atlas.days': 'الأيام', + 'atlas.visitedCountries': 'الدول التي تمت زيارتها', + 'atlas.cities': 'المدن', + 'atlas.noData': 'لا توجد بيانات سفر بعد', + 'atlas.noDataHint': 'أنشئ رحلة وأضف أماكن لرؤية خريطتك العالمية', + 'atlas.lastTrip': 'آخر رحلة', + 'atlas.nextTrip': 'الرحلة القادمة', + 'atlas.daysLeft': 'يوم متبقٍ', + 'atlas.streak': 'سلسلة', + 'atlas.year': 'سنة', + 'atlas.years': 'سنوات', + 'atlas.tripSingular': 'رحلة', + 'atlas.tripPlural': 'رحلات', + 'atlas.placeVisited': 'مكان تمت زيارته', + 'atlas.placesVisited': 'أماكن تمت زيارتها', + 'atlas.firstVisit': 'أول رحلة', + 'atlas.lastVisitLabel': 'آخر رحلة', + + // Collab + 'collab.tabs.chat': 'الدردشة', + 'collab.tabs.notes': 'الملاحظات', + 'collab.tabs.polls': 'الاستطلاعات', + 'collab.whatsNext.title': 'ما التالي', + 'collab.whatsNext.today': 'اليوم', + 'collab.whatsNext.tomorrow': 'غدًا', + 'collab.whatsNext.empty': 'لا توجد أنشطة قادمة', + 'collab.whatsNext.until': 'إلى', + 'collab.whatsNext.emptyHint': 'ستظهر الأنشطة التي لها وقت هنا', + 'collab.chat.send': 'إرسال', + 'collab.chat.placeholder': 'اكتب رسالة...', + 'collab.chat.empty': 'ابدأ المحادثة', + 'collab.chat.emptyHint': 'تتم مشاركة الرسائل مع جميع أعضاء الرحلة', + 'collab.chat.emptyDesc': 'شارك الأفكار والخطط والتحديثات مع مجموعة السفر', + 'collab.chat.today': 'اليوم', + 'collab.chat.yesterday': 'أمس', + 'collab.chat.deletedMessage': 'حذف رسالة', + 'collab.chat.loadMore': 'تحميل الرسائل الأقدم', + 'collab.chat.justNow': 'الآن', + 'collab.chat.minutesAgo': 'منذ {n} د', + 'collab.chat.hoursAgo': 'منذ {n} س', + 'collab.notes.title': 'الملاحظات', + 'collab.notes.new': 'ملاحظة جديدة', + 'collab.notes.empty': 'لا توجد ملاحظات بعد', + 'collab.notes.emptyHint': 'ابدأ بتسجيل الأفكار والخطط', + 'collab.notes.all': 'الكل', + 'collab.notes.titlePlaceholder': 'عنوان الملاحظة', + 'collab.notes.contentPlaceholder': 'اكتب شيئًا...', + 'collab.notes.categoryPlaceholder': 'الفئة', + 'collab.notes.newCategory': 'فئة جديدة...', + 'collab.notes.category': 'الفئة', + 'collab.notes.noCategory': 'بلا فئة', + 'collab.notes.color': 'اللون', + 'collab.notes.save': 'حفظ', + 'collab.notes.cancel': 'إلغاء', + 'collab.notes.edit': 'تعديل', + 'collab.notes.delete': 'حذف', + 'collab.notes.pin': 'تثبيت', + 'collab.notes.unpin': 'إلغاء التثبيت', + 'collab.notes.daysAgo': 'منذ {n} يوم', + 'collab.notes.categorySettings': 'إدارة الفئات', + 'collab.notes.create': 'إنشاء', + 'collab.notes.website': 'الموقع الإلكتروني', + 'collab.notes.attachFiles': 'إرفاق ملفات', + 'collab.notes.noCategoriesYet': 'لا توجد فئات بعد', + 'collab.notes.emptyDesc': 'أنشئ ملاحظة للبدء', + 'collab.polls.title': 'الاستطلاعات', + 'collab.polls.new': 'استطلاع جديد', + 'collab.polls.empty': 'لا توجد استطلاعات بعد', + 'collab.polls.emptyHint': 'اسأل المجموعة وصوّتوا معًا', + 'collab.polls.question': 'السؤال', + 'collab.polls.questionPlaceholder': 'ماذا ينبغي أن نفعل؟', + 'collab.polls.addOption': '+ إضافة خيار', + 'collab.polls.optionPlaceholder': 'الخيار {n}', + 'collab.polls.create': 'إنشاء استطلاع', + 'collab.polls.close': 'إغلاق', + 'collab.polls.closed': 'مغلق', + 'collab.polls.votes': '{n} أصوات', + 'collab.polls.vote': '{n} صوت', + 'collab.polls.multipleChoice': 'اختيار متعدد', + 'collab.polls.multiChoice': 'اختيار متعدد', + 'collab.polls.deadline': 'الموعد النهائي', + 'collab.polls.option': 'خيار', + 'collab.polls.options': 'الخيارات', + 'collab.polls.delete': 'حذف', + 'collab.polls.closedSection': 'مغلق', +} + +export default ar diff --git a/client/src/i18n/translations/de.ts b/client/src/i18n/translations/de.ts index b0268ef..e4138a7 100644 --- a/client/src/i18n/translations/de.ts +++ b/client/src/i18n/translations/de.ts @@ -355,6 +355,7 @@ const de: Record = { 'admin.github.loading': 'Wird geladen...', 'admin.github.error': 'Releases konnten nicht geladen werden', 'admin.github.by': 'von', + 'admin.github.support': 'Hilft mir, TREK weiterzuentwickeln', 'admin.update.available': 'Update verfügbar', 'admin.update.text': 'TREK {version} ist verfügbar. Du verwendest {current}.', diff --git a/client/src/i18n/translations/en.ts b/client/src/i18n/translations/en.ts index 48fb3c4..d11a63f 100644 --- a/client/src/i18n/translations/en.ts +++ b/client/src/i18n/translations/en.ts @@ -355,6 +355,7 @@ const en: Record = { 'admin.github.loading': 'Loading...', 'admin.github.error': 'Failed to load releases', 'admin.github.by': 'by', + 'admin.github.support': 'Helps me keep building TREK', 'admin.update.available': 'Update available', 'admin.update.text': 'TREK {version} is available. You are running {current}.', diff --git a/client/src/pages/LoginPage.tsx b/client/src/pages/LoginPage.tsx index 2b4cc4e..ba8c1e0 100644 --- a/client/src/pages/LoginPage.tsx +++ b/client/src/pages/LoginPage.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { useAuthStore } from '../store/authStore' import { useSettingsStore } from '../store/settingsStore' -import { useTranslation } from '../i18n' +import { SUPPORTED_LANGUAGES, useTranslation } from '../i18n' import { authApi } from '../api/client' import { Plane, Eye, EyeOff, Mail, Lock, MapPin, Calendar, Package, User, Globe, Zap, Users, Wallet, Map, CheckSquare, BookMarked, FolderOpen, Route, Shield } from 'lucide-react' @@ -273,8 +273,8 @@ export default function LoginPage(): React.ReactElement { {/* Language toggle */}