Merge pull request #76 from mansourSaleh/add-arabic-language-support

feat(client): add Arabic language support
This commit is contained in:
mauriceboe
2026-03-29 12:56:45 +02:00
committed by GitHub
10 changed files with 782 additions and 22 deletions

View File

@@ -130,7 +130,7 @@ export default function GitHubPanel() {
</div>
<div>
<div className="text-sm font-semibold" style={{ color: 'var(--text-primary)' }}>Ko-fi</div>
<div className="text-xs" style={{ color: 'var(--text-faint)' }}>{language === 'de' ? 'Hilft mir, TREK weiterzuentwickeln' : 'Helps me keep building TREK'}</div>
<div className="text-xs" style={{ color: 'var(--text-faint)' }}>{t('admin.github.support')}</div>
</div>
<ExternalLink size={14} className="ml-auto flex-shrink-0" style={{ color: 'var(--text-faint)' }} />
</a>
@@ -148,7 +148,7 @@ export default function GitHubPanel() {
</div>
<div>
<div className="text-sm font-semibold" style={{ color: 'var(--text-primary)' }}>Buy Me a Coffee</div>
<div className="text-xs" style={{ color: 'var(--text-faint)' }}>{language === 'de' ? 'Hilft mir, TREK weiterzuentwickeln' : 'Helps me keep building TREK'}</div>
<div className="text-xs" style={{ color: 'var(--text-faint)' }}>{t('admin.github.support')}</div>
</div>
<ExternalLink size={14} className="ml-auto flex-shrink-0" style={{ color: 'var(--text-faint)' }} />
</a>

View File

@@ -118,6 +118,38 @@ const texts: Record<string, DemoTexts> = {
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]

View File

@@ -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)

View File

@@ -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<string, string | { name: string; category: string }[]>
const translations: Record<string, TranslationStrings> = { de, en, es, fr, ru, zh, nl }
const LOCALES: Record<string, string> = { 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<string, TranslationStrings> = { de, en, es, fr, ru, zh, nl, ar }
const LOCALES: Record<string, string> = { 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

View File

@@ -1 +1,8 @@
export { TranslationProvider, useTranslation, getLocaleForLanguage, getIntlLanguage } from './TranslationContext'
export {
TranslationProvider,
useTranslation,
getLocaleForLanguage,
getIntlLanguage,
isRtlLanguage,
SUPPORTED_LANGUAGES,
} from './TranslationContext'

View File

@@ -0,0 +1,703 @@
import en from './en'
const ar: Record<string, string | { name: string; category: string }[]> = {
...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

View File

@@ -375,6 +375,7 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'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}.',

View File

@@ -375,6 +375,7 @@ const en: Record<string, string | { name: string; category: string }[]> = {
'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}.',

View File

@@ -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'
@@ -290,8 +290,8 @@ export default function LoginPage(): React.ReactElement {
{/* Language toggle */}
<button
onClick={() => {
const languages = ['en', 'es', 'de']
const currentIndex = languages.indexOf(language)
const languages = SUPPORTED_LANGUAGES.map(({ value }) => value)
const currentIndex = languages.findIndex(code => code === language)
const nextLanguage = languages[(currentIndex + 1) % languages.length]
setLanguageLocal(nextLanguage)
}}

View File

@@ -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 Navbar from '../components/Layout/Navbar'
import CustomSelect from '../components/shared/CustomSelect'
import { useToast } from '../components/shared/Toast'
@@ -266,15 +266,7 @@ export default function SettingsPage(): React.ReactElement {
<div>
<label className="block text-sm font-medium mb-2" style={{ color: 'var(--text-secondary)' }}>{t('settings.language')}</label>
<div className="flex flex-wrap gap-2">
{[
{ 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: '中文' },
].map(opt => (
{SUPPORTED_LANGUAGES.map(opt => (
<button
key={opt.value}
onClick={async () => {