arblant
FoxyVPN-MultiAcc / app / src / main / java / com / vauth / foxyvpn / ui / l10n / L10n.kt
L10n.kt238 lines · 13.8 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
package com.vauth.foxyvpn.ui.l10n

import androidx.compose.runtime.Composable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalConfiguration
import com.vauth.foxyvpn.ui.theme.AppLanguage

/**
 * Maintainable UI localization. English is the source of truth; Persian
 * translations live in [FA]. Untranslated keys fall back to English.
 * Emails, server names, IPs and numeric values are never passed through
 * this map — callers keep them as raw data.
 */
object L10n {
    private val FA: Map<String, String> = mapOf(
        "  Sign out" to "  خروج",
        "AMOLED black" to "مشکی AMOLED",
        "About" to "درباره",
        "Accent color" to "رنگ تأکید",
        "Account" to "حساب",
        "Accounts" to "حساب‌ها",
        "Add account" to "افزودن حساب",
        "Advanced" to "پیشرفته",
        "All" to "همه",
        "All traffic is protected" to "همه ترافیک محافظت می‌شود",
        "Apply" to "اعمال",
        "Ask before deleting an account" to "قبل از حذف حساب بپرس",
        "Automatically chosen for the best performance" to "به‌صورت خودکار برای بهترین عملکرد انتخاب می‌شود",
        "Back" to "بازگشت",
        "Back home after selecting" to "بازگشت به خانه پس از انتخاب",
        "Batch import" to "ورود دسته‌ای",
        "Batch import accounts" to "ورود دسته‌ای حساب‌ها",
        "Behaviour" to "رفتار",
        "Best performance" to "بهترین عملکرد",
        "Block ads" to "مسدودسازی تبلیغات",
        "Cancel" to "لغو",
        "Card corners" to "گوشه‌های کارت",
        "Chain through upstream proxy" to "زنجیره از طریق پروکسی بالادستی",
        "Check that the tunnel exits in the selected country" to "بررسی کنید تونل در کشور انتخاب‌شده خارج شود",
        "Choose location" to "انتخاب مکان",
        "Clear logs" to "پاک کردن گزارش‌ها",
        "Close" to "بستن",
        "Color style" to "سبک رنگ",
        "Colors" to "رنگ‌ها",
        "Comfortable" to "راحت",
        "Compact" to "فشرده",
        "Confirm before removing" to "قبل از حذف تأیید کن",
        "Confirmation code" to "کد تأیید",
        "Connect" to "اتصال",
        "Connect button style" to "سبک دکمه اتصال",
        "Connect to the VPN server through another proxy first" to "ابتدا از طریق پروکسی دیگری به سرور VPN متصل شوید",
        "Connected" to "متصل",
        "Connecting…" to "در حال اتصال…",
        "Connection" to "اتصال",
        "Connection details" to "جزئیات اتصال",
        "Continue" to "ادامه",
        "Contrast" to "کنتراست",
        "Come back here and add the account. Repeat for more accounts." to "برگرد اینجا و حساب رو اضافه کن. برای حساب‌های بیشتر همین رو تکرار کن.",
        "Copy logs" to "کپی گزارش‌ها",
        "Create Firefox account" to "ساخت حساب فایرفاکس",
        "Create a Firefox account with a new dotted variation of your Gmail address." to "با یه مدل نقطه‌دار جدید از آدرس جیمیلت یه حساب فایرفاکس بساز.",
        "Created by" to "ساخته‌شده توسط",
        "Curtain" to "پرده",
        "Custom" to "سفارشی",
        "Custom DoH (Not configured)" to "DoH سفارشی (پیکربندی نشده)",
        "Custom IP (Not configured)" to "IP سفارشی (پیکربندی نشده)",
        "Custom edge address" to "آدرس لبه سفارشی",
        "DNS" to "DNS",
        "DNS used inside the VPN tunnel for all app and browsing traffic." to "DNS داخل تونل VPN برای همه ترافیک برنامه و مرور.",
        "DNS used to resolve the VPN server address before the tunnel connects." to "DNS برای حل آدرس سرور VPN قبل از اتصال تونل.",
        "Dark" to "تاریک",
        "Default" to "پیش‌فرض",
        "Depth" to "عمق",
        "Disconnect" to "قطع اتصال",
        "Disconnected" to "قطع‌شده",
        "Display" to "نمایش",
        "Dot method" to "روش دات",
        "Download" to "دانلود",
        "Dynamic color" to "رنگ پویا",
        "Elastic" to "کشسان",
        "Email" to "ایمیل",
        "Enable HTTP proxy" to "فعال‌سازی پروکسی HTTP",
        "English" to "English",
        "Enter the confirmation code sent to your email" to "کد تأییدی که به ایمیلتان ارسال شده را وارد کنید",
        "Excluded apps" to "برنامه‌های مستثنی",
        "Example" to "مثال",
        "Exit check" to "بررسی خروجی",
        "Expressive" to "بیانی",
        "Fade" to "محو",
        "Failed to load server list" to "بارگذاری فهرست سرورها ناموفق بود",
        "Fidelity" to "وفاداری",
        "Firefox account email" to "ایمیل حساب فایرفاکس",
        "Gmail ignores the dots in your address: a.b@gmail.com and ab@gmail.com are the same inbox." to "جیمیل نقطه‌های آدرست رو نادیده می‌گیره: a.b@gmail.com و ab@gmail.com یه اینباکس‌ان.",
        "Flip" to "چرخش",
        "Follow your wallpaper colors (Android 12+)" to "دنبال کردن رنگ‌های کاغذدیواری (اندروید ۱۲+)",
        "Font size" to "اندازه قلم",
        "For a Firefox signup, each dotted variation counts as a brand-new email address — but every verification email arrives in that same one inbox." to "برای ثبت‌نام فایرفاکس، هر مدل نقطه‌دار یه آدرس ایمیل کاملاً جدید حساب می‌شه — ولی همه‌ی ایمیل‌های تأیید می‌رسن به همون یه اینباکس.",
        "FoxyVPN logs" to "گزارش‌های FoxyVPN",
        "Fruit Salad" to "سالاد میوه",
        "HTTP port" to "پورت HTTP",
        "Haptic feedback" to "بازخورد لمسی",
        "High" to "بالا",
        "Home" to "خانه",
        "Home screen design" to "طراحی صفحه اصلی",
        "Host or IP" to "میزبان یا IP",
        "Idle" to "بیکار",
        "Import" to "ورود",
        "Importing…" to "در حال ورود…",
        "Info" to "اطلاعات",
        "Interface" to "رابط کاربری",
        "Language" to "زبان",
        "Larger" to "بزرگ‌تر",
        "Largest" to "بزرگ‌ترین",
        "Leave empty to resolve the server hostname normally." to "خالی بگذارید تا نام میزبان سرور به‌طور عادی حل شود.",
        "Light" to "روشن",
        "List density" to "تراکم فهرست",
        "Live" to "زنده",
        "Local address" to "آدرس محلی",
        "Local port" to "پورت محلی",
        "Local proxy" to "پروکسی محلی",
        "Location" to "مکان",
        "Locations" to "مکان‌ها",
        "Logs" to "گزارش‌ها",
        "Manage accounts" to "مدیریت حساب‌ها",
        "Measure latency next to each location" to "اندازه‌گیری تأخیر کنار هر مکان",
        "Medium" to "متوسط",
        "Minimize animations across the app" to "کاهش انیمیشن‌ها در سراسر برنامه",
        "Monochrome" to "تک‌رنگ",
        "Monospace addresses" to "آدرس‌های monospace",
        "Multi-account list, quotas and batch import" to "فهرست چندحسابی، سهمیه‌ها و ورود دسته‌ای",
        "Navigation bar style" to "سبک نوار ناوبری",
        "Neutral" to "خنثی",
        "No accounts yet" to "هنوز حسابی نیست",
        "No accounts yet — add some from the Accounts screen." to "هنوز حسابی نیست — از صفحه حساب‌ها چند تا اضافه کنید.",
        "No log entries yet" to "هنوز ورودی گزارشی نیست",
        "No log entries yet." to "هنوز ورودی گزارشی نیست.",
        "No servers" to "سروری نیست",
        "No servers available" to "سروری در دسترس نیست",
        "No warnings or errors. Nothing has gone wrong." to "هیچ هشدار یا خطایی نیست. مشکلی پیش نیامده.",
        "None" to "هیچ‌کدام",
        "Normal" to "عادی",
        "Not connected" to "متصل نیست",
        "Not set" to "تنظیم نشده",
        "Nothing matches this filter." to "چیزی با این فیلتر مطابقت ندارد.",
        "Old" to "قدیمی",
        "One Gmail, many accounts" to "یک جیمیل، چند حساب",
        "One tap applies a curated look. Picking any color above returns to custom." to "با یک ضربه ظاهری انتخاب‌شده اعمال می‌شود. انتخاب هر رنگ بالا به حالت سفارشی برمی‌گردد.",
        "Open the verification email in your one inbox and confirm it, or note the code." to "ایمیل تأیید رو تو همون اینباکس باز کن و تأییدش کن، یا کدش رو یادداشت کن.",
        "Password" to "رمز عبور",
        "Password (optional)" to "رمز عبور (اختیاری)",
        "Personalize" to "شخصی‌سازی",
        "Ping" to "پینگ",
        "Pixel" to "پیکسل",
        "Portal" to "پورتال",
        "Problems" to "مشکلات",
        "Proxy address" to "آدرس پروکسی",
        "Proxy credentials" to "اعتبارنامه پروکسی",
        "Proxy type" to "نوع پروکسی",
        "Proxy-only mode" to "حالت فقط-پروکسی",
        "Pure black background to save battery on OLED screens" to "پس‌زمینه مشکی خالص برای صرفه‌جویی باتری در صفحه‌های OLED",
        "Push" to "فشار",
        "Quota exhausted" to "سهمیه تمام شده",
        "Quota refresh failed" to "تازه‌سازی سهمیه ناموفق بود",
        "Rainbow" to "رنگین‌کمان",
        "Recent connection activity" to "فعالیت اخیر اتصال",
        "Recommended" to "پیشنهادی",
        "Reduce motion" to "کاهش حرکت",
        "Refresh ping" to "تازه‌سازی پینگ",
        "Resend code" to "ارسال دوباره کد",
        "Refresh quotas" to "تازه‌سازی سهمیه‌ها",
        "Remove" to "حذف",
        "Remove account?" to "حذف حساب؟",
        "Reset all personalization" to "بازنشانی همه شخصی‌سازی‌ها",
        "Resolver DNS" to "DNS حل‌کننده",
        "Resolver IP address" to "آدرس IP حل‌کننده",
        "Save" to "ذخیره",
        "Screen transition" to "گذار صفحه",
        "Search apps" to "جستجوی برنامه‌ها",
        "Selected apps bypass the VPN and use your normal connection." to "برنامه‌های انتخاب‌شده از VPN عبور می‌کنند و از اتصال عادی شما استفاده می‌کنند.",
        "Settings" to "تنظیمات",
        "Show usage next to each location" to "نمایش مصرف کنار هر مکان",
        "Sign in" to "ورود",
        "Sign in with Firefox" to "ورود با فایرفاکس",
        "Sign out" to "خروج",
        "Sign out?" to "خروج؟",
        "Signature" to "امضا",
        "Skip" to "رد کردن",
        "Slide" to "اسلاید",
        "Smaller" to "کوچک‌تر",
        "Smallest" to "کوچک‌ترین",
        "Split tunneling" to "تونل‌زنی تفکیکی",
        "Standard" to "استاندارد",
        "Start over" to "شروع از اول",
        "System" to "سیستم",
        "Tap the core to cancel" to "برای لغو روی هسته ضربه بزنید",
        "Tap the core to connect" to "برای اتصال روی هسته ضربه بزنید",
        "Theme" to "پوسته",
        "Theme profiles" to "پروفایل‌های پوسته",
        "This removes every saved account from this device. You can sign in again later." to "این کار همه حساب‌های ذخیره‌شده را از این دستگاه حذف می‌کند. بعداً می‌توانید دوباره وارد شوید.",
        "Tonal Spot" to "نقطه تونال",
        "Tunnel DNS" to "DNS تونل",
        "Upload" to "آپلود",
        "Username (optional)" to "نام کاربری (اختیاری)",
        "Verify" to "تأیید",
        "Vibrant" to "زنده",
        "Vibrate on connect control and theme changes" to "لرزش هنگام کنترل اتصال و تغییر پوسته",
    )

    fun t(key: String, lang: ResolvedLang): String {
        if (lang != ResolvedLang.Persian) return key
        return FA[key] ?: key
    }
}

enum class ResolvedLang { English, Persian }

val LocalL10n = compositionLocalOf { ResolvedLang.English }

@Composable
fun rememberResolvedLang(appLanguage: AppLanguage): ResolvedLang {
    val config = LocalConfiguration.current
    return remember(appLanguage, config.locales[0]?.language) {
        when (appLanguage) {
            AppLanguage.English -> ResolvedLang.English
            AppLanguage.Persian -> ResolvedLang.Persian
            AppLanguage.System -> {
                val tag = config.locales[0]?.language ?: "en"
                if (tag.startsWith("fa")) ResolvedLang.Persian else ResolvedLang.English
            }
        }
    }
}

/** Translate [key] using the ambient language. */
@Composable
fun s(key: String): String = L10n.t(key, LocalL10n.current)