From 6d5d4d36c105829eb0b721f35206163d32a82ac1 Mon Sep 17 00:00:00 2001 From: codytseng Date: Sun, 20 Jul 2025 21:47:10 +0800 Subject: [PATCH] feat: add Persian language support --- src/i18n/index.ts | 4 + src/i18n/locales/fa.ts | 300 +++++++++++++++++++++++++++++++++++++++++ src/lib/utils.ts | 5 + 3 files changed, 309 insertions(+) create mode 100644 src/i18n/locales/fa.ts diff --git a/src/i18n/index.ts b/src/i18n/index.ts index d4fb62b4..ed0d0509 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -6,6 +6,7 @@ import ar from './locales/ar' import de from './locales/de' import en from './locales/en' import es from './locales/es' +import fa from './locales/fa' import fr from './locales/fr' import it from './locales/it' import ja from './locales/ja' @@ -22,6 +23,7 @@ const languages = { de: { resource: de, name: 'Deutsch' }, en: { resource: en, name: 'English' }, es: { resource: es, name: 'Español' }, + fa: { resource: fa, name: 'فارسی' }, fr: { resource: fr, name: 'Français' }, it: { resource: it, name: 'Italiano' }, ja: { resource: ja, name: '日本語' }, @@ -71,6 +73,8 @@ i18n.services.formatter?.add('date', (timestamp, lng) => { case 'de': case 'ru': return dayjs(timestamp).format('DD.MM.YYYY') + case 'fa': + return dayjs(timestamp).format('YYYY/MM/DD') case 'it': case 'es': case 'fr': diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts new file mode 100644 index 00000000..0541ae33 --- /dev/null +++ b/src/i18n/locales/fa.ts @@ -0,0 +1,300 @@ +export default { + translation: { + 'Welcome! 🥳': 'خوش آمدید! 🥳', + About: 'درباره', + 'New Note': 'یادداشت جدید', + Post: 'ارسال', + Home: 'خانه', + 'Relay settings': 'تنظیمات رله', + Settings: 'تنظیمات', + SidebarRelays: 'رله‌ها', + Refresh: 'بازخوانی', + Profile: 'پروفایل', + Logout: 'خروج', + Following: 'دنبال می‌کنم', + followings: 'دنبال شونده‌ها', + reposted: 'بازنشر شده', + 'just now': 'همین الان', + 'n minutes ago': '{{n}} دقیقه پیش', + 'n m': '{{n}}د', + 'n hours ago': '{{n}} ساعت پیش', + 'n h': '{{n}}س', + 'n days ago': '{{n}} روز پیش', + 'n d': '{{n}}ر', + date: '{{timestamp, date}}', + Follow: 'دنبال کردن', + Unfollow: 'لغو دنبال کردن', + 'Follow failed': 'دنبال کردن ناموفق', + 'Unfollow failed': 'لغو دنبال کردن ناموفق', + 'show new notes': 'نمایش یادداشت‌های جدید', + 'loading...': 'در حال بارگذاری...', + 'no more notes': 'یادداشت بیشتری وجود ندارد', + 'reply to': 'پاسخ به', + reply: 'پاسخ', + Reply: 'پاسخ', + 'load more older replies': 'بارگذاری پاسخ‌های قدیمی‌تر', + 'Write something...': 'چیزی بنویسید...', + Cancel: 'لغو', + Mentions: 'اشاره‌ها', + 'Failed to post': 'ارسال ناموفق', + 'Post successful': 'ارسال موفق', + 'Your post has been published': 'پست شما منتشر شد', + Repost: 'بازنشر', + Quote: 'نقل قول', + 'Copy event ID': 'کپی شناسه رویداد', + 'Copy user ID': 'کپی شناسه کاربر', + 'View raw event': 'نمایش رویداد خام', + Like: 'پسند', + 'switch to light theme': 'تغییر به تم روشن', + 'switch to dark theme': 'تغییر به تم تاریک', + 'switch to system theme': 'تغییر به تم سیستم', + Note: 'یادداشت', + note: 'یادداشت', + "username's following": 'دنبال شونده‌های {{username}}', + "username's used relays": 'رله‌های استفاده شده {{username}}', + "username's muted": 'بی‌صدا شده‌های {{username}}', + Login: 'ورود', + 'Follows you': 'شما را دنبال می‌کند', + 'Relay Settings': 'تنظیمات رله', + 'Relay set name': 'نام مجموعه رله', + 'Add a new relay set': 'افزودن مجموعه رله جدید', + Add: 'افزودن', + 'n relays': '{{n}} رله', + Rename: 'تغییر نام', + 'Copy share link': 'کپی لینک اشتراک', + Delete: 'حذف', + 'Relay already exists': 'رله از قبل موجود است', + 'invalid relay URL': 'آدرس رله نامعتبر', + 'Add a new relay': 'افزودن رله جدید', + back: 'بازگشت', + 'Lost in the void': 'گم شده در خلاء', + 'Carry me home': 'مرا به خانه ببر', + 'no replies': 'پاسخی وجود ندارد', + 'Reply to': 'پاسخ به', + Search: 'جستجو', + 'The relays you are connected to do not support search': + 'رله‌هایی که متصل هستید از جستجو پشتیبانی نمی‌کنند', + 'Show more...': 'نمایش بیشتر...', + 'All users': 'همه کاربران', + 'Display replies': 'نمایش پاسخ‌ها', + Notes: 'یادداشت‌ها', + Replies: 'پاسخ‌ها', + Notifications: 'اعلان‌ها', + 'no more notifications': 'اعلان بیشتری وجود ندارد', + 'Using private key login is insecure. It is recommended to use a browser extension for login, such as alby, nostr-keyx or nos2x. If you must use a private key, please set a password for encryption at minimum.': + 'استفاده از کلید خصوصی برای ورود ناامن است. توصیه می‌شود از افزونه مرورگر برای ورود استفاده کنید، مانند alby، nostr-keyx یا nos2x. اگر مجبور به استفاده از کلید خصوصی هستید، حداقل یک رمز عبور برای رمزگذاری تنظیم کنید.', + 'Login with Browser Extension': 'ورود با افزونه مرورگر', + 'Login with Bunker': 'ورود با Bunker', + 'Login with Private Key': 'ورود با کلید خصوصی', + 'reload notes': 'بازخوانی یادداشت‌ها', + 'Logged in Accounts': 'حساب‌های وارد شده', + 'Add an Account': 'افزودن حساب', + 'More options': 'گزینه‌های بیشتر', + 'Add client tag': 'افزودن برچسب کلاینت', + 'Show others this was sent via Jumble': 'به دیگران نشان دهید که از طریق Jumble ارسال شده', + 'Are you sure you want to logout?': 'آیا مطمئن هستید که می‌خواهید خارج شوید؟', + 'relay sets': 'مجموعه‌های رله', + edit: 'ویرایش', + Languages: 'زبان‌ها', + Theme: 'تم', + System: 'سیستم', + Light: 'روشن', + Dark: 'تاریک', + Temporary: 'موقت', + 'Choose a relay set': 'یک مجموعه رله انتخاب کنید', + 'Switch account': 'تغییر حساب', + Pictures: 'تصاویر', + 'Picture note': 'یادداشت تصویری', + 'A special note for picture-first clients like Olas': + 'یادداشت ویژه برای کلاینت‌های تصویر محور مانند Olas', + 'Picture note requires images': 'یادداشت تصویری نیاز به تصاویر دارد', + Relays: 'رله‌ها', + image: 'تصویر', + 'R & W': 'خواندن و نوشتن', + Read: 'خواندن', + Write: 'نوشتن', + 'Pull relay sets': 'کشیدن مجموعه‌های رله', + 'Select the relay sets you want to pull': 'مجموعه‌های رله‌ای که می‌خواهید بکشید انتخاب کنید', + 'No relay sets found': 'مجموعه رله‌ای یافت نشد', + 'Pull n relay sets': 'کشیدن {{n}} مجموعه رله', + Pull: 'کشیدن', + 'Select all': 'انتخاب همه', + 'Relay Sets': 'مجموعه‌های رله', + 'Read & Write Relays': 'رله‌های خواندن و نوشتن', + 'read relays description': + 'رله‌های خواندن برای جستجوی رویدادهای مربوط به شما استفاده می‌شوند. سایر کاربران رویدادهایی که می‌خواهند شما ببینید را به رله‌های خواندن شما منتشر می‌کنند.', + 'write relays description': + 'رله‌های نوشتن برای انتشار رویدادهای شما استفاده می‌شوند. سایر کاربران رویدادهای شما را از رله‌های نوشتن شما جستجو می‌کنند.', + 'read & write relays notice': + 'تعداد سرورهای خواندن و نوشتن ترجیحاً باید بین ۲ تا ۴ نگه داشته شود.', + "Don't have an account yet?": 'هنوز حساب کاربری ندارید؟', + 'or simply generate a private key': 'یا به سادگی یک کلید خصوصی تولید کنید', + 'This is a private key. Do not share it with anyone. Keep it safe and secure. You will not be able to recover it if you lose it.': + 'این یک کلید خصوصی است. آن را با هیچ کس به اشتراک نگذارید. آن را ایمن و محفوظ نگه دارید. اگر آن را گم کنید نمی‌توانید بازیابی کنید.', + Edit: 'ویرایش', + Save: 'ذخیره', + 'Display Name': 'نام نمایشی', + Bio: 'بیوگرافی', + 'Nostr Address (NIP-05)': 'آدرس Nostr (NIP-05)', + 'Invalid NIP-05 address': 'آدرس NIP-05 نامعتبر', + 'Copy private key': 'کپی کلید خصوصی', + 'Enter the password to decrypt your ncryptsec': + 'رمز عبور را برای رمزگشایی ncryptsec خود وارد کنید', + Back: 'بازگشت', + 'optional: encrypt nsec': 'اختیاری: رمزگذاری nsec', + password: 'رمز عبور', + 'Sign up': 'ثبت نام', + 'Save to': 'ذخیره در', + 'Enter a name for the new relay set': 'نامی برای مجموعه رله جدید وارد کنید', + 'Save to a new relay set': 'ذخیره در مجموعه رله جدید', + Mute: 'بی‌صدا', + Muted: 'بی‌صدا شده', + Unmute: 'لغو بی‌صدا', + 'Unmute user': 'لغو بی‌صدا کردن کاربر', + 'Append n relays': 'افزودن {{n}} رله', + Append: 'افزودن', + 'Select relays to append': 'رله‌ها را برای افزودن انتخاب کنید', + 'calculating...': 'در حال محاسبه...', + 'Calculate optimal read relays': 'محاسبه رله‌های خواندن بهینه', + 'Login to set': 'برای تنظیم وارد شوید', + 'Please login to view following feed': 'لطفاً برای مشاهده فید دنبال شونده‌ها وارد شوید', + 'Send only to r': 'فقط به {{r}} ارسال شود', + 'Send only to these relays': 'فقط به این رله‌ها ارسال شود', + Explore: 'کاوش', + 'Search relays': 'جستجو رله‌ها', + relayInfoBadgeAuth: 'احراز هویت', + relayInfoBadgeSearch: 'جستجو', + relayInfoBadgePayment: 'پرداخت', + Operator: 'اپراتور', + Contact: 'تماس', + Software: 'نرم‌افزار', + Version: 'نسخه', + 'Random Relays': 'رله‌های تصادفی', + randomRelaysRefresh: 'بازخوانی', + 'Explore more': 'کاوش بیشتر', + 'Payment page': 'صفحه پرداخت', + 'Supported NIPs': 'NIPهای پشتیبانی شده', + 'Open in a': 'باز کردن در {{a}}', + 'Cannot handle event of kind k': 'نمی‌توان رویداد از نوع {{k}} را پردازش کرد', + 'Sorry! The note cannot be found 😔': 'متأسفانه! یادداشت یافت نشد 😔', + 'This user has been muted': 'این کاربر بی‌صدا شده است', + Wallet: 'کیف پول', + Sats: 'ساتوشی', + sats: 'ساتوشی', + 'Zap to': 'زپ به', + 'Zap n sats': 'زپ {{n}} ساتوشی', + zapComment: 'نظر', + 'Default zap amount': 'مقدار پیش‌فرض زپ', + 'Default zap comment': 'نظر پیش‌فرض زپ', + 'Lightning Address (or LNURL)': 'آدرس لایتنینگ (یا LNURL)', + 'Quick zap': 'زپ سریع', + 'If enabled, you can zap with a single click. Click and hold for custom amounts': + 'در صورت فعال بودن، می‌توانید با یک کلیک زپ کنید. برای مقادیر سفارشی کلیک کرده و نگه دارید', + All: 'همه', + Reactions: 'واکنش‌ها', + Zaps: 'زپ‌ها', + 'Enjoying Jumble?': 'از Jumble لذت می‌برید؟', + 'Your donation helps me maintain Jumble and make it better! 😊': + 'کمک مالی شما به من در نگهداری Jumble و بهتر کردن آن کمک می‌کند! 😊', + 'Earlier notifications': 'اعلان‌های قبلی', + 'Temporarily display this note': 'نمایش موقت این یادداشت', + buttonFollowing: 'دنبال می‌کنم', + 'Are you sure you want to unfollow this user?': + 'آیا مطمئن هستید که می‌خواهید این کاربر را دنبال نکنید؟', + 'Recent Supporters': 'حامیان اخیر', + 'Seen on': 'دیده شده در', + 'Temporarily display this reply': 'نمایش موقت این پاسخ', + 'Note not found': 'یادداشت یافت نشد', + 'no more replies': 'پاسخ بیشتری وجود ندارد', + 'Relay sets': 'مجموعه‌های رله', + 'Favorite Relays': 'رله‌های مورد علاقه', + "Following's Favorites": 'مورد علاقه دنبال شونده‌ها', + 'no more relays': 'رله بیشتری وجود ندارد', + 'Favorited by': 'مورد علاقه', + 'Post settings': 'تنظیمات پست', + 'Media upload service': 'سرویس آپلود رسانه', + 'Choose a relay': 'یک رله انتخاب کنید', + 'no relays found': 'رله‌ای یافت نشد', + video: 'ویدیو', + 'Show n new notes': 'نمایش {{n}} یادداشت جدید', + YouTabName: 'شما', + Bookmark: 'نشانک', + 'Remove bookmark': 'حذف نشانک', + 'no bookmarks found': 'نشانکی یافت نشد', + 'no more bookmarks': 'نشانک بیشتری وجود ندارد', + Bookmarks: 'نشانک‌ها', + 'Show more': 'نمایش بیشتر', + General: 'عمومی', + Autoplay: 'پخش خودکار', + 'Enable video autoplay on this device': 'فعال کردن پخش خودکار ویدیو در این دستگاه', + 'Paste or drop media files to upload': 'فایل‌های رسانه را برای آپلود بچسبانید یا بکشید', + Preview: 'پیش‌نمایش', + 'You are about to publish an event signed by [{{eventAuthorName}}]. You are currently logged in as [{{currentUsername}}]. Are you sure?': + 'شما در حال انتشار رویدادی امضا شده توسط [{{eventAuthorName}}] هستید. در حال حاضر به عنوان [{{currentUsername}}] وارد شده‌اید. آیا مطمئن هستید؟', + 'Platinum Sponsors': 'حامیان پلاتینی', + From: 'از', + 'Comment on': 'نظر در مورد', + 'View on njump.me': 'مشاهده در njump.me', + 'Hide content from untrusted users': 'مخفی کردن محتوا از کاربران غیرقابل اعتماد', + 'Only show content from your followed users and the users they follow': + 'فقط محتوای کاربران دنبال شده و کاربرانی که آنها دنبال می‌کنند نشان دهید', + 'Followed by': 'دنبال شده توسط', + 'Mute user privately': 'بی‌صدا کردن کاربر به صورت خصوصی', + 'Mute user publicly': 'بی‌صدا کردن کاربر به صورت عمومی', + Quotes: 'نقل قول‌ها', + 'Lightning Invoice': 'فاکتور لایتنینگ', + 'Bookmark failed': 'نشانک‌گذاری ناموفق', + 'Remove bookmark failed': 'حذف نشانک ناموفق', + Translation: 'ترجمه', + Balance: 'موجودی', + characters: 'کاراکتر', + jumbleTranslateApiKeyDescription: + 'می‌توانید از این کلید API در هر جای دیگری که از LibreTranslate پشتیبانی می‌کند استفاده کنید. آدرس سرویس {{serviceUrl}} است', + 'Top up': 'شارژ', + 'Will receive: {n} characters': 'دریافت خواهید کرد: {{n}} کاراکتر', + 'Top up {n} sats': 'شارژ {{n}} ساتوشی', + 'Minimum top up is {n} sats': 'حداقل شارژ {{n}} ساتوشی است', + Service: 'سرویس', + 'Reset API key': 'بازنشانی کلید API', + 'Are you sure you want to reset your API key? This action cannot be undone.': + 'آیا مطمئن هستید که می‌خواهید کلید API خود را بازنشانی کنید؟ این عمل قابل برگشت نیست.', + Warning: 'هشدار', + 'Your current API key will become invalid immediately, and any applications using it will stop working until you update them with the new key.': + 'کلید API فعلی شما فوراً نامعتبر خواهد شد و هر برنامه‌ای که از آن استفاده می‌کند تا زمانی که آن را با کلید جدید به‌روزرسانی نکنید کار نخواهد کرد.', + 'Service address': 'آدرس سرویس', + Pay: 'پرداخت', + interactions: 'تعاملات', + notifications: 'اعلان‌ها', + 'Show untrusted {type}': 'نمایش {{type}} غیرقابل اعتماد', + 'Hide untrusted {type}': 'مخفی کردن {{type}} غیرقابل اعتماد', + 'Currently hiding {type} from untrusted users.': + 'در حال حاضر {{type}} از کاربران غیرقابل اعتماد مخفی می‌شود.', + 'Currently showing all {type}.': 'در حال حاضر همه {{type}} نمایش داده می‌شود.', + 'Click continue to show all {type}.': 'برای نمایش همه {{type}} روی ادامه کلیک کنید.', + 'Click continue to hide {type} from untrusted users.': + 'برای مخفی کردن {{type}} از کاربران غیرقابل اعتماد روی ادامه کلیک کنید.', + 'Trusted users include people you follow and people they follow.': + 'کاربران قابل اعتماد شامل افرادی که دنبال می‌کنید و افرادی که آنها دنبال می‌کنند می‌شوند.', + Continue: 'ادامه', + 'Successfully updated mute list': 'لیست بی‌صدا با موفقیت به‌روزرسانی شد', + 'No pubkeys found from {url}': 'هیچ کلید عمومی از {{url}} یافت نشد', + 'Translating...': 'در حال ترجمه...', + Translate: 'ترجمه', + 'Show original': 'نمایش اصل', + Website: 'وب‌سایت', + 'Hide untrusted notes': 'مخفی کردن یادداشت‌های غیرقابل اعتماد', + 'Open in another client': 'باز کردن در کلاینت دیگر', + Community: 'جامعه', + Group: 'گروه', + 'Live event': 'رویداد زنده', + Article: 'مقاله', + Unfavorite: 'حذف از علاقه‌مندی‌ها', + 'Recommended relays': 'رله‌های توصیه شده', + 'Blossom server URLs': 'آدرس‌های سرور Blossom', + 'You need to add at least one blossom server in order to upload media files.': + 'برای آپلود فایل‌های رسانه نیاز دارید حداقل یک سرور blossom اضافه کنید.', + 'Recommended blossom servers': 'سرورهای blossom توصیه شده', + 'Enter Blossom server URL': 'آدرس سرور Blossom را وارد کنید', + Preferred: 'ترجیحی' + } +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 2cae3ff3..28e832c8 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -87,6 +87,9 @@ export function detectLanguage(text?: string): string | null { if (/[\u0600-\u06ff]/.test(cleanText)) { return 'ar' } + if (/[\u0590-\u05FF]/.test(cleanText)) { + return 'fa' + } if (/[\u0400-\u04ff]/.test(cleanText)) { return 'ru' } @@ -98,6 +101,8 @@ export function detectLanguage(text?: string): string | null { deu: 'de', // German eng: 'en', // English spa: 'es', // Spanish + fas: 'fa', // Persian (Farsi) + pes: 'fa', // Persian (alternative code) fra: 'fr', // French ita: 'it', // Italian jpn: 'ja', // Japanese