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)