diff --git a/app/Http/Controllers/AdminChannelCreationController.php b/app/Http/Controllers/AdminChannelCreationController.php index a1af04d..4116fb7 100644 --- a/app/Http/Controllers/AdminChannelCreationController.php +++ b/app/Http/Controllers/AdminChannelCreationController.php @@ -29,8 +29,8 @@ class AdminChannelCreationController extends Controller return back()->with( 'success', $settings->channel_creation_open - ? 'All verified users can create channels.' - : 'Channel creation now requires admin approval.', + ? __('All verified users can create channels.') + : __('Channel creation now requires admin approval.'), ); } @@ -51,8 +51,8 @@ class AdminChannelCreationController extends Controller return back()->with( 'success', $user->can_create_channel - ? 'User can create a channel.' - : 'User channel creation access removed.', + ? __('User can create a channel.') + : __('User channel creation access removed.'), ); } diff --git a/app/Http/Controllers/AdminModerationController.php b/app/Http/Controllers/AdminModerationController.php index aa3453d..2be5ec9 100644 --- a/app/Http/Controllers/AdminModerationController.php +++ b/app/Http/Controllers/AdminModerationController.php @@ -25,7 +25,7 @@ class AdminModerationController extends Controller $this->audit($request, $channel, 'channel.suspended'); - return back()->with('success', 'Channel suspended.'); + return back()->with('success', __('Channel suspended.')); } public function restoreChannel(Request $request, Channel $channel): RedirectResponse @@ -34,7 +34,7 @@ class AdminModerationController extends Controller $this->audit($request, $channel, 'channel.restored'); - return back()->with('success', 'Channel restored.'); + return back()->with('success', __('Channel restored.')); } public function suspendUser(Request $request, User $user, ViewerCountStore $viewerCounts): RedirectResponse @@ -52,7 +52,7 @@ class AdminModerationController extends Controller $this->audit($request, $user, 'user.suspended'); - return back()->with('success', 'User suspended.'); + return back()->with('success', __('User suspended.')); } public function stopBroadcast(Request $request, Broadcast $broadcast, ViewerCountStore $viewerCounts): RedirectResponse @@ -71,7 +71,7 @@ class AdminModerationController extends Controller $this->audit($request, $broadcast, 'broadcast.stopped'); - return back()->with('success', 'Broadcast stopped.'); + return back()->with('success', __('Broadcast stopped.')); } public function deleteVod(Request $request, Vod $vod): RedirectResponse @@ -80,7 +80,7 @@ class AdminModerationController extends Controller $this->audit($request, $vod, 'vod.deleted'); - return back()->with('success', 'VOD deleted.'); + return back()->with('success', __('VOD deleted.')); } private function audit(Request $request, object $subject, string $action): void diff --git a/app/Http/Controllers/AdminSupportConversationController.php b/app/Http/Controllers/AdminSupportConversationController.php index 4ef97b0..a9fc204 100644 --- a/app/Http/Controllers/AdminSupportConversationController.php +++ b/app/Http/Controllers/AdminSupportConversationController.php @@ -46,7 +46,7 @@ class AdminSupportConversationController extends Controller public function reply(StoreSupportMessageRequest $request, SupportConversation $conversation, SupportMessageCreator $messages): RedirectResponse { - abort_unless($conversation->isOpen(), 422, 'Reopen this conversation before replying.'); + abort_unless($conversation->isOpen(), 422, __('Reopen this conversation before replying.')); $validated = $request->validated(); @@ -57,7 +57,7 @@ class AdminSupportConversationController extends Controller $request->file('attachments', []) ?: [], ); - return back()->with('success', 'Reply sent.'); + return back()->with('success', __('Reply sent.')); } public function update(UpdateSupportConversationStatusRequest $request, SupportConversation $conversation): RedirectResponse @@ -68,7 +68,7 @@ class AdminSupportConversationController extends Controller return back()->with( 'success', - $conversation->isOpen() ? 'Conversation reopened.' : 'Conversation closed.', + $conversation->isOpen() ? __('Conversation reopened.') : __('Conversation closed.'), ); } @@ -103,7 +103,7 @@ class AdminSupportConversationController extends Controller return [ 'id' => $conversation->id, 'category' => $conversation->category, - 'category_label' => SupportConversation::categoryLabels()[$conversation->category] ?? 'Other', + 'category_label' => SupportConversation::categoryLabels()[$conversation->category] ?? __('Other'), 'subject' => $conversation->subject, 'status' => $conversation->status, 'last_message_at' => $conversation->last_message_at?->toIso8601String(), @@ -144,7 +144,7 @@ class AdminSupportConversationController extends Controller 'created_at' => $message->created_at?->toIso8601String(), 'author' => [ 'id' => $message->user?->id, - 'name' => $message->user?->name ?? 'Deleted user', + 'name' => $message->user?->name ?? __('Deleted user'), 'is_admin' => (bool) $message->user?->is_admin, ], ]; diff --git a/app/Http/Controllers/ChatMessageController.php b/app/Http/Controllers/ChatMessageController.php index 0995946..eb3f24a 100644 --- a/app/Http/Controllers/ChatMessageController.php +++ b/app/Http/Controllers/ChatMessageController.php @@ -11,7 +11,7 @@ class ChatMessageController extends Controller public function store(Request $request, Channel $channel): RedirectResponse { abort_if($request->user()->isSuspended() || $channel->isSuspended(), 403); - abort_unless($channel->is_live && $channel->live_broadcast_id, 422, 'Chat is available only while the channel is live.'); + abort_unless($channel->is_live && $channel->live_broadcast_id, 422, __('Chat is available only while the channel is live.')); $validated = $request->validate([ 'body' => ['required', 'string', 'max:500'], diff --git a/app/Http/Controllers/CreatorDashboardController.php b/app/Http/Controllers/CreatorDashboardController.php index 666ee9f..8737481 100644 --- a/app/Http/Controllers/CreatorDashboardController.php +++ b/app/Http/Controllers/CreatorDashboardController.php @@ -58,8 +58,8 @@ class CreatorDashboardController extends Controller public function storeChannel(Request $request, StreamKeyManager $streamKeys): RedirectResponse { - abort_if($request->user()->channel()->exists(), 422, 'This account already owns a channel.'); - abort_unless($request->user()->canCreateChannel(), 403, 'An admin must enable channel creation for this account.'); + abort_if($request->user()->channel()->exists(), 422, __('This account already owns a channel.')); + abort_unless($request->user()->canCreateChannel(), 403, __('An admin must enable channel creation for this account.')); $validated = $request->validate([ 'display_name' => ['required', 'string', 'max:80'], @@ -79,7 +79,7 @@ class CreatorDashboardController extends Controller return redirect() ->route('dashboard') ->with('plain_stream_key', $plainStreamKey) - ->with('success', 'Channel created. Save the stream key now; it will not be shown again.'); + ->with('success', __('Channel created. Save the stream key now; it will not be shown again.')); } public function updateChannel(Request $request): RedirectResponse @@ -112,7 +112,7 @@ class CreatorDashboardController extends Controller 'slug' => Str::slug($validated['slug']), ]); - return back()->with('success', 'Channel updated.'); + return back()->with('success', __('Channel updated.')); } public function rotateStreamKey(Request $request, StreamKeyManager $streamKeys): RedirectResponse @@ -125,7 +125,7 @@ class CreatorDashboardController extends Controller return back() ->with('plain_stream_key', $plainStreamKey) - ->with('success', 'Stream key rotated. Update OBS before going live again.'); + ->with('success', __('Stream key rotated. Update OBS before going live again.')); } public function storeBroadcast(Request $request): RedirectResponse @@ -137,7 +137,7 @@ class CreatorDashboardController extends Controller abort_if( $channel->broadcasts()->whereIn('status', [Broadcast::STATUS_PENDING, Broadcast::STATUS_LIVE])->exists(), 422, - 'Finish the current broadcast before creating another.', + __('Finish the current broadcast before creating another.'), ); $validated = $request->validate([ @@ -152,7 +152,7 @@ class CreatorDashboardController extends Controller 'mediamtx_path' => $channel->slug, ]); - return back()->with('success', 'Broadcast is ready. Start streaming from OBS.'); + return back()->with('success', __('Broadcast is ready. Start streaming from OBS.')); } public function stopBroadcast(Request $request, Broadcast $broadcast, ViewerCountStore $viewerCounts): RedirectResponse @@ -171,7 +171,7 @@ class CreatorDashboardController extends Controller ])->save(); $viewerCounts->forget($broadcast->channel); - return back()->with('success', 'Broadcast ended.'); + return back()->with('success', __('Broadcast ended.')); } private function channelPayload(Channel $channel): array diff --git a/app/Http/Controllers/LanguageController.php b/app/Http/Controllers/LanguageController.php new file mode 100644 index 0000000..3524d8c --- /dev/null +++ b/app/Http/Controllers/LanguageController.php @@ -0,0 +1,29 @@ +validated(); + + return back()->withCookie(cookie( + (string) config('localization.cookie', 'locale'), + (string) $validated['locale'], + 60 * 24 * 365, + (string) config('session.path', '/'), + config('session.domain'), + config('session.secure'), + true, + false, + config('session.same_site', 'lax'), + )); + } +} diff --git a/app/Http/Controllers/SupportConversationController.php b/app/Http/Controllers/SupportConversationController.php index 511ebaf..17bc237 100644 --- a/app/Http/Controllers/SupportConversationController.php +++ b/app/Http/Controllers/SupportConversationController.php @@ -45,7 +45,7 @@ class SupportConversationController extends Controller return redirect() ->route('support.show', $conversation) - ->with('success', 'Message sent to admin.'); + ->with('success', __('Message sent to admin.')); } public function show(Request $request, SupportConversation $conversation): Response @@ -65,7 +65,7 @@ class SupportConversationController extends Controller public function reply(StoreSupportMessageRequest $request, SupportConversation $conversation, SupportMessageCreator $messages): RedirectResponse { $this->authorizeUserConversation($request, $conversation); - abort_unless($conversation->isOpen(), 422, 'This conversation is closed.'); + abort_unless($conversation->isOpen(), 422, __('This conversation is closed.')); $validated = $request->validated(); @@ -76,7 +76,7 @@ class SupportConversationController extends Controller $request->file('attachments', []) ?: [], ); - return back()->with('success', 'Reply sent.'); + return back()->with('success', __('Reply sent.')); } private function authorizeUserConversation(Request $request, SupportConversation $conversation): void @@ -117,7 +117,7 @@ class SupportConversationController extends Controller return [ 'id' => $conversation->id, 'category' => $conversation->category, - 'category_label' => SupportConversation::categoryLabels()[$conversation->category] ?? 'Other', + 'category_label' => SupportConversation::categoryLabels()[$conversation->category] ?? __('Other'), 'subject' => $conversation->subject, 'status' => $conversation->status, 'last_message_at' => $conversation->last_message_at?->toIso8601String(), @@ -153,7 +153,7 @@ class SupportConversationController extends Controller 'created_at' => $message->created_at?->toIso8601String(), 'author' => [ 'id' => $message->user?->id, - 'name' => $message->user?->name ?? 'Deleted user', + 'name' => $message->user?->name ?? __('Deleted user'), 'is_admin' => (bool) $message->user?->is_admin, ], ]; diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index f4cc770..1336100 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -35,13 +35,85 @@ class HandleInertiaRequests extends Middleware */ public function share(Request $request): array { + $fallbackLocale = (string) config('app.fallback_locale'); + $locale = app()->getLocale(); + $locales = $this->locales(); + $direction = $locales[$locale]['direction'] ?? 'ltr'; + return [ ...parent::share($request), 'name' => config('app.name'), 'auth' => [ 'user' => $request->user(), ], + 'fallbackLocale' => $fallbackLocale, + 'locale' => $locale, + 'localization' => [ + 'locale' => $locale, + 'fallbackLocale' => $fallbackLocale, + 'direction' => $direction, + 'isRtl' => $direction === 'rtl', + 'locales' => $this->localeOptions($locales), + ], 'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true', + 'translations' => fn () => $this->translations(), ]; } + + /** + * @return array + */ + private function locales(): array + { + $locales = config('localization.locales', []); + + return is_array($locales) ? $locales : []; + } + + /** + * @param array $locales + * @return array + */ + private function localeOptions(array $locales): array + { + return collect($locales) + ->map(fn (array $locale, string $code): array => [ + 'code' => $code, + 'name' => $locale['name'], + 'nativeName' => $locale['native_name'], + 'direction' => $locale['direction'], + ]) + ->values() + ->all(); + } + + /** + * @return array + */ + private function translations(): array + { + $fallbackLocale = (string) config('app.fallback_locale'); + $locale = app()->getLocale(); + + return [ + ...$this->jsonTranslations($fallbackLocale), + ...$this->jsonTranslations($locale), + ]; + } + + /** + * @return array + */ + private function jsonTranslations(string $locale): array + { + $path = lang_path("{$locale}.json"); + + if (! is_file($path)) { + return []; + } + + $translations = json_decode((string) file_get_contents($path), true); + + return is_array($translations) ? $translations : []; + } } diff --git a/app/Http/Middleware/SetLocale.php b/app/Http/Middleware/SetLocale.php new file mode 100644 index 0000000..5653447 --- /dev/null +++ b/app/Http/Middleware/SetLocale.php @@ -0,0 +1,56 @@ +cookie($cookieName); + $configuredLocale = (string) config('app.locale', 'en'); + + $locale = $this->validLocale($cookieLocale, $supportedLocales) + ?? $this->validLocale($configuredLocale, $supportedLocales) + ?? $this->validLocale((string) config('app.fallback_locale', 'en'), $supportedLocales) + ?? 'en'; + + App::setLocale($locale); + + $response = $next($request); + + if ($request->hasCookie($cookieName) && $this->validLocale($cookieLocale, $supportedLocales) === null) { + $response->headers->setCookie(Cookie::forget( + $cookieName, + (string) config('session.path', '/'), + config('session.domain'), + )); + } + + return $response; + } + + /** + * @param array $supportedLocales + */ + private function validLocale(mixed $locale, array $supportedLocales): ?string + { + if (! is_string($locale)) { + return null; + } + + return in_array($locale, $supportedLocales, true) ? $locale : null; + } +} diff --git a/app/Http/Requests/UpdateLanguageRequest.php b/app/Http/Requests/UpdateLanguageRequest.php new file mode 100644 index 0000000..a40e693 --- /dev/null +++ b/app/Http/Requests/UpdateLanguageRequest.php @@ -0,0 +1,30 @@ +|string> + */ + public function rules(): array + { + return [ + 'locale' => ['required', 'string', Rule::in(array_keys((array) config('localization.locales', [])))], + ]; + } +} diff --git a/app/Models/SupportConversation.php b/app/Models/SupportConversation.php index 54ead81..8e1995b 100644 --- a/app/Models/SupportConversation.php +++ b/app/Models/SupportConversation.php @@ -34,10 +34,10 @@ class SupportConversation extends Model public static function categoryLabels(): array { return [ - self::CATEGORY_BUG => 'Bug report', - self::CATEGORY_SUGGESTION => 'Suggestion', - self::CATEGORY_CHANNEL_REQUEST => 'Channel request', - self::CATEGORY_OTHER => 'Other', + self::CATEGORY_BUG => (string) __('Bug report'), + self::CATEGORY_SUGGESTION => (string) __('Suggestion'), + self::CATEGORY_CHANNEL_REQUEST => (string) __('Channel request'), + self::CATEGORY_OTHER => (string) __('Other'), ]; } diff --git a/app/Rules/SupportAttachmentFile.php b/app/Rules/SupportAttachmentFile.php index 422e31c..a5aecb9 100644 --- a/app/Rules/SupportAttachmentFile.php +++ b/app/Rules/SupportAttachmentFile.php @@ -64,9 +64,9 @@ class SupportAttachmentFile implements ValidationRule ], ], [ - 'attachment.file' => 'Attachment upload failed. Try the file again or choose a different file.', - 'attachment.max' => 'Attachments must be 200 MB or smaller.', - 'attachment.mimetypes' => 'Unsupported attachment type. Upload media, PDF, text/data, Office, OpenDocument, or archive files.', + 'attachment.file' => __('Attachment upload failed. Try the file again or choose a different file.'), + 'attachment.max' => __('Attachments must be 200 MB or smaller.'), + 'attachment.mimetypes' => __('Unsupported attachment type. Upload media, PDF, text/data, Office, OpenDocument, or archive files.'), ], ); diff --git a/bootstrap/app.php b/bootstrap/app.php index fa812df..d746fa7 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -3,6 +3,7 @@ use App\Http\Middleware\EnsureUserIsAdmin; use App\Http\Middleware\HandleAppearance; use App\Http\Middleware\HandleInertiaRequests; +use App\Http\Middleware\SetLocale; use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; @@ -24,6 +25,7 @@ return Application::configure(basePath: dirname(__DIR__)) ]); $middleware->web(append: [ + SetLocale::class, HandleAppearance::class, HandleInertiaRequests::class, AddLinkHeadersForPreloadedAssets::class, diff --git a/config/localization.php b/config/localization.php new file mode 100644 index 0000000..4579c2e --- /dev/null +++ b/config/localization.php @@ -0,0 +1,50 @@ + 'locale', + + /* + |-------------------------------------------------------------------------- + | Supported Interface Locales + |-------------------------------------------------------------------------- + | + | These locales are available in the language switcher and are validated + | before being stored in the locale cookie. The direction value controls + | the document dir attribute, RTL layout variants, and RTL font selection. + | + */ + + 'locales' => [ + 'en' => [ + 'name' => 'English', + 'native_name' => 'English', + 'direction' => 'ltr', + ], + 'es' => [ + 'name' => 'Spanish', + 'native_name' => 'Español', + 'direction' => 'ltr', + ], + 'fa' => [ + 'name' => 'Persian', + 'native_name' => 'فارسی', + 'direction' => 'rtl', + ], + 'ar' => [ + 'name' => 'Arabic', + 'native_name' => 'العربية', + 'direction' => 'rtl', + ], + ], +]; diff --git a/lang/ar.json b/lang/ar.json new file mode 100644 index 0000000..d0a02d9 --- /dev/null +++ b/lang/ar.json @@ -0,0 +1,333 @@ +{ + "2FA recovery codes": "رموز استرداد 2FA", + ":channel by :owner": ":channel بواسطة :owner", + ":channel is live now with :count viewers.": ":channel مباشر الآن مع :count مشاهدين.", + ":channel is offline": ":channel غير متصل", + ":count active broadcasts": ":count بث نشط", + ":count available": ":count متاح", + ":count followers": ":count متابعين", + ":count messages": ":count رسائل", + ":count rows": ":count صفوف", + ":count threads": ":count محادثات", + ":count viewers": ":count مشاهدين", + ":status - :mode": ":status - :mode", + ":status - :mode - VOD ready": ":status - :mode - VOD جاهز", + "@:slug owned by :owner": "@:slug مملوك لـ :owner", + "A new verification link has been sent to the email address you provided during registration.": "تم إرسال رابط تحقق جديد إلى عنوان البريد الإلكتروني الذي قدمته أثناء التسجيل.", + "A new verification link has been sent to your email address.": "تم إرسال رابط تحقق جديد إلى عنوان بريدك الإلكتروني.", + "ADMIN": "مسؤول", + "Admin": "مسؤول", + "All categories": "كل الفئات", + "All verified users can create channels.": "يمكن لجميع المستخدمين الموثقين إنشاء قنوات.", + "Already have an account?": "هل لديك حساب بالفعل؟", + "An admin can grant creator access from the admin console.": "يمكن للمسؤول منح وصول المنشئ من لوحة الإدارة.", + "An admin must enable channel creation for this account.": "يجب على المسؤول تمكين إنشاء القنوات لهذا الحساب.", + "Appearance": "المظهر", + "Appearance settings": "إعدادات المظهر", + "Approval only": "بالموافقة فقط", + "Are you sure you want to delete your account?": "هل أنت متأكد أنك تريد حذف حسابك؟", + "Attach up to 3 files, 200 MB each.": "أرفق ما يصل إلى 3 ملفات، 200 ميغابايت لكل ملف.", + "Attachment upload failed. Try the file again or choose a different file.": "فشل رفع المرفق. جرّب الملف مرة أخرى أو اختر ملفًا آخر.", + "Attachments": "المرفقات", + "Attachments must be 200 MB or smaller.": "يجب أن تكون المرفقات 200 ميغابايت أو أقل.", + "Authentication code": "رمز المصادقة", + "Avatar": "الصورة الرمزية", + "Back": "رجوع", + "Browse active channels by category or title.": "تصفح القنوات النشطة حسب الفئة أو العنوان.", + "Broadcast ended.": "انتهى البث.", + "Broadcast is ready. Start streaming from OBS.": "البث جاهز. ابدأ البث من OBS.", + "Broadcast prep": "تحضير البث", + "Broadcast state": "حالة البث", + "Broadcast stopped.": "تم إيقاف البث.", + "Bug report": "تقرير خطأ", + "Bug reports, suggestions, channel requests, and account questions.": "تقارير الأخطاء والاقتراحات وطلبات القنوات وأسئلة الحساب.", + "Building Nyone live": "بناء Nyone Live", + "CAN CREATE": "يمكنه الإنشاء", + "CAN CREATE CHANNEL": "يمكنه إنشاء قناة", + "CLOSED": "مغلق", + "Cancel": "إلغاء", + "Categories": "الفئات", + "Category": "الفئة", + "Channel": "القناة", + "Channel created. Save the stream key now; it will not be shown again.": "تم إنشاء القناة. احفظ مفتاح البث الآن؛ لن يظهر مرة أخرى.", + "Channel creation access": "وصول إنشاء القنوات", + "Channel creation now requires admin approval.": "يتطلب إنشاء القنوات الآن موافقة المسؤول.", + "Channel creation requires approval": "إنشاء القنوات يتطلب موافقة", + "Channel is offline": "القناة غير متصلة", + "Channel metadata": "بيانات القناة الوصفية", + "Channel request": "طلب قناة", + "Channel restored.": "تمت استعادة القناة.", + "Channel setup": "إعداد القناة", + "Channel suspended.": "تم تعليق القناة.", + "Channel updated.": "تم تحديث القناة.", + "Chat": "الدردشة", + "Chat appears when the channel is live.": "تظهر الدردشة عندما تكون القناة مباشرة.", + "Chat is available only while the channel is live.": "الدردشة متاحة فقط أثناء البث المباشر للقناة.", + "Clear search or switch categories.": "امسح البحث أو بدّل الفئات.", + "Click here to resend the verification email.": "انقر هنا لإعادة إرسال بريد التحقق.", + "Close": "إغلاق", + "Closed": "مغلق", + "Confirm": "تأكيد", + "Confirm password": "تأكيد كلمة المرور", + "Confirm your password": "أكد كلمة المرور", + "Contact": "اتصال", + "Contact admin": "اتصل بالمسؤول", + "Contact threads from users and channel request notes.": "محادثات الاتصال من المستخدمين وملاحظات طلبات القنوات.", + "Continue": "متابعة", + "Continue setup": "متابعة الإعداد", + "Control who can create a creator channel.": "تحكم في من يمكنه إنشاء قناة منشئ.", + "Conversations": "المحادثات", + "Create a pending session before starting OBS.": "أنشئ جلسة معلقة قبل بدء OBS.", + "Create account": "إنشاء حساب", + "Create an account": "إنشاء حساب", + "Create channel": "إنشاء قناة", + "Create your channel": "أنشئ قناتك", + "Creator Studio": "استوديو المنشئ", + "Creator access grants stay in the moderation console.": "تبقى أذونات وصول المنشئ في وحدة الإشراف.", + "Creator grants": "أذونات المنشئ", + "Creators can prepare a broadcast in the studio and go live from OBS when ready.": "يمكن للمنشئين تحضير بث في الاستوديو والبدء مباشرة من OBS عند الجاهزية.", + "Current password": "كلمة المرور الحالية", + "Current session": "الجلسة الحالية", + "Current viewers": "المشاهدون الحاليون", + "DEFAULT": "افتراضي", + "Dark": "داكن", + "Delete account": "حذف الحساب", + "Delete your account and all of its resources": "احذف حسابك وكل موارده", + "Deleted user": "مستخدم محذوف", + "Description": "الوصف", + "Disable 2FA": "تعطيل 2FA", + "Displays the mobile sidebar.": "يعرض الشريط الجانبي للجوال.", + "Display name": "اسم العرض", + "Don't have an account?": "ليس لديك حساب؟", + "Each account can own one public channel in this version.": "يمكن لكل حساب امتلاك قناة عامة واحدة في هذا الإصدار.", + "Each recovery code can be used once to access your account and will be removed after use. If you need more, click": "يمكن استخدام كل رمز استرداد مرة واحدة للوصول إلى حسابك وسيتم حذفه بعد الاستخدام. إذا احتجت إلى المزيد، فانقر", + "Email": "البريد الإلكتروني", + "Email address": "عنوان البريد الإلكتروني", + "Email password reset link": "إرسال رابط إعادة تعيين كلمة المرور", + "Email verification": "التحقق من البريد الإلكتروني", + "Enable 2FA": "تفعيل 2FA", + "Enable two-factor authentication": "تفعيل المصادقة الثنائية", + "Ensure your account is using a long, random password to stay secure": "تأكد من أن حسابك يستخدم كلمة مرور طويلة وعشوائية ليبقى آمنًا", + "Enter recovery code": "أدخل رمز الاسترداد", + "Enter the 6-digit code from your authenticator app": "أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة", + "Enter the authentication code provided by your authenticator application.": "أدخل رمز المصادقة الذي يوفره تطبيق المصادقة.", + "Enter your details below to create your account": "أدخل بياناتك أدناه لإنشاء حسابك", + "Enter your email and password below to log in": "أدخل بريدك الإلكتروني وكلمة المرور أدناه لتسجيل الدخول", + "Enter your email to receive a password reset link": "أدخل بريدك الإلكتروني لتلقي رابط إعادة تعيين كلمة المرور", + "Expires :date": "ينتهي في :date", + "Featured live": "بث مباشر مميز", + "Finish the current broadcast before creating another.": "أنهِ البث الحالي قبل إنشاء بث آخر.", + "Finish the current broadcast before preparing another session.": "أنهِ البث الحالي قبل تحضير جلسة أخرى.", + "Follow": "متابعة", + "Following": "تتابع", + "Forgot password": "نسيت كلمة المرور", + "Forgot password?": "هل نسيت كلمة المرور؟", + "Full name": "الاسم الكامل", + "Grant": "منح", + "Hide password": "إخفاء كلمة المرور", + "Hide recovery codes": "إخفاء رموز الاسترداد", + "Insert :emoji": "إدراج :emoji", + "JPG, PNG, or WebP up to 2 MB.": "JPG أو PNG أو WebP حتى 2 ميغابايت.", + "Light": "فاتح", + "LIVE": "مباشر", + "LIVE - :count viewers": "مباشر - :count مشاهدين", + "Live": "مباشر", + "Live broadcasts": "البثوث المباشرة", + "Live channels": "القنوات المباشرة", + "Live directory": "دليل البث المباشر", + "Live ingest signal": "إشارة إدخال البث المباشر", + "Live only": "المباشر فقط", + "Live preview": "معاينة مباشرة", + "Live signal": "إشارة مباشرة", + "Loading": "جارٍ التحميل", + "Loading recovery codes": "جارٍ تحميل رموز الاسترداد", + "Log in": "تسجيل الدخول", + "Log in to chat": "سجّل الدخول للدردشة", + "Log in to follow": "سجّل الدخول للمتابعة", + "Log in to your account": "سجّل الدخول إلى حسابك", + "Log out": "تسجيل الخروج", + "Manage your profile and account settings": "أدر ملفك الشخصي وإعدادات حسابك", + "Manage your two-factor authentication settings": "أدر إعدادات المصادقة الثنائية", + "Message": "رسالة", + "Message sent to admin.": "تم إرسال الرسالة إلى المسؤول.", + "Moderation console": "وحدة الإشراف", + "More": "المزيد", + "NEEDS APPROVAL": "يتطلب موافقة", + "NEEDS CHANNEL GRANT": "يتطلب إذن قناة", + "Name": "الاسم", + "Navigation menu": "قائمة التنقل", + "Never": "أبدًا", + "New conversation": "محادثة جديدة", + "New password": "كلمة مرور جديدة", + "No active session.": "لا توجد جلسة نشطة.", + "No broadcasts yet.": "لا توجد بثوث بعد.", + "No category": "بلا فئة", + "No channels are live.": "لا توجد قنوات مباشرة.", + "No conversations yet.": "لا توجد محادثات بعد.", + "No messages yet.": "لا توجد رسائل بعد.", + "No public recordings yet.": "لا توجد تسجيلات عامة بعد.", + "No streams match this view": "لا توجد بثوث تطابق هذا العرض", + "No support conversations found.": "لم يتم العثور على محادثات دعم.", + "No thumbnail": "بلا صورة مصغرة", + "No users found.": "لم يتم العثور على مستخدمين.", + "Nyone Live": "Nyone Live", + "OBS readiness": "جاهزية OBS", + "OBS stream key": "مفتاح بث OBS", + "OFFLINE": "غير متصل", + "OPEN": "مفتوح", + "Offline": "غير متصل", + "Once your account is deleted, all of its resources and data will also be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "بمجرد حذف حسابك، سيتم حذف كل موارده وبياناته نهائيًا أيضًا. يرجى إدخال كلمة المرور لتأكيد رغبتك في حذف حسابك نهائيًا.", + "Open": "فتح", + "Open channel": "فتح القناة", + "Open emoji picker": "فتح منتقي الرموز التعبيرية", + "Open moderation": "فتح الإشراف", + "Open studio": "فتح الاستوديو", + "Open to all": "مفتوح للجميع", + "Or, return to": "أو ارجع إلى", + "Other": "أخرى", + "Owner": "المالك", + "Password": "كلمة المرور", + "Password updated.": "تم تحديث كلمة المرور.", + "Platform": "المنصة", + "Please confirm access to your account by entering one of your emergency recovery codes.": "يرجى تأكيد الوصول إلى حسابك بإدخال أحد رموز الاسترداد الطارئة.", + "Please enter your new password below": "يرجى إدخال كلمة المرور الجديدة أدناه", + "Please proceed with caution, this cannot be undone.": "يرجى المتابعة بحذر، لا يمكن التراجع عن هذا.", + "Please verify your email address by clicking on the link we just emailed to you.": "يرجى التحقق من عنوان بريدك الإلكتروني بالنقر على الرابط الذي أرسلناه إليك للتو.", + "Prepare": "تحضير", + "Prepare broadcasts, secure your stream key, and monitor the current session.": "حضّر البثوث، أمّن مفتاح البث، وراقب الجلسة الحالية.", + "Prepared broadcast": "بث محضّر", + "Private stream key": "مفتاح بث خاص", + "Processing recording": "جارٍ معالجة التسجيل", + "Profile": "الملف الشخصي", + "Profile information": "معلومات الملف الشخصي", + "Profile settings": "إعدادات الملف الشخصي", + "Profile updated.": "تم تحديث الملف الشخصي.", + "Public channels broadcasting now": "قنوات عامة تبث الآن", + "Public metadata used in the directory and watch page.": "بيانات وصفية عامة مستخدمة في الدليل وصفحة المشاهدة.", + "READY": "جاهز", + "Recent VODs": "أحدث VOD", + "Recent broadcasts": "البثوث الأخيرة", + "Recent channels": "القنوات الأخيرة", + "Recent recordings remain available below when the creator publishes VODs.": "تبقى التسجيلات الأخيرة متاحة أدناه عندما ينشر المنشئ VOD.", + "Record VOD": "تسجيل VOD", + "Recording enabled": "التسجيل مفعّل", + "Recovery code": "رمز الاسترداد", + "Recovery codes": "رموز الاسترداد", + "Recovery codes let you regain access if you lose your 2FA device. Store them in a secure password manager.": "تتيح لك رموز الاسترداد استعادة الوصول إذا فقدت جهاز 2FA. خزّنها في مدير كلمات مرور آمن.", + "Regenerate codes": "إعادة إنشاء الرموز", + "Register": "تسجيل", + "Remember me": "تذكرني", + "Remove grant": "إزالة الإذن", + "Reopen": "إعادة فتح", + "Reopen the conversation to reply.": "أعد فتح المحادثة للرد.", + "Reopen this conversation before replying.": "أعد فتح هذه المحادثة قبل الرد.", + "Reply": "رد", + "Reply sent.": "تم إرسال الرد.", + "Resend verification email": "إعادة إرسال بريد التحقق", + "Reset password": "إعادة تعيين كلمة المرور", + "Restore": "استعادة", + "Restore channel?": "استعادة القناة؟", + "Review live streams, suspended states, users, and recordings.": "راجع البثوث المباشرة وحالات التعليق والمستخدمين والتسجيلات.", + "Rotate immediately if it was exposed.": "قم بتدويره فورًا إذا تم كشفه.", + "Rotate key": "تدوير المفتاح", + "Rotate the key to reveal it once.": "دوّر المفتاح لكشفه مرة واحدة.", + "Save": "حفظ", + "Save channel": "حفظ القناة", + "Save password": "حفظ كلمة المرور", + "Search live channels": "البحث في القنوات المباشرة", + "Security": "الأمان", + "Security settings": "إعدادات الأمان", + "Send": "إرسال", + "Send a message": "إرسال رسالة", + "Send message": "إرسال رسالة", + "Send reply": "إرسال رد", + "Server": "الخادم", + "Settings": "الإعدادات", + "Show password": "إظهار كلمة المرور", + "Sidebar": "الشريط الجانبي", + "Sign up": "إنشاء حساب", + "Slug": "المعرّف", + "Something went wrong.": "حدث خطأ ما.", + "Start OBS to push the session live.": "ابدأ OBS لدفع الجلسة إلى البث المباشر.", + "Start a channel": "بدء قناة", + "Stop": "إيقاف", + "Stop broadcast": "إيقاف البث", + "Stop broadcast?": "إيقاف البث؟", + "Stop live broadcast?": "إيقاف البث المباشر؟", + "Stream key": "مفتاح البث", + "Stream key rotated. Update OBS before going live again.": "تم تدوير مفتاح البث. حدّث OBS قبل البث المباشر مرة أخرى.", + "Stream key used": "تم استخدام مفتاح البث", + "Stream keys can publish to this channel. Rotate immediately if it was exposed.": "يمكن لمفاتيح البث النشر إلى هذه القناة. قم بتدويرها فورًا إذا تم كشفها.", + "Stream title": "عنوان البث", + "Studio": "الاستوديو", + "Subject": "الموضوع", + "Suggestion": "اقتراح", + "Support": "الدعم", + "Support inbox": "صندوق الدعم", + "SUSPENDED": "معلّق", + "Suspend": "تعليق", + "Suspend channel?": "تعليق القناة؟", + "System": "النظام", + "The channel will be hidden from public browsing and watch pages.": "سيتم إخفاء القناة من التصفح العام وصفحات المشاهدة.", + "The channel will be visible again if the owning account is active.": "ستظهر القناة مرة أخرى إذا كان حساب المالك نشطًا.", + "The live floor is quiet": "ساحة البث هادئة", + "The live session will end immediately and the channel will move offline.": "ستنتهي الجلسة المباشرة فورًا وستصبح القناة غير متصلة.", + "This account already owns a channel.": "هذا الحساب يمتلك قناة بالفعل.", + "This account can browse and follow channels, but cannot create a channel yet.": "يمكن لهذا الحساب تصفح القنوات ومتابعتها، لكنه لا يستطيع إنشاء قناة بعد.", + "This conversation is closed.": "هذه المحادثة مغلقة.", + "This ends the live session and clears the channel's live state. Viewers will see the offline screen.": "ينهي هذا الجلسة المباشرة ويمسح حالة القناة المباشرة. سيرى المشاهدون شاشة عدم الاتصال.", + "This key is shown once. Store it in OBS before leaving this page.": "يظهر هذا المفتاح مرة واحدة. خزّنه في OBS قبل مغادرة هذه الصفحة.", + "To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app": "لإنهاء تفعيل المصادقة الثنائية، امسح رمز QR أو أدخل مفتاح الإعداد في تطبيق المصادقة", + "Toggle sidebar": "تبديل الشريط الجانبي", + "Two-factor authentication": "المصادقة الثنائية", + "Two-factor authentication enabled": "تم تفعيل المصادقة الثنائية", + "Uncategorized": "غير مصنف", + "Unsupported attachment type. Upload media, PDF, text/data, Office, OpenDocument, or archive files.": "نوع المرفق غير مدعوم. ارفع ملفات وسائط أو PDF أو نص/بيانات أو Office أو OpenDocument أو ملفات أرشيف.", + "Update password": "تحديث كلمة المرور", + "Update your account's appearance settings": "حدّث إعدادات مظهر حسابك", + "Update your name, email address, and avatar": "حدّث اسمك وعنوان بريدك الإلكتروني وصورتك الرمزية", + "Use a 16:9 JPG, PNG, or WebP up to 4 MB.": "استخدم JPG أو PNG أو WebP بنسبة 16:9 حتى 4 ميغابايت.", + "USER": "مستخدم", + "User": "مستخدم", + "User can create a channel.": "يمكن للمستخدم إنشاء قناة.", + "User channel creation access removed.": "تمت إزالة وصول إنشاء القنوات للمستخدم.", + "User suspended.": "تم تعليق المستخدم.", + "Users": "المستخدمون", + "Verify email": "تحقق من البريد الإلكتروني", + "View channel": "عرض القناة", + "View recovery codes": "عرض رموز الاسترداد", + "Viewers": "المشاهدون", + "VOD deleted.": "تم حذف VOD.", + "VOD ready": "VOD جاهز", + "VODs": "VOD", + "Warning": "تحذير", + "Watch": "مشاهدة", + "Watch VOD": "مشاهدة VOD", + "When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.": "عند تفعيل المصادقة الثنائية، سيُطلب منك رقم PIN آمن أثناء تسجيل الدخول. يمكن الحصول على هذا الرقم من تطبيق يدعم TOTP على هاتفك.", + "You": "أنت", + "YOU": "أنت", + "You will be prompted for a secure, random pin during login, which you can retrieve from the TOTP-supported application on your phone.": "سيُطلب منك رقم PIN آمن وعشوائي أثناء تسجيل الدخول، ويمكنك الحصول عليه من تطبيق يدعم TOTP على هاتفك.", + "Your email address is unverified.": "عنوان بريدك الإلكتروني غير موثق.", + "above.": "أعلاه.", + "breadcrumb": "مسار التنقل", + "email@example.com": "email@example.com", + "later": "لاحقًا", + "live only": "المباشر فقط", + "log in": "تسجيل الدخول", + "login using a recovery code": "تسجيل الدخول باستخدام رمز استرداد", + "login using an authentication code": "تسجيل الدخول باستخدام رمز مصادقة", + "nyone-live": "nyone-live", + "or you can": "أو يمكنك", + "or, enter the code manually": "أو أدخل الرمز يدويًا", + "pending": "معلّق", + "recording": "جارٍ التسجيل", + "Arabic": "العربية", + "Change language": "تغيير اللغة", + "Choose the language used across Nyone.": "اختر اللغة المستخدمة في Nyone.", + "English": "الإنجليزية", + "Interface language": "لغة الواجهة", + "Language": "اللغة", + "Persian": "الفارسية", + "Spanish": "الإسبانية", + "unknown": "غير معروف" +} diff --git a/lang/ar/auth.php b/lang/ar/auth.php new file mode 100644 index 0000000..1042a3b --- /dev/null +++ b/lang/ar/auth.php @@ -0,0 +1,20 @@ + 'بيانات الاعتماد هذه لا تطابق سجلاتنا.', + 'password' => 'كلمة المرور المقدمة غير صحيحة.', + 'throttle' => 'عدد محاولات تسجيل الدخول كبير جدًا. يرجى المحاولة مرة أخرى بعد :seconds ثوانٍ.', + +]; diff --git a/lang/ar/pagination.php b/lang/ar/pagination.php new file mode 100644 index 0000000..2eee353 --- /dev/null +++ b/lang/ar/pagination.php @@ -0,0 +1,19 @@ + '« السابق', + 'next' => 'التالي »', + +]; diff --git a/lang/ar/passwords.php b/lang/ar/passwords.php new file mode 100644 index 0000000..d4b7ff1 --- /dev/null +++ b/lang/ar/passwords.php @@ -0,0 +1,22 @@ + 'تمت إعادة تعيين كلمة المرور.', + 'sent' => 'لقد أرسلنا رابط إعادة تعيين كلمة المرور إلى بريدك الإلكتروني.', + 'throttled' => 'يرجى الانتظار قبل المحاولة مرة أخرى.', + 'token' => 'رمز إعادة تعيين كلمة المرور هذا غير صالح.', + 'user' => 'لا يمكننا العثور على مستخدم بهذا البريد الإلكتروني.', + +]; diff --git a/lang/ar/validation.php b/lang/ar/validation.php new file mode 100644 index 0000000..d49827f --- /dev/null +++ b/lang/ar/validation.php @@ -0,0 +1,200 @@ + 'يجب قبول حقل :attribute.', + 'accepted_if' => 'يجب قبول حقل :attribute عندما يكون :other هو :value.', + 'active_url' => 'يجب أن يكون حقل :attribute رابطًا صالحًا.', + 'after' => 'يجب أن يكون حقل :attribute تاريخًا بعد :date.', + 'after_or_equal' => 'يجب أن يكون حقل :attribute تاريخًا بعد أو يساوي :date.', + 'alpha' => 'يجب أن يحتوي حقل :attribute على أحرف فقط.', + 'alpha_dash' => 'يجب أن يحتوي حقل :attribute على أحرف وأرقام وشرطات وشرطات سفلية فقط.', + 'alpha_num' => 'يجب أن يحتوي حقل :attribute على أحرف وأرقام فقط.', + 'any_of' => 'حقل :attribute غير صالح.', + 'array' => 'يجب أن يكون حقل :attribute مصفوفة.', + 'ascii' => 'يجب أن يحتوي حقل :attribute على أحرف وأرقام ورموز أحادية البايت فقط.', + 'before' => 'يجب أن يكون حقل :attribute تاريخًا قبل :date.', + 'before_or_equal' => 'يجب أن يكون حقل :attribute تاريخًا قبل أو يساوي :date.', + 'between' => [ + 'array' => 'يجب أن يحتوي حقل :attribute على ما بين :min و :max عناصر.', + 'file' => 'يجب أن يكون حقل :attribute بين :min و :max كيلوبايت.', + 'numeric' => 'يجب أن يكون حقل :attribute بين :min و :max.', + 'string' => 'يجب أن يكون حقل :attribute بين :min و :max أحرف.', + ], + 'boolean' => 'يجب أن يكون حقل :attribute صحيحًا أو خطأ.', + 'can' => 'يحتوي حقل :attribute على قيمة غير مصرح بها.', + 'confirmed' => 'تأكيد حقل :attribute غير مطابق.', + 'contains' => 'يفتقد حقل :attribute قيمة مطلوبة.', + 'current_password' => 'كلمة المرور غير صحيحة.', + 'date' => 'يجب أن يكون حقل :attribute تاريخًا صالحًا.', + 'date_equals' => 'يجب أن يكون حقل :attribute تاريخًا يساوي :date.', + 'date_format' => 'يجب أن يطابق حقل :attribute التنسيق :format.', + 'decimal' => 'يجب أن يحتوي حقل :attribute على :decimal منازل عشرية.', + 'declined' => 'يجب رفض حقل :attribute.', + 'declined_if' => 'يجب رفض حقل :attribute عندما يكون :other هو :value.', + 'different' => 'يجب أن يكون حقل :attribute و :other مختلفين.', + 'digits' => 'يجب أن يكون حقل :attribute مكونًا من :digits أرقام.', + 'digits_between' => 'يجب أن يكون حقل :attribute بين :min و :max أرقام.', + 'dimensions' => 'أبعاد صورة حقل :attribute غير صالحة.', + 'distinct' => 'يحتوي حقل :attribute على قيمة مكررة.', + 'doesnt_contain' => 'يجب ألا يحتوي حقل :attribute على أي من القيم التالية: :values.', + 'doesnt_end_with' => 'يجب ألا ينتهي حقل :attribute بإحدى القيم التالية: :values.', + 'doesnt_start_with' => 'يجب ألا يبدأ حقل :attribute بإحدى القيم التالية: :values.', + 'email' => 'يجب أن يكون حقل :attribute عنوان بريد إلكتروني صالحًا.', + 'encoding' => 'يجب أن يكون حقل :attribute مشفرًا باستخدام :encoding.', + 'ends_with' => 'يجب أن ينتهي حقل :attribute بإحدى القيم التالية: :values.', + 'enum' => ':attribute المحدد غير صالح.', + 'exists' => ':attribute المحدد غير صالح.', + 'extensions' => 'يجب أن يحتوي حقل :attribute على إحدى الامتدادات التالية: :values.', + 'file' => 'يجب أن يكون حقل :attribute ملفًا.', + 'filled' => 'يجب أن يحتوي حقل :attribute على قيمة.', + 'gt' => [ + 'array' => 'يجب أن يحتوي حقل :attribute على أكثر من :value عناصر.', + 'file' => 'يجب أن يكون حقل :attribute أكبر من :value كيلوبايت.', + 'numeric' => 'يجب أن يكون حقل :attribute أكبر من :value.', + 'string' => 'يجب أن يكون حقل :attribute أكبر من :value أحرف.', + ], + 'gte' => [ + 'array' => 'يجب أن يحتوي حقل :attribute على :value عناصر أو أكثر.', + 'file' => 'يجب أن يكون حقل :attribute أكبر من أو يساوي :value كيلوبايت.', + 'numeric' => 'يجب أن يكون حقل :attribute أكبر من أو يساوي :value.', + 'string' => 'يجب أن يكون حقل :attribute :value أحرف أو أكثر.', + ], + 'hex_color' => 'يجب أن يكون حقل :attribute لونًا سداسيًا صالحًا.', + 'image' => 'يجب أن يكون حقل :attribute صورة.', + 'in' => ':attribute المحدد غير صالح.', + 'in_array' => 'يجب أن يوجد حقل :attribute في :other.', + 'in_array_keys' => 'يجب أن يحتوي حقل :attribute على واحد على الأقل من المفاتيح التالية: :values.', + 'integer' => 'يجب أن يكون حقل :attribute عددًا صحيحًا.', + 'ip' => 'يجب أن يكون حقل :attribute عنوان IP صالحًا.', + 'ipv4' => 'يجب أن يكون حقل :attribute عنوان IPv4 صالحًا.', + 'ipv6' => 'يجب أن يكون حقل :attribute عنوان IPv6 صالحًا.', + 'json' => 'يجب أن يكون حقل :attribute سلسلة JSON صالحة.', + 'list' => 'يجب أن يكون حقل :attribute قائمة.', + 'lowercase' => 'يجب أن يكون حقل :attribute بأحرف صغيرة.', + 'lt' => [ + 'array' => 'يجب أن يحتوي حقل :attribute على أقل من :value عناصر.', + 'file' => 'يجب أن يكون حقل :attribute أقل من :value كيلوبايت.', + 'numeric' => 'يجب أن يكون حقل :attribute أقل من :value.', + 'string' => 'يجب أن يكون حقل :attribute أقل من :value أحرف.', + ], + 'lte' => [ + 'array' => 'يجب ألا يحتوي حقل :attribute على أكثر من :value عناصر.', + 'file' => 'يجب أن يكون حقل :attribute أقل من أو يساوي :value كيلوبايت.', + 'numeric' => 'يجب أن يكون حقل :attribute أقل من أو يساوي :value.', + 'string' => 'يجب أن يكون حقل :attribute :value أحرف أو أقل.', + ], + 'mac_address' => 'يجب أن يكون حقل :attribute عنوان MAC صالحًا.', + 'max' => [ + 'array' => 'يجب ألا يحتوي حقل :attribute على أكثر من :max عناصر.', + 'file' => 'يجب ألا يكون حقل :attribute أكبر من :max كيلوبايت.', + 'numeric' => 'يجب ألا يكون حقل :attribute أكبر من :max.', + 'string' => 'يجب ألا يكون حقل :attribute أكبر من :max أحرف.', + ], + 'max_digits' => 'يجب ألا يحتوي حقل :attribute على أكثر من :max أرقام.', + 'mimes' => 'يجب أن يكون حقل :attribute ملفًا من النوع: :values.', + 'mimetypes' => 'يجب أن يكون حقل :attribute ملفًا من النوع: :values.', + 'min' => [ + 'array' => 'يجب أن يحتوي حقل :attribute على الأقل على :min عناصر.', + 'file' => 'يجب أن يكون حقل :attribute على الأقل :min كيلوبايت.', + 'numeric' => 'يجب أن يكون حقل :attribute على الأقل :min.', + 'string' => 'يجب أن يكون حقل :attribute على الأقل :min أحرف.', + ], + 'min_digits' => 'يجب أن يحتوي حقل :attribute على الأقل على :min أرقام.', + 'missing' => 'يجب أن يكون حقل :attribute غير موجود.', + 'missing_if' => 'يجب أن يكون حقل :attribute غير موجود عندما يكون :other هو :value.', + 'missing_unless' => 'يجب أن يكون حقل :attribute غير موجود ما لم يكن :other هو :value.', + 'missing_with' => 'يجب أن يكون حقل :attribute غير موجود عندما تكون :values موجودة.', + 'missing_with_all' => 'يجب أن يكون حقل :attribute غير موجود عندما تكون :values موجودة.', + 'multiple_of' => 'يجب أن يكون حقل :attribute مضاعفًا لـ :value.', + 'not_in' => ':attribute المحدد غير صالح.', + 'not_regex' => 'تنسيق حقل :attribute غير صالح.', + 'numeric' => 'يجب أن يكون حقل :attribute رقمًا.', + 'password' => [ + 'letters' => 'يجب أن يحتوي حقل :attribute على حرف واحد على الأقل.', + 'mixed' => 'يجب أن يحتوي حقل :attribute على حرف كبير وحرف صغير على الأقل.', + 'numbers' => 'يجب أن يحتوي حقل :attribute على رقم واحد على الأقل.', + 'symbols' => 'يجب أن يحتوي حقل :attribute على رمز واحد على الأقل.', + 'uncompromised' => 'ظهر :attribute المحدد في تسريب بيانات. يرجى اختيار :attribute مختلف.', + ], + 'present' => 'يجب أن يكون حقل :attribute موجودًا.', + 'present_if' => 'يجب أن يكون حقل :attribute موجودًا عندما يكون :other هو :value.', + 'present_unless' => 'يجب أن يكون حقل :attribute موجودًا ما لم يكن :other هو :value.', + 'present_with' => 'يجب أن يكون حقل :attribute موجودًا عندما تكون :values موجودة.', + 'present_with_all' => 'يجب أن يكون حقل :attribute موجودًا عندما تكون :values موجودة.', + 'prohibited' => 'حقل :attribute محظور.', + 'prohibited_if' => 'حقل :attribute محظور عندما يكون :other هو :value.', + 'prohibited_if_accepted' => 'حقل :attribute محظور عندما يكون :other مقبولًا.', + 'prohibited_if_declined' => 'حقل :attribute محظور عندما يكون :other مرفوضًا.', + 'prohibited_unless' => 'حقل :attribute محظور ما لم يكن :other ضمن :values.', + 'prohibits' => 'حقل :attribute يمنع :other من الوجود.', + 'regex' => 'تنسيق حقل :attribute غير صالح.', + 'required' => 'حقل :attribute مطلوب.', + 'required_array_keys' => 'يجب أن يحتوي حقل :attribute على مدخلات لـ: :values.', + 'required_if' => 'حقل :attribute مطلوب عندما يكون :other هو :value.', + 'required_if_accepted' => 'حقل :attribute مطلوب عندما يكون :other مقبولًا.', + 'required_if_declined' => 'حقل :attribute مطلوب عندما يكون :other مرفوضًا.', + 'required_unless' => 'حقل :attribute مطلوب ما لم يكن :other ضمن :values.', + 'required_with' => 'حقل :attribute مطلوب عندما تكون :values موجودة.', + 'required_with_all' => 'حقل :attribute مطلوب عندما تكون :values موجودة.', + 'required_without' => 'حقل :attribute مطلوب عندما لا تكون :values موجودة.', + 'required_without_all' => 'حقل :attribute مطلوب عندما لا تكون أي من :values موجودة.', + 'same' => 'يجب أن يطابق حقل :attribute حقل :other.', + 'size' => [ + 'array' => 'يجب أن يحتوي حقل :attribute على :size عناصر.', + 'file' => 'يجب أن يكون حقل :attribute :size كيلوبايت.', + 'numeric' => 'يجب أن يكون حقل :attribute :size.', + 'string' => 'يجب أن يكون حقل :attribute :size أحرف.', + ], + 'starts_with' => 'يجب أن يبدأ حقل :attribute بإحدى القيم التالية: :values.', + 'string' => 'يجب أن يكون حقل :attribute سلسلة نصية.', + 'timezone' => 'يجب أن يكون حقل :attribute منطقة زمنية صالحة.', + 'unique' => 'قيمة :attribute مستخدمة بالفعل.', + 'uploaded' => 'فشل رفع :attribute.', + 'uppercase' => 'يجب أن يكون حقل :attribute بأحرف كبيرة.', + 'url' => 'يجب أن يكون حقل :attribute رابطًا صالحًا.', + 'ulid' => 'يجب أن يكون حقل :attribute ULID صالحًا.', + 'uuid' => 'يجب أن يكون حقل :attribute UUID صالحًا.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'رسالة-مخصصة', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +]; diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..8ea7dd1 --- /dev/null +++ b/lang/en.json @@ -0,0 +1,333 @@ +{ + "2FA recovery codes": "2FA recovery codes", + ":channel by :owner": ":channel by :owner", + ":channel is live now with :count viewers.": ":channel is live now with :count viewers.", + ":channel is offline": ":channel is offline", + ":count active broadcasts": ":count active broadcasts", + ":count available": ":count available", + ":count followers": ":count followers", + ":count messages": ":count messages", + ":count rows": ":count rows", + ":count threads": ":count threads", + ":count viewers": ":count viewers", + ":status - :mode": ":status - :mode", + ":status - :mode - VOD ready": ":status - :mode - VOD ready", + "@:slug owned by :owner": "@:slug owned by :owner", + "A new verification link has been sent to the email address you provided during registration.": "A new verification link has been sent to the email address you provided during registration.", + "A new verification link has been sent to your email address.": "A new verification link has been sent to your email address.", + "ADMIN": "ADMIN", + "Admin": "Admin", + "All categories": "All categories", + "All verified users can create channels.": "All verified users can create channels.", + "Already have an account?": "Already have an account?", + "An admin can grant creator access from the admin console.": "An admin can grant creator access from the admin console.", + "An admin must enable channel creation for this account.": "An admin must enable channel creation for this account.", + "Appearance": "Appearance", + "Appearance settings": "Appearance settings", + "Approval only": "Approval only", + "Are you sure you want to delete your account?": "Are you sure you want to delete your account?", + "Attach up to 3 files, 200 MB each.": "Attach up to 3 files, 200 MB each.", + "Attachment upload failed. Try the file again or choose a different file.": "Attachment upload failed. Try the file again or choose a different file.", + "Attachments": "Attachments", + "Attachments must be 200 MB or smaller.": "Attachments must be 200 MB or smaller.", + "Authentication code": "Authentication code", + "Avatar": "Avatar", + "Back": "Back", + "Browse active channels by category or title.": "Browse active channels by category or title.", + "Broadcast ended.": "Broadcast ended.", + "Broadcast is ready. Start streaming from OBS.": "Broadcast is ready. Start streaming from OBS.", + "Broadcast prep": "Broadcast prep", + "Broadcast state": "Broadcast state", + "Broadcast stopped.": "Broadcast stopped.", + "Bug report": "Bug report", + "Bug reports, suggestions, channel requests, and account questions.": "Bug reports, suggestions, channel requests, and account questions.", + "Building Nyone live": "Building Nyone live", + "CAN CREATE": "CAN CREATE", + "CAN CREATE CHANNEL": "CAN CREATE CHANNEL", + "CLOSED": "CLOSED", + "Cancel": "Cancel", + "Categories": "Categories", + "Category": "Category", + "Channel": "Channel", + "Channel created. Save the stream key now; it will not be shown again.": "Channel created. Save the stream key now; it will not be shown again.", + "Channel creation access": "Channel creation access", + "Channel creation now requires admin approval.": "Channel creation now requires admin approval.", + "Channel creation requires approval": "Channel creation requires approval", + "Channel is offline": "Channel is offline", + "Channel metadata": "Channel metadata", + "Channel request": "Channel request", + "Channel restored.": "Channel restored.", + "Channel setup": "Channel setup", + "Channel suspended.": "Channel suspended.", + "Channel updated.": "Channel updated.", + "Chat": "Chat", + "Chat appears when the channel is live.": "Chat appears when the channel is live.", + "Chat is available only while the channel is live.": "Chat is available only while the channel is live.", + "Clear search or switch categories.": "Clear search or switch categories.", + "Click here to resend the verification email.": "Click here to resend the verification email.", + "Close": "Close", + "Closed": "Closed", + "Confirm": "Confirm", + "Confirm password": "Confirm password", + "Confirm your password": "Confirm your password", + "Contact": "Contact", + "Contact admin": "Contact admin", + "Contact threads from users and channel request notes.": "Contact threads from users and channel request notes.", + "Continue": "Continue", + "Continue setup": "Continue setup", + "Control who can create a creator channel.": "Control who can create a creator channel.", + "Conversations": "Conversations", + "Create a pending session before starting OBS.": "Create a pending session before starting OBS.", + "Create account": "Create account", + "Create an account": "Create an account", + "Create channel": "Create channel", + "Create your channel": "Create your channel", + "Creator Studio": "Creator Studio", + "Creator access grants stay in the moderation console.": "Creator access grants stay in the moderation console.", + "Creator grants": "Creator grants", + "Creators can prepare a broadcast in the studio and go live from OBS when ready.": "Creators can prepare a broadcast in the studio and go live from OBS when ready.", + "Current password": "Current password", + "Current session": "Current session", + "Current viewers": "Current viewers", + "DEFAULT": "DEFAULT", + "Dark": "Dark", + "Delete account": "Delete account", + "Delete your account and all of its resources": "Delete your account and all of its resources", + "Deleted user": "Deleted user", + "Description": "Description", + "Disable 2FA": "Disable 2FA", + "Displays the mobile sidebar.": "Displays the mobile sidebar.", + "Display name": "Display name", + "Don't have an account?": "Don't have an account?", + "Each account can own one public channel in this version.": "Each account can own one public channel in this version.", + "Each recovery code can be used once to access your account and will be removed after use. If you need more, click": "Each recovery code can be used once to access your account and will be removed after use. If you need more, click", + "Email": "Email", + "Email address": "Email address", + "Email password reset link": "Email password reset link", + "Email verification": "Email verification", + "Enable 2FA": "Enable 2FA", + "Enable two-factor authentication": "Enable two-factor authentication", + "Ensure your account is using a long, random password to stay secure": "Ensure your account is using a long, random password to stay secure", + "Enter recovery code": "Enter recovery code", + "Enter the 6-digit code from your authenticator app": "Enter the 6-digit code from your authenticator app", + "Enter the authentication code provided by your authenticator application.": "Enter the authentication code provided by your authenticator application.", + "Enter your details below to create your account": "Enter your details below to create your account", + "Enter your email and password below to log in": "Enter your email and password below to log in", + "Enter your email to receive a password reset link": "Enter your email to receive a password reset link", + "Expires :date": "Expires :date", + "Featured live": "Featured live", + "Finish the current broadcast before creating another.": "Finish the current broadcast before creating another.", + "Finish the current broadcast before preparing another session.": "Finish the current broadcast before preparing another session.", + "Follow": "Follow", + "Following": "Following", + "Forgot password": "Forgot password", + "Forgot password?": "Forgot password?", + "Full name": "Full name", + "Grant": "Grant", + "Hide password": "Hide password", + "Hide recovery codes": "Hide recovery codes", + "Insert :emoji": "Insert :emoji", + "JPG, PNG, or WebP up to 2 MB.": "JPG, PNG, or WebP up to 2 MB.", + "Light": "Light", + "LIVE": "LIVE", + "LIVE - :count viewers": "LIVE - :count viewers", + "Live": "Live", + "Live broadcasts": "Live broadcasts", + "Live channels": "Live channels", + "Live directory": "Live directory", + "Live ingest signal": "Live ingest signal", + "Live only": "Live only", + "Live preview": "Live preview", + "Live signal": "Live signal", + "Loading": "Loading", + "Loading recovery codes": "Loading recovery codes", + "Log in": "Log in", + "Log in to chat": "Log in to chat", + "Log in to follow": "Log in to follow", + "Log in to your account": "Log in to your account", + "Log out": "Log out", + "Manage your profile and account settings": "Manage your profile and account settings", + "Manage your two-factor authentication settings": "Manage your two-factor authentication settings", + "Message": "Message", + "Message sent to admin.": "Message sent to admin.", + "Moderation console": "Moderation console", + "More": "More", + "NEEDS APPROVAL": "NEEDS APPROVAL", + "NEEDS CHANNEL GRANT": "NEEDS CHANNEL GRANT", + "Name": "Name", + "Navigation menu": "Navigation menu", + "Never": "Never", + "New conversation": "New conversation", + "New password": "New password", + "No active session.": "No active session.", + "No broadcasts yet.": "No broadcasts yet.", + "No category": "No category", + "No channels are live.": "No channels are live.", + "No conversations yet.": "No conversations yet.", + "No messages yet.": "No messages yet.", + "No public recordings yet.": "No public recordings yet.", + "No streams match this view": "No streams match this view", + "No support conversations found.": "No support conversations found.", + "No thumbnail": "No thumbnail", + "No users found.": "No users found.", + "Nyone Live": "Nyone Live", + "OBS readiness": "OBS readiness", + "OBS stream key": "OBS stream key", + "OFFLINE": "OFFLINE", + "OPEN": "OPEN", + "Offline": "Offline", + "Once your account is deleted, all of its resources and data will also be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "Once your account is deleted, all of its resources and data will also be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.", + "Open": "Open", + "Open channel": "Open channel", + "Open emoji picker": "Open emoji picker", + "Open moderation": "Open moderation", + "Open studio": "Open studio", + "Open to all": "Open to all", + "Or, return to": "Or, return to", + "Other": "Other", + "Owner": "Owner", + "Password": "Password", + "Password updated.": "Password updated.", + "Platform": "Platform", + "Please confirm access to your account by entering one of your emergency recovery codes.": "Please confirm access to your account by entering one of your emergency recovery codes.", + "Please enter your new password below": "Please enter your new password below", + "Please proceed with caution, this cannot be undone.": "Please proceed with caution, this cannot be undone.", + "Please verify your email address by clicking on the link we just emailed to you.": "Please verify your email address by clicking on the link we just emailed to you.", + "Prepare": "Prepare", + "Prepare broadcasts, secure your stream key, and monitor the current session.": "Prepare broadcasts, secure your stream key, and monitor the current session.", + "Prepared broadcast": "Prepared broadcast", + "Private stream key": "Private stream key", + "Processing recording": "Processing recording", + "Profile": "Profile", + "Profile information": "Profile information", + "Profile settings": "Profile settings", + "Profile updated.": "Profile updated.", + "Public channels broadcasting now": "Public channels broadcasting now", + "Public metadata used in the directory and watch page.": "Public metadata used in the directory and watch page.", + "READY": "READY", + "Recent VODs": "Recent VODs", + "Recent broadcasts": "Recent broadcasts", + "Recent channels": "Recent channels", + "Recent recordings remain available below when the creator publishes VODs.": "Recent recordings remain available below when the creator publishes VODs.", + "Record VOD": "Record VOD", + "Recording enabled": "Recording enabled", + "Recovery code": "Recovery code", + "Recovery codes": "Recovery codes", + "Recovery codes let you regain access if you lose your 2FA device. Store them in a secure password manager.": "Recovery codes let you regain access if you lose your 2FA device. Store them in a secure password manager.", + "Regenerate codes": "Regenerate codes", + "Register": "Register", + "Remember me": "Remember me", + "Remove grant": "Remove grant", + "Reopen": "Reopen", + "Reopen the conversation to reply.": "Reopen the conversation to reply.", + "Reopen this conversation before replying.": "Reopen this conversation before replying.", + "Reply": "Reply", + "Reply sent.": "Reply sent.", + "Resend verification email": "Resend verification email", + "Reset password": "Reset password", + "Restore": "Restore", + "Restore channel?": "Restore channel?", + "Review live streams, suspended states, users, and recordings.": "Review live streams, suspended states, users, and recordings.", + "Rotate immediately if it was exposed.": "Rotate immediately if it was exposed.", + "Rotate key": "Rotate key", + "Rotate the key to reveal it once.": "Rotate the key to reveal it once.", + "Save": "Save", + "Save channel": "Save channel", + "Save password": "Save password", + "Search live channels": "Search live channels", + "Security": "Security", + "Security settings": "Security settings", + "Send": "Send", + "Send a message": "Send a message", + "Send message": "Send message", + "Send reply": "Send reply", + "Server": "Server", + "Settings": "Settings", + "Show password": "Show password", + "Sidebar": "Sidebar", + "Sign up": "Sign up", + "Slug": "Slug", + "Something went wrong.": "Something went wrong.", + "Start OBS to push the session live.": "Start OBS to push the session live.", + "Start a channel": "Start a channel", + "Stop": "Stop", + "Stop broadcast": "Stop broadcast", + "Stop broadcast?": "Stop broadcast?", + "Stop live broadcast?": "Stop live broadcast?", + "Stream key": "Stream key", + "Stream key rotated. Update OBS before going live again.": "Stream key rotated. Update OBS before going live again.", + "Stream key used": "Stream key used", + "Stream keys can publish to this channel. Rotate immediately if it was exposed.": "Stream keys can publish to this channel. Rotate immediately if it was exposed.", + "Stream title": "Stream title", + "Studio": "Studio", + "Subject": "Subject", + "Suggestion": "Suggestion", + "Support": "Support", + "Support inbox": "Support inbox", + "SUSPENDED": "SUSPENDED", + "Suspend": "Suspend", + "Suspend channel?": "Suspend channel?", + "System": "System", + "The channel will be hidden from public browsing and watch pages.": "The channel will be hidden from public browsing and watch pages.", + "The channel will be visible again if the owning account is active.": "The channel will be visible again if the owning account is active.", + "The live floor is quiet": "The live floor is quiet", + "The live session will end immediately and the channel will move offline.": "The live session will end immediately and the channel will move offline.", + "This account already owns a channel.": "This account already owns a channel.", + "This account can browse and follow channels, but cannot create a channel yet.": "This account can browse and follow channels, but cannot create a channel yet.", + "This conversation is closed.": "This conversation is closed.", + "This ends the live session and clears the channel's live state. Viewers will see the offline screen.": "This ends the live session and clears the channel's live state. Viewers will see the offline screen.", + "This key is shown once. Store it in OBS before leaving this page.": "This key is shown once. Store it in OBS before leaving this page.", + "To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app": "To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app", + "Toggle sidebar": "Toggle sidebar", + "Two-factor authentication": "Two-factor authentication", + "Two-factor authentication enabled": "Two-factor authentication enabled", + "Uncategorized": "Uncategorized", + "Unsupported attachment type. Upload media, PDF, text/data, Office, OpenDocument, or archive files.": "Unsupported attachment type. Upload media, PDF, text/data, Office, OpenDocument, or archive files.", + "Update password": "Update password", + "Update your account's appearance settings": "Update your account's appearance settings", + "Update your name, email address, and avatar": "Update your name, email address, and avatar", + "Use a 16:9 JPG, PNG, or WebP up to 4 MB.": "Use a 16:9 JPG, PNG, or WebP up to 4 MB.", + "USER": "USER", + "User": "User", + "User can create a channel.": "User can create a channel.", + "User channel creation access removed.": "User channel creation access removed.", + "User suspended.": "User suspended.", + "Users": "Users", + "Verify email": "Verify email", + "View channel": "View channel", + "View recovery codes": "View recovery codes", + "Viewers": "Viewers", + "VOD deleted.": "VOD deleted.", + "VOD ready": "VOD ready", + "VODs": "VODs", + "Warning": "Warning", + "Watch": "Watch", + "Watch VOD": "Watch VOD", + "When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.": "When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.", + "You": "You", + "YOU": "YOU", + "You will be prompted for a secure, random pin during login, which you can retrieve from the TOTP-supported application on your phone.": "You will be prompted for a secure, random pin during login, which you can retrieve from the TOTP-supported application on your phone.", + "Your email address is unverified.": "Your email address is unverified.", + "above.": "above.", + "breadcrumb": "breadcrumb", + "email@example.com": "email@example.com", + "later": "later", + "live only": "live only", + "log in": "log in", + "login using a recovery code": "login using a recovery code", + "login using an authentication code": "login using an authentication code", + "nyone-live": "nyone-live", + "or you can": "or you can", + "or, enter the code manually": "or, enter the code manually", + "pending": "pending", + "recording": "recording", + "Arabic": "Arabic", + "Change language": "Change language", + "Choose the language used across Nyone.": "Choose the language used across Nyone.", + "English": "English", + "Interface language": "Interface language", + "Language": "Language", + "Persian": "Persian", + "Spanish": "Spanish", + "unknown": "unknown" +} diff --git a/lang/en/auth.php b/lang/en/auth.php new file mode 100644 index 0000000..6598e2c --- /dev/null +++ b/lang/en/auth.php @@ -0,0 +1,20 @@ + 'These credentials do not match our records.', + 'password' => 'The provided password is incorrect.', + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + +]; diff --git a/lang/en/pagination.php b/lang/en/pagination.php new file mode 100644 index 0000000..d481411 --- /dev/null +++ b/lang/en/pagination.php @@ -0,0 +1,19 @@ + '« Previous', + 'next' => 'Next »', + +]; diff --git a/lang/en/passwords.php b/lang/en/passwords.php new file mode 100644 index 0000000..fad3a7d --- /dev/null +++ b/lang/en/passwords.php @@ -0,0 +1,22 @@ + 'Your password has been reset.', + 'sent' => 'We have emailed your password reset link.', + 'throttled' => 'Please wait before retrying.', + 'token' => 'This password reset token is invalid.', + 'user' => "We can't find a user with that email address.", + +]; diff --git a/lang/en/validation.php b/lang/en/validation.php new file mode 100644 index 0000000..63ec29a --- /dev/null +++ b/lang/en/validation.php @@ -0,0 +1,200 @@ + 'The :attribute field must be accepted.', + 'accepted_if' => 'The :attribute field must be accepted when :other is :value.', + 'active_url' => 'The :attribute field must be a valid URL.', + 'after' => 'The :attribute field must be a date after :date.', + 'after_or_equal' => 'The :attribute field must be a date after or equal to :date.', + 'alpha' => 'The :attribute field must only contain letters.', + 'alpha_dash' => 'The :attribute field must only contain letters, numbers, dashes, and underscores.', + 'alpha_num' => 'The :attribute field must only contain letters and numbers.', + 'any_of' => 'The :attribute field is invalid.', + 'array' => 'The :attribute field must be an array.', + 'ascii' => 'The :attribute field must only contain single-byte alphanumeric characters and symbols.', + 'before' => 'The :attribute field must be a date before :date.', + 'before_or_equal' => 'The :attribute field must be a date before or equal to :date.', + 'between' => [ + 'array' => 'The :attribute field must have between :min and :max items.', + 'file' => 'The :attribute field must be between :min and :max kilobytes.', + 'numeric' => 'The :attribute field must be between :min and :max.', + 'string' => 'The :attribute field must be between :min and :max characters.', + ], + 'boolean' => 'The :attribute field must be true or false.', + 'can' => 'The :attribute field contains an unauthorized value.', + 'confirmed' => 'The :attribute field confirmation does not match.', + 'contains' => 'The :attribute field is missing a required value.', + 'current_password' => 'The password is incorrect.', + 'date' => 'The :attribute field must be a valid date.', + 'date_equals' => 'The :attribute field must be a date equal to :date.', + 'date_format' => 'The :attribute field must match the format :format.', + 'decimal' => 'The :attribute field must have :decimal decimal places.', + 'declined' => 'The :attribute field must be declined.', + 'declined_if' => 'The :attribute field must be declined when :other is :value.', + 'different' => 'The :attribute field and :other must be different.', + 'digits' => 'The :attribute field must be :digits digits.', + 'digits_between' => 'The :attribute field must be between :min and :max digits.', + 'dimensions' => 'The :attribute field has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'doesnt_contain' => 'The :attribute field must not contain any of the following: :values.', + 'doesnt_end_with' => 'The :attribute field must not end with one of the following: :values.', + 'doesnt_start_with' => 'The :attribute field must not start with one of the following: :values.', + 'email' => 'The :attribute field must be a valid email address.', + 'encoding' => 'The :attribute field must be encoded in :encoding.', + 'ends_with' => 'The :attribute field must end with one of the following: :values.', + 'enum' => 'The selected :attribute is invalid.', + 'exists' => 'The selected :attribute is invalid.', + 'extensions' => 'The :attribute field must have one of the following extensions: :values.', + 'file' => 'The :attribute field must be a file.', + 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'array' => 'The :attribute field must have more than :value items.', + 'file' => 'The :attribute field must be greater than :value kilobytes.', + 'numeric' => 'The :attribute field must be greater than :value.', + 'string' => 'The :attribute field must be greater than :value characters.', + ], + 'gte' => [ + 'array' => 'The :attribute field must have :value items or more.', + 'file' => 'The :attribute field must be greater than or equal to :value kilobytes.', + 'numeric' => 'The :attribute field must be greater than or equal to :value.', + 'string' => 'The :attribute field must be greater than or equal to :value characters.', + ], + 'hex_color' => 'The :attribute field must be a valid hexadecimal color.', + 'image' => 'The :attribute field must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field must exist in :other.', + 'in_array_keys' => 'The :attribute field must contain at least one of the following keys: :values.', + 'integer' => 'The :attribute field must be an integer.', + 'ip' => 'The :attribute field must be a valid IP address.', + 'ipv4' => 'The :attribute field must be a valid IPv4 address.', + 'ipv6' => 'The :attribute field must be a valid IPv6 address.', + 'json' => 'The :attribute field must be a valid JSON string.', + 'list' => 'The :attribute field must be a list.', + 'lowercase' => 'The :attribute field must be lowercase.', + 'lt' => [ + 'array' => 'The :attribute field must have less than :value items.', + 'file' => 'The :attribute field must be less than :value kilobytes.', + 'numeric' => 'The :attribute field must be less than :value.', + 'string' => 'The :attribute field must be less than :value characters.', + ], + 'lte' => [ + 'array' => 'The :attribute field must not have more than :value items.', + 'file' => 'The :attribute field must be less than or equal to :value kilobytes.', + 'numeric' => 'The :attribute field must be less than or equal to :value.', + 'string' => 'The :attribute field must be less than or equal to :value characters.', + ], + 'mac_address' => 'The :attribute field must be a valid MAC address.', + 'max' => [ + 'array' => 'The :attribute field must not have more than :max items.', + 'file' => 'The :attribute field must not be greater than :max kilobytes.', + 'numeric' => 'The :attribute field must not be greater than :max.', + 'string' => 'The :attribute field must not be greater than :max characters.', + ], + 'max_digits' => 'The :attribute field must not have more than :max digits.', + 'mimes' => 'The :attribute field must be a file of type: :values.', + 'mimetypes' => 'The :attribute field must be a file of type: :values.', + 'min' => [ + 'array' => 'The :attribute field must have at least :min items.', + 'file' => 'The :attribute field must be at least :min kilobytes.', + 'numeric' => 'The :attribute field must be at least :min.', + 'string' => 'The :attribute field must be at least :min characters.', + ], + 'min_digits' => 'The :attribute field must have at least :min digits.', + 'missing' => 'The :attribute field must be missing.', + 'missing_if' => 'The :attribute field must be missing when :other is :value.', + 'missing_unless' => 'The :attribute field must be missing unless :other is :value.', + 'missing_with' => 'The :attribute field must be missing when :values is present.', + 'missing_with_all' => 'The :attribute field must be missing when :values are present.', + 'multiple_of' => 'The :attribute field must be a multiple of :value.', + 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute field format is invalid.', + 'numeric' => 'The :attribute field must be a number.', + 'password' => [ + 'letters' => 'The :attribute field must contain at least one letter.', + 'mixed' => 'The :attribute field must contain at least one uppercase and one lowercase letter.', + 'numbers' => 'The :attribute field must contain at least one number.', + 'symbols' => 'The :attribute field must contain at least one symbol.', + 'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.', + ], + 'present' => 'The :attribute field must be present.', + 'present_if' => 'The :attribute field must be present when :other is :value.', + 'present_unless' => 'The :attribute field must be present unless :other is :value.', + 'present_with' => 'The :attribute field must be present when :values is present.', + 'present_with_all' => 'The :attribute field must be present when :values are present.', + 'prohibited' => 'The :attribute field is prohibited.', + 'prohibited_if' => 'The :attribute field is prohibited when :other is :value.', + 'prohibited_if_accepted' => 'The :attribute field is prohibited when :other is accepted.', + 'prohibited_if_declined' => 'The :attribute field is prohibited when :other is declined.', + 'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.', + 'prohibits' => 'The :attribute field prohibits :other from being present.', + 'regex' => 'The :attribute field format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_array_keys' => 'The :attribute field must contain entries for: :values.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_if_accepted' => 'The :attribute field is required when :other is accepted.', + 'required_if_declined' => 'The :attribute field is required when :other is declined.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values are present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute field must match :other.', + 'size' => [ + 'array' => 'The :attribute field must contain :size items.', + 'file' => 'The :attribute field must be :size kilobytes.', + 'numeric' => 'The :attribute field must be :size.', + 'string' => 'The :attribute field must be :size characters.', + ], + 'starts_with' => 'The :attribute field must start with one of the following: :values.', + 'string' => 'The :attribute field must be a string.', + 'timezone' => 'The :attribute field must be a valid timezone.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'uppercase' => 'The :attribute field must be uppercase.', + 'url' => 'The :attribute field must be a valid URL.', + 'ulid' => 'The :attribute field must be a valid ULID.', + 'uuid' => 'The :attribute field must be a valid UUID.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +]; diff --git a/lang/es.json b/lang/es.json new file mode 100644 index 0000000..8ef8a8b --- /dev/null +++ b/lang/es.json @@ -0,0 +1,333 @@ +{ + "2FA recovery codes": "Códigos de recuperación 2FA", + ":channel by :owner": ":channel de :owner", + ":channel is live now with :count viewers.": ":channel está en vivo ahora con :count espectadores.", + ":channel is offline": ":channel está sin conexión", + ":count active broadcasts": ":count transmisiones activas", + ":count available": ":count disponibles", + ":count followers": ":count seguidores", + ":count messages": ":count mensajes", + ":count rows": ":count filas", + ":count threads": ":count conversaciones", + ":count viewers": ":count espectadores", + ":status - :mode": ":status - :mode", + ":status - :mode - VOD ready": ":status - :mode - VOD listo", + "@:slug owned by :owner": "@:slug pertenece a :owner", + "A new verification link has been sent to the email address you provided during registration.": "Se ha enviado un nuevo enlace de verificación a la dirección de correo electrónico que proporcionaste durante el registro.", + "A new verification link has been sent to your email address.": "Se ha enviado un nuevo enlace de verificación a tu dirección de correo electrónico.", + "ADMIN": "ADMINISTRADOR", + "Admin": "Administrador", + "All categories": "Todas las categorías", + "All verified users can create channels.": "Todos los usuarios verificados pueden crear canales.", + "Already have an account?": "¿Ya tienes una cuenta?", + "An admin can grant creator access from the admin console.": "Un administrador puede conceder acceso de creador desde la consola de administración.", + "An admin must enable channel creation for this account.": "Un administrador debe habilitar la creación de canales para esta cuenta.", + "Appearance": "Apariencia", + "Appearance settings": "Configuración de apariencia", + "Approval only": "Solo con aprobación", + "Are you sure you want to delete your account?": "¿Seguro que quieres eliminar tu cuenta?", + "Attach up to 3 files, 200 MB each.": "Adjunta hasta 3 archivos, de 200 MB cada uno.", + "Attachment upload failed. Try the file again or choose a different file.": "No se pudo subir el archivo adjunto. Intenta con el archivo de nuevo o elige otro.", + "Attachments": "Archivos adjuntos", + "Attachments must be 200 MB or smaller.": "Los archivos adjuntos deben ser de 200 MB o menos.", + "Authentication code": "Código de autenticación", + "Avatar": "Avatar", + "Back": "Volver", + "Browse active channels by category or title.": "Explora canales activos por categoría o título.", + "Broadcast ended.": "La transmisión terminó.", + "Broadcast is ready. Start streaming from OBS.": "La transmisión está lista. Empieza a transmitir desde OBS.", + "Broadcast prep": "Preparación de transmisión", + "Broadcast state": "Estado de la transmisión", + "Broadcast stopped.": "Transmisión detenida.", + "Bug report": "Informe de error", + "Bug reports, suggestions, channel requests, and account questions.": "Informes de errores, sugerencias, solicitudes de canal y preguntas sobre cuentas.", + "Building Nyone live": "Creando Nyone en vivo", + "CAN CREATE": "PUEDE CREAR", + "CAN CREATE CHANNEL": "PUEDE CREAR CANAL", + "CLOSED": "CERRADO", + "Cancel": "Cancelar", + "Categories": "Categorías", + "Category": "Categoría", + "Channel": "Canal", + "Channel created. Save the stream key now; it will not be shown again.": "Canal creado. Guarda la clave de transmisión ahora; no se volverá a mostrar.", + "Channel creation access": "Acceso para crear canales", + "Channel creation now requires admin approval.": "La creación de canales ahora requiere aprobación de un administrador.", + "Channel creation requires approval": "La creación de canales requiere aprobación", + "Channel is offline": "El canal está sin conexión", + "Channel metadata": "Metadatos del canal", + "Channel request": "Solicitud de canal", + "Channel restored.": "Canal restaurado.", + "Channel setup": "Configuración del canal", + "Channel suspended.": "Canal suspendido.", + "Channel updated.": "Canal actualizado.", + "Chat": "Chat", + "Chat appears when the channel is live.": "El chat aparece cuando el canal está en vivo.", + "Chat is available only while the channel is live.": "El chat solo está disponible mientras el canal está en vivo.", + "Clear search or switch categories.": "Borra la búsqueda o cambia de categoría.", + "Click here to resend the verification email.": "Haz clic aquí para reenviar el correo de verificación.", + "Close": "Cerrar", + "Closed": "Cerrado", + "Confirm": "Confirmar", + "Confirm password": "Confirmar contraseña", + "Confirm your password": "Confirma tu contraseña", + "Contact": "Contacto", + "Contact admin": "Contactar al administrador", + "Contact threads from users and channel request notes.": "Conversaciones de contacto de usuarios y notas de solicitudes de canal.", + "Continue": "Continuar", + "Continue setup": "Continuar configuración", + "Control who can create a creator channel.": "Controla quién puede crear un canal de creador.", + "Conversations": "Conversaciones", + "Create a pending session before starting OBS.": "Crea una sesión pendiente antes de iniciar OBS.", + "Create account": "Crear cuenta", + "Create an account": "Crear una cuenta", + "Create channel": "Crear canal", + "Create your channel": "Crea tu canal", + "Creator Studio": "Estudio de creador", + "Creator access grants stay in the moderation console.": "Los permisos de acceso de creador permanecen en la consola de moderación.", + "Creator grants": "Permisos de creador", + "Creators can prepare a broadcast in the studio and go live from OBS when ready.": "Los creadores pueden preparar una transmisión en el estudio y salir en vivo desde OBS cuando esté lista.", + "Current password": "Contraseña actual", + "Current session": "Sesión actual", + "Current viewers": "Espectadores actuales", + "DEFAULT": "PREDETERMINADO", + "Dark": "Oscuro", + "Delete account": "Eliminar cuenta", + "Delete your account and all of its resources": "Elimina tu cuenta y todos sus recursos", + "Deleted user": "Usuario eliminado", + "Description": "Descripción", + "Disable 2FA": "Desactivar 2FA", + "Displays the mobile sidebar.": "Muestra la barra lateral móvil.", + "Display name": "Nombre visible", + "Don't have an account?": "¿No tienes una cuenta?", + "Each account can own one public channel in this version.": "Cada cuenta puede tener un canal público en esta versión.", + "Each recovery code can be used once to access your account and will be removed after use. If you need more, click": "Cada código de recuperación puede usarse una vez para acceder a tu cuenta y se eliminará después de usarlo. Si necesitas más, haz clic", + "Email": "Correo electrónico", + "Email address": "Dirección de correo electrónico", + "Email password reset link": "Enviar enlace para restablecer contraseña", + "Email verification": "Verificación de correo electrónico", + "Enable 2FA": "Activar 2FA", + "Enable two-factor authentication": "Activar autenticación de dos factores", + "Ensure your account is using a long, random password to stay secure": "Asegúrate de que tu cuenta use una contraseña larga y aleatoria para mantenerse segura", + "Enter recovery code": "Introduce el código de recuperación", + "Enter the 6-digit code from your authenticator app": "Introduce el código de 6 dígitos de tu aplicación de autenticación", + "Enter the authentication code provided by your authenticator application.": "Introduce el código de autenticación proporcionado por tu aplicación de autenticación.", + "Enter your details below to create your account": "Introduce tus datos a continuación para crear tu cuenta", + "Enter your email and password below to log in": "Introduce tu correo electrónico y contraseña a continuación para iniciar sesión", + "Enter your email to receive a password reset link": "Introduce tu correo electrónico para recibir un enlace de restablecimiento de contraseña", + "Expires :date": "Caduca el :date", + "Featured live": "En vivo destacado", + "Finish the current broadcast before creating another.": "Finaliza la transmisión actual antes de crear otra.", + "Finish the current broadcast before preparing another session.": "Finaliza la transmisión actual antes de preparar otra sesión.", + "Follow": "Seguir", + "Following": "Siguiendo", + "Forgot password": "Olvidé mi contraseña", + "Forgot password?": "¿Olvidaste tu contraseña?", + "Full name": "Nombre completo", + "Grant": "Conceder", + "Hide password": "Ocultar contraseña", + "Hide recovery codes": "Ocultar códigos de recuperación", + "Insert :emoji": "Insertar :emoji", + "JPG, PNG, or WebP up to 2 MB.": "JPG, PNG o WebP de hasta 2 MB.", + "Light": "Claro", + "LIVE": "EN VIVO", + "LIVE - :count viewers": "EN VIVO - :count espectadores", + "Live": "En vivo", + "Live broadcasts": "Transmisiones en vivo", + "Live channels": "Canales en vivo", + "Live directory": "Directorio en vivo", + "Live ingest signal": "Señal de ingesta en vivo", + "Live only": "Solo en vivo", + "Live preview": "Vista previa en vivo", + "Live signal": "Señal en vivo", + "Loading": "Cargando", + "Loading recovery codes": "Cargando códigos de recuperación", + "Log in": "Iniciar sesión", + "Log in to chat": "Inicia sesión para chatear", + "Log in to follow": "Inicia sesión para seguir", + "Log in to your account": "Inicia sesión en tu cuenta", + "Log out": "Cerrar sesión", + "Manage your profile and account settings": "Gestiona tu perfil y la configuración de tu cuenta", + "Manage your two-factor authentication settings": "Gestiona la configuración de tu autenticación de dos factores", + "Message": "Mensaje", + "Message sent to admin.": "Mensaje enviado al administrador.", + "Moderation console": "Consola de moderación", + "More": "Más", + "NEEDS APPROVAL": "REQUIERE APROBACIÓN", + "NEEDS CHANNEL GRANT": "REQUIERE PERMISO DE CANAL", + "Name": "Nombre", + "Navigation menu": "Menú de navegación", + "Never": "Nunca", + "New conversation": "Nueva conversación", + "New password": "Nueva contraseña", + "No active session.": "No hay sesión activa.", + "No broadcasts yet.": "Aún no hay transmisiones.", + "No category": "Sin categoría", + "No channels are live.": "No hay canales en vivo.", + "No conversations yet.": "Aún no hay conversaciones.", + "No messages yet.": "Aún no hay mensajes.", + "No public recordings yet.": "Aún no hay grabaciones públicas.", + "No streams match this view": "Ninguna transmisión coincide con esta vista", + "No support conversations found.": "No se encontraron conversaciones de soporte.", + "No thumbnail": "Sin miniatura", + "No users found.": "No se encontraron usuarios.", + "Nyone Live": "Nyone Live", + "OBS readiness": "Preparación de OBS", + "OBS stream key": "Clave de transmisión de OBS", + "OFFLINE": "SIN CONEXIÓN", + "OPEN": "ABIERTO", + "Offline": "Sin conexión", + "Once your account is deleted, all of its resources and data will also be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "Cuando se elimine tu cuenta, todos sus recursos y datos también se eliminarán permanentemente. Introduce tu contraseña para confirmar que quieres eliminar permanentemente tu cuenta.", + "Open": "Abrir", + "Open channel": "Abrir canal", + "Open emoji picker": "Abrir selector de emojis", + "Open moderation": "Abrir moderación", + "Open studio": "Abrir estudio", + "Open to all": "Abierto para todos", + "Or, return to": "O vuelve a", + "Other": "Otro", + "Owner": "Propietario", + "Password": "Contraseña", + "Password updated.": "Contraseña actualizada.", + "Platform": "Plataforma", + "Please confirm access to your account by entering one of your emergency recovery codes.": "Confirma el acceso a tu cuenta introduciendo uno de tus códigos de recuperación de emergencia.", + "Please enter your new password below": "Introduce tu nueva contraseña a continuación", + "Please proceed with caution, this cannot be undone.": "Procede con cuidado; esto no se puede deshacer.", + "Please verify your email address by clicking on the link we just emailed to you.": "Verifica tu dirección de correo electrónico haciendo clic en el enlace que acabamos de enviarte.", + "Prepare": "Preparar", + "Prepare broadcasts, secure your stream key, and monitor the current session.": "Prepara transmisiones, protege tu clave de transmisión y supervisa la sesión actual.", + "Prepared broadcast": "Transmisión preparada", + "Private stream key": "Clave de transmisión privada", + "Processing recording": "Procesando grabación", + "Profile": "Perfil", + "Profile information": "Información del perfil", + "Profile settings": "Configuración del perfil", + "Profile updated.": "Perfil actualizado.", + "Public channels broadcasting now": "Canales públicos transmitiendo ahora", + "Public metadata used in the directory and watch page.": "Metadatos públicos usados en el directorio y la página de visualización.", + "READY": "LISTO", + "Recent VODs": "VOD recientes", + "Recent broadcasts": "Transmisiones recientes", + "Recent channels": "Canales recientes", + "Recent recordings remain available below when the creator publishes VODs.": "Las grabaciones recientes estarán disponibles abajo cuando el creador publique VOD.", + "Record VOD": "Grabar VOD", + "Recording enabled": "Grabación activada", + "Recovery code": "Código de recuperación", + "Recovery codes": "Códigos de recuperación", + "Recovery codes let you regain access if you lose your 2FA device. Store them in a secure password manager.": "Los códigos de recuperación te permiten recuperar el acceso si pierdes tu dispositivo 2FA. Guárdalos en un gestor de contraseñas seguro.", + "Regenerate codes": "Regenerar códigos", + "Register": "Registrarse", + "Remember me": "Recordarme", + "Remove grant": "Quitar permiso", + "Reopen": "Reabrir", + "Reopen the conversation to reply.": "Reabre la conversación para responder.", + "Reopen this conversation before replying.": "Reabre esta conversación antes de responder.", + "Reply": "Responder", + "Reply sent.": "Respuesta enviada.", + "Resend verification email": "Reenviar correo de verificación", + "Reset password": "Restablecer contraseña", + "Restore": "Restaurar", + "Restore channel?": "¿Restaurar canal?", + "Review live streams, suspended states, users, and recordings.": "Revisa transmisiones en vivo, estados suspendidos, usuarios y grabaciones.", + "Rotate immediately if it was exposed.": "Rótala de inmediato si fue expuesta.", + "Rotate key": "Rotar clave", + "Rotate the key to reveal it once.": "Rota la clave para mostrarla una vez.", + "Save": "Guardar", + "Save channel": "Guardar canal", + "Save password": "Guardar contraseña", + "Search live channels": "Buscar canales en vivo", + "Security": "Seguridad", + "Security settings": "Configuración de seguridad", + "Send": "Enviar", + "Send a message": "Enviar un mensaje", + "Send message": "Enviar mensaje", + "Send reply": "Enviar respuesta", + "Server": "Servidor", + "Settings": "Configuración", + "Show password": "Mostrar contraseña", + "Sidebar": "Barra lateral", + "Sign up": "Registrarse", + "Slug": "Slug", + "Something went wrong.": "Algo salió mal.", + "Start OBS to push the session live.": "Inicia OBS para poner la sesión en vivo.", + "Start a channel": "Crear un canal", + "Stop": "Detener", + "Stop broadcast": "Detener transmisión", + "Stop broadcast?": "¿Detener transmisión?", + "Stop live broadcast?": "¿Detener transmisión en vivo?", + "Stream key": "Clave de transmisión", + "Stream key rotated. Update OBS before going live again.": "Clave de transmisión rotada. Actualiza OBS antes de volver a transmitir en vivo.", + "Stream key used": "Clave de transmisión usada", + "Stream keys can publish to this channel. Rotate immediately if it was exposed.": "Las claves de transmisión pueden publicar en este canal. Rótalas de inmediato si fueron expuestas.", + "Stream title": "Título de la transmisión", + "Studio": "Estudio", + "Subject": "Asunto", + "Suggestion": "Sugerencia", + "Support": "Soporte", + "Support inbox": "Bandeja de soporte", + "SUSPENDED": "SUSPENDIDO", + "Suspend": "Suspender", + "Suspend channel?": "¿Suspender canal?", + "System": "Sistema", + "The channel will be hidden from public browsing and watch pages.": "El canal se ocultará de la navegación pública y de las páginas de visualización.", + "The channel will be visible again if the owning account is active.": "El canal volverá a estar visible si la cuenta propietaria está activa.", + "The live floor is quiet": "No hay actividad en vivo", + "The live session will end immediately and the channel will move offline.": "La sesión en vivo terminará de inmediato y el canal pasará a estar sin conexión.", + "This account already owns a channel.": "Esta cuenta ya tiene un canal.", + "This account can browse and follow channels, but cannot create a channel yet.": "Esta cuenta puede explorar y seguir canales, pero todavía no puede crear un canal.", + "This conversation is closed.": "Esta conversación está cerrada.", + "This ends the live session and clears the channel's live state. Viewers will see the offline screen.": "Esto finaliza la sesión en vivo y borra el estado en vivo del canal. Los espectadores verán la pantalla sin conexión.", + "This key is shown once. Store it in OBS before leaving this page.": "Esta clave se muestra una sola vez. Guárdala en OBS antes de salir de esta página.", + "To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app": "Para terminar de activar la autenticación de dos factores, escanea el código QR o introduce la clave de configuración en tu aplicación de autenticación", + "Toggle sidebar": "Alternar barra lateral", + "Two-factor authentication": "Autenticación de dos factores", + "Two-factor authentication enabled": "Autenticación de dos factores activada", + "Uncategorized": "Sin categoría", + "Unsupported attachment type. Upload media, PDF, text/data, Office, OpenDocument, or archive files.": "Tipo de archivo adjunto no compatible. Sube archivos multimedia, PDF, texto/datos, Office, OpenDocument o archivos comprimidos.", + "Update password": "Actualizar contraseña", + "Update your account's appearance settings": "Actualiza la configuración de apariencia de tu cuenta", + "Update your name, email address, and avatar": "Actualiza tu nombre, dirección de correo electrónico y avatar", + "Use a 16:9 JPG, PNG, or WebP up to 4 MB.": "Usa un JPG, PNG o WebP 16:9 de hasta 4 MB.", + "USER": "USUARIO", + "User": "Usuario", + "User can create a channel.": "El usuario puede crear un canal.", + "User channel creation access removed.": "Se quitó el acceso del usuario para crear canales.", + "User suspended.": "Usuario suspendido.", + "Users": "Usuarios", + "Verify email": "Verificar correo electrónico", + "View channel": "Ver canal", + "View recovery codes": "Ver códigos de recuperación", + "Viewers": "Espectadores", + "VOD deleted.": "VOD eliminado.", + "VOD ready": "VOD listo", + "VODs": "VOD", + "Warning": "Advertencia", + "Watch": "Ver", + "Watch VOD": "Ver VOD", + "When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.": "Cuando actives la autenticación de dos factores, se te pedirá un PIN seguro durante el inicio de sesión. Puedes obtener este PIN desde una aplicación compatible con TOTP en tu teléfono.", + "You": "Tú", + "YOU": "TÚ", + "You will be prompted for a secure, random pin during login, which you can retrieve from the TOTP-supported application on your phone.": "Se te pedirá un PIN seguro y aleatorio durante el inicio de sesión, que puedes obtener desde la aplicación compatible con TOTP en tu teléfono.", + "Your email address is unverified.": "Tu dirección de correo electrónico no está verificada.", + "above.": "arriba.", + "breadcrumb": "ruta de navegación", + "email@example.com": "email@example.com", + "later": "más tarde", + "live only": "solo en vivo", + "log in": "iniciar sesión", + "login using a recovery code": "iniciar sesión con un código de recuperación", + "login using an authentication code": "iniciar sesión con un código de autenticación", + "nyone-live": "nyone-live", + "or you can": "o puedes", + "or, enter the code manually": "o introduce el código manualmente", + "pending": "pendiente", + "recording": "grabando", + "Arabic": "Árabe", + "Change language": "Cambiar idioma", + "Choose the language used across Nyone.": "Elige el idioma usado en Nyone.", + "English": "Inglés", + "Interface language": "Idioma de la interfaz", + "Language": "Idioma", + "Persian": "Persa", + "Spanish": "Español", + "unknown": "desconocido" +} diff --git a/lang/es/auth.php b/lang/es/auth.php new file mode 100644 index 0000000..35fb6b9 --- /dev/null +++ b/lang/es/auth.php @@ -0,0 +1,20 @@ + 'Estas credenciales no coinciden con nuestros registros.', + 'password' => 'La contraseña proporcionada es incorrecta.', + 'throttle' => 'Demasiados intentos de inicio de sesión. Inténtalo de nuevo en :seconds segundos.', + +]; diff --git a/lang/es/pagination.php b/lang/es/pagination.php new file mode 100644 index 0000000..f8f044e --- /dev/null +++ b/lang/es/pagination.php @@ -0,0 +1,19 @@ + '« Anterior', + 'next' => 'Siguiente »', + +]; diff --git a/lang/es/passwords.php b/lang/es/passwords.php new file mode 100644 index 0000000..51e061e --- /dev/null +++ b/lang/es/passwords.php @@ -0,0 +1,22 @@ + 'Tu contraseña ha sido restablecida.', + 'sent' => 'Te hemos enviado por correo electrónico el enlace para restablecer tu contraseña.', + 'throttled' => 'Espera antes de intentarlo de nuevo.', + 'token' => 'Este token de restablecimiento de contraseña no es válido.', + 'user' => 'No encontramos ningún usuario con esa dirección de correo electrónico.', + +]; diff --git a/lang/es/validation.php b/lang/es/validation.php new file mode 100644 index 0000000..340c5cd --- /dev/null +++ b/lang/es/validation.php @@ -0,0 +1,200 @@ + 'El campo :attribute debe ser aceptado.', + 'accepted_if' => 'El campo :attribute debe ser aceptado cuando :other es :value.', + 'active_url' => 'El campo :attribute debe ser una URL válida.', + 'after' => 'El campo :attribute debe ser una fecha posterior a :date.', + 'after_or_equal' => 'El campo :attribute debe ser una fecha posterior o igual a :date.', + 'alpha' => 'El campo :attribute solo debe contener letras.', + 'alpha_dash' => 'El campo :attribute solo debe contener letras, números, guiones y guiones bajos.', + 'alpha_num' => 'El campo :attribute solo debe contener letras y números.', + 'any_of' => 'El campo :attribute no es válido.', + 'array' => 'El campo :attribute debe ser un arreglo.', + 'ascii' => 'El campo :attribute solo debe contener caracteres alfanuméricos y símbolos de un solo byte.', + 'before' => 'El campo :attribute debe ser una fecha anterior a :date.', + 'before_or_equal' => 'El campo :attribute debe ser una fecha anterior o igual a :date.', + 'between' => [ + 'array' => 'El campo :attribute debe tener entre :min y :max elementos.', + 'file' => 'El campo :attribute debe tener entre :min y :max kilobytes.', + 'numeric' => 'El campo :attribute debe estar entre :min y :max.', + 'string' => 'El campo :attribute debe tener entre :min y :max caracteres.', + ], + 'boolean' => 'El campo :attribute debe ser verdadero o falso.', + 'can' => 'El campo :attribute contiene un valor no autorizado.', + 'confirmed' => 'La confirmación del campo :attribute no coincide.', + 'contains' => 'Al campo :attribute le falta un valor requerido.', + 'current_password' => 'La contraseña es incorrecta.', + 'date' => 'El campo :attribute debe ser una fecha válida.', + 'date_equals' => 'El campo :attribute debe ser una fecha igual a :date.', + 'date_format' => 'El campo :attribute debe coincidir con el formato :format.', + 'decimal' => 'El campo :attribute debe tener :decimal decimales.', + 'declined' => 'El campo :attribute debe ser rechazado.', + 'declined_if' => 'El campo :attribute debe ser rechazado cuando :other es :value.', + 'different' => 'El campo :attribute y :other deben ser diferentes.', + 'digits' => 'El campo :attribute debe tener :digits dígitos.', + 'digits_between' => 'El campo :attribute debe tener entre :min y :max dígitos.', + 'dimensions' => 'El campo :attribute tiene dimensiones de imagen no válidas.', + 'distinct' => 'El campo :attribute tiene un valor duplicado.', + 'doesnt_contain' => 'El campo :attribute no debe contener ninguno de los siguientes valores: :values.', + 'doesnt_end_with' => 'El campo :attribute no debe terminar con uno de los siguientes valores: :values.', + 'doesnt_start_with' => 'El campo :attribute no debe comenzar con uno de los siguientes valores: :values.', + 'email' => 'El campo :attribute debe ser una dirección de correo electrónico válida.', + 'encoding' => 'El campo :attribute debe estar codificado en :encoding.', + 'ends_with' => 'El campo :attribute debe terminar con uno de los siguientes valores: :values.', + 'enum' => 'El :attribute seleccionado no es válido.', + 'exists' => 'El :attribute seleccionado no es válido.', + 'extensions' => 'El campo :attribute debe tener una de las siguientes extensiones: :values.', + 'file' => 'El campo :attribute debe ser un archivo.', + 'filled' => 'El campo :attribute debe tener un valor.', + 'gt' => [ + 'array' => 'El campo :attribute debe tener más de :value elementos.', + 'file' => 'El campo :attribute debe ser mayor que :value kilobytes.', + 'numeric' => 'El campo :attribute debe ser mayor que :value.', + 'string' => 'El campo :attribute debe tener más de :value caracteres.', + ], + 'gte' => [ + 'array' => 'El campo :attribute debe tener :value elementos o más.', + 'file' => 'El campo :attribute debe ser mayor o igual que :value kilobytes.', + 'numeric' => 'El campo :attribute debe ser mayor o igual que :value.', + 'string' => 'El campo :attribute debe tener :value caracteres o más.', + ], + 'hex_color' => 'El campo :attribute debe ser un color hexadecimal válido.', + 'image' => 'El campo :attribute debe ser una imagen.', + 'in' => 'El :attribute seleccionado no es válido.', + 'in_array' => 'El campo :attribute debe existir en :other.', + 'in_array_keys' => 'El campo :attribute debe contener al menos una de las siguientes claves: :values.', + 'integer' => 'El campo :attribute debe ser un entero.', + 'ip' => 'El campo :attribute debe ser una dirección IP válida.', + 'ipv4' => 'El campo :attribute debe ser una dirección IPv4 válida.', + 'ipv6' => 'El campo :attribute debe ser una dirección IPv6 válida.', + 'json' => 'El campo :attribute debe ser una cadena JSON válida.', + 'list' => 'El campo :attribute debe ser una lista.', + 'lowercase' => 'El campo :attribute debe estar en minúsculas.', + 'lt' => [ + 'array' => 'El campo :attribute debe tener menos de :value elementos.', + 'file' => 'El campo :attribute debe ser menor que :value kilobytes.', + 'numeric' => 'El campo :attribute debe ser menor que :value.', + 'string' => 'El campo :attribute debe tener menos de :value caracteres.', + ], + 'lte' => [ + 'array' => 'El campo :attribute no debe tener más de :value elementos.', + 'file' => 'El campo :attribute debe ser menor o igual que :value kilobytes.', + 'numeric' => 'El campo :attribute debe ser menor o igual que :value.', + 'string' => 'El campo :attribute debe tener :value caracteres o menos.', + ], + 'mac_address' => 'El campo :attribute debe ser una dirección MAC válida.', + 'max' => [ + 'array' => 'El campo :attribute no debe tener más de :max elementos.', + 'file' => 'El campo :attribute no debe ser mayor que :max kilobytes.', + 'numeric' => 'El campo :attribute no debe ser mayor que :max.', + 'string' => 'El campo :attribute no debe tener más de :max caracteres.', + ], + 'max_digits' => 'El campo :attribute no debe tener más de :max dígitos.', + 'mimes' => 'El campo :attribute debe ser un archivo de tipo: :values.', + 'mimetypes' => 'El campo :attribute debe ser un archivo de tipo: :values.', + 'min' => [ + 'array' => 'El campo :attribute debe tener al menos :min elementos.', + 'file' => 'El campo :attribute debe tener al menos :min kilobytes.', + 'numeric' => 'El campo :attribute debe ser al menos :min.', + 'string' => 'El campo :attribute debe tener al menos :min caracteres.', + ], + 'min_digits' => 'El campo :attribute debe tener al menos :min dígitos.', + 'missing' => 'El campo :attribute debe faltar.', + 'missing_if' => 'El campo :attribute debe faltar cuando :other es :value.', + 'missing_unless' => 'El campo :attribute debe faltar a menos que :other sea :value.', + 'missing_with' => 'El campo :attribute debe faltar cuando :values está presente.', + 'missing_with_all' => 'El campo :attribute debe faltar cuando :values están presentes.', + 'multiple_of' => 'El campo :attribute debe ser múltiplo de :value.', + 'not_in' => 'El :attribute seleccionado no es válido.', + 'not_regex' => 'El formato del campo :attribute no es válido.', + 'numeric' => 'El campo :attribute debe ser un número.', + 'password' => [ + 'letters' => 'El campo :attribute debe contener al menos una letra.', + 'mixed' => 'El campo :attribute debe contener al menos una letra mayúscula y una minúscula.', + 'numbers' => 'El campo :attribute debe contener al menos un número.', + 'symbols' => 'El campo :attribute debe contener al menos un símbolo.', + 'uncompromised' => 'El :attribute proporcionado apareció en una filtración de datos. Elige otro :attribute.', + ], + 'present' => 'El campo :attribute debe estar presente.', + 'present_if' => 'El campo :attribute debe estar presente cuando :other es :value.', + 'present_unless' => 'El campo :attribute debe estar presente a menos que :other sea :value.', + 'present_with' => 'El campo :attribute debe estar presente cuando :values está presente.', + 'present_with_all' => 'El campo :attribute debe estar presente cuando :values están presentes.', + 'prohibited' => 'El campo :attribute está prohibido.', + 'prohibited_if' => 'El campo :attribute está prohibido cuando :other es :value.', + 'prohibited_if_accepted' => 'El campo :attribute está prohibido cuando :other está aceptado.', + 'prohibited_if_declined' => 'El campo :attribute está prohibido cuando :other está rechazado.', + 'prohibited_unless' => 'El campo :attribute está prohibido a menos que :other esté en :values.', + 'prohibits' => 'El campo :attribute impide que :other esté presente.', + 'regex' => 'El formato del campo :attribute no es válido.', + 'required' => 'El campo :attribute es obligatorio.', + 'required_array_keys' => 'El campo :attribute debe contener entradas para: :values.', + 'required_if' => 'El campo :attribute es obligatorio cuando :other es :value.', + 'required_if_accepted' => 'El campo :attribute es obligatorio cuando :other está aceptado.', + 'required_if_declined' => 'El campo :attribute es obligatorio cuando :other está rechazado.', + 'required_unless' => 'El campo :attribute es obligatorio a menos que :other esté en :values.', + 'required_with' => 'El campo :attribute es obligatorio cuando :values está presente.', + 'required_with_all' => 'El campo :attribute es obligatorio cuando :values están presentes.', + 'required_without' => 'El campo :attribute es obligatorio cuando :values no está presente.', + 'required_without_all' => 'El campo :attribute es obligatorio cuando ninguno de :values está presente.', + 'same' => 'El campo :attribute debe coincidir con :other.', + 'size' => [ + 'array' => 'El campo :attribute debe contener :size elementos.', + 'file' => 'El campo :attribute debe tener :size kilobytes.', + 'numeric' => 'El campo :attribute debe ser :size.', + 'string' => 'El campo :attribute debe tener :size caracteres.', + ], + 'starts_with' => 'El campo :attribute debe comenzar con uno de los siguientes valores: :values.', + 'string' => 'El campo :attribute debe ser una cadena.', + 'timezone' => 'El campo :attribute debe ser una zona horaria válida.', + 'unique' => 'El valor de :attribute ya está en uso.', + 'uploaded' => 'No se pudo subir :attribute.', + 'uppercase' => 'El campo :attribute debe estar en mayúsculas.', + 'url' => 'El campo :attribute debe ser una URL válida.', + 'ulid' => 'El campo :attribute debe ser un ULID válido.', + 'uuid' => 'El campo :attribute debe ser un UUID válido.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'mensaje-personalizado', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +]; diff --git a/lang/fa.json b/lang/fa.json new file mode 100644 index 0000000..10629b2 --- /dev/null +++ b/lang/fa.json @@ -0,0 +1,333 @@ +{ + "2FA recovery codes": "کدهای بازیابی 2FA", + ":channel by :owner": ":channel توسط :owner", + ":channel is live now with :count viewers.": ":channel اکنون با :count بیننده زنده است.", + ":channel is offline": ":channel آفلاین است", + ":count active broadcasts": ":count پخش فعال", + ":count available": ":count موجود", + ":count followers": ":count دنبال‌کننده", + ":count messages": ":count پیام", + ":count rows": ":count ردیف", + ":count threads": ":count گفتگو", + ":count viewers": ":count بیننده", + ":status - :mode": ":status - :mode", + ":status - :mode - VOD ready": ":status - :mode - VOD آماده", + "@:slug owned by :owner": "@:slug متعلق به :owner است", + "A new verification link has been sent to the email address you provided during registration.": "یک لینک تأیید جدید به آدرس ایمیلی که هنگام ثبت‌نام وارد کردید ارسال شد.", + "A new verification link has been sent to your email address.": "یک لینک تأیید جدید به آدرس ایمیل شما ارسال شد.", + "ADMIN": "مدیر", + "Admin": "مدیر", + "All categories": "همه دسته‌بندی‌ها", + "All verified users can create channels.": "همه کاربران تأییدشده می‌توانند کانال ایجاد کنند.", + "Already have an account?": "از قبل حساب دارید؟", + "An admin can grant creator access from the admin console.": "مدیر می‌تواند از کنسول مدیریت دسترسی سازنده را اعطا کند.", + "An admin must enable channel creation for this account.": "مدیر باید ایجاد کانال را برای این حساب فعال کند.", + "Appearance": "ظاهر", + "Appearance settings": "تنظیمات ظاهر", + "Approval only": "فقط با تأیید", + "Are you sure you want to delete your account?": "آیا مطمئن هستید که می‌خواهید حساب خود را حذف کنید؟", + "Attach up to 3 files, 200 MB each.": "حداکثر ۳ فایل، هرکدام تا ۲۰۰ مگابایت، پیوست کنید.", + "Attachment upload failed. Try the file again or choose a different file.": "بارگذاری پیوست ناموفق بود. فایل را دوباره امتحان کنید یا فایل دیگری انتخاب کنید.", + "Attachments": "پیوست‌ها", + "Attachments must be 200 MB or smaller.": "پیوست‌ها باید ۲۰۰ مگابایت یا کمتر باشند.", + "Authentication code": "کد احراز هویت", + "Avatar": "آواتار", + "Back": "بازگشت", + "Browse active channels by category or title.": "کانال‌های فعال را بر اساس دسته‌بندی یا عنوان مرور کنید.", + "Broadcast ended.": "پخش پایان یافت.", + "Broadcast is ready. Start streaming from OBS.": "پخش آماده است. از OBS شروع به پخش کنید.", + "Broadcast prep": "آماده‌سازی پخش", + "Broadcast state": "وضعیت پخش", + "Broadcast stopped.": "پخش متوقف شد.", + "Bug report": "گزارش خطا", + "Bug reports, suggestions, channel requests, and account questions.": "گزارش خطا، پیشنهادها، درخواست‌های کانال و پرسش‌های حساب.", + "Building Nyone live": "در حال ساخت Nyone Live", + "CAN CREATE": "می‌تواند ایجاد کند", + "CAN CREATE CHANNEL": "می‌تواند کانال ایجاد کند", + "CLOSED": "بسته", + "Cancel": "لغو", + "Categories": "دسته‌بندی‌ها", + "Category": "دسته‌بندی", + "Channel": "کانال", + "Channel created. Save the stream key now; it will not be shown again.": "کانال ایجاد شد. کلید پخش را اکنون ذخیره کنید؛ دوباره نمایش داده نخواهد شد.", + "Channel creation access": "دسترسی ایجاد کانال", + "Channel creation now requires admin approval.": "ایجاد کانال اکنون به تأیید مدیر نیاز دارد.", + "Channel creation requires approval": "ایجاد کانال نیاز به تأیید دارد", + "Channel is offline": "کانال آفلاین است", + "Channel metadata": "فراداده کانال", + "Channel request": "درخواست کانال", + "Channel restored.": "کانال بازیابی شد.", + "Channel setup": "راه‌اندازی کانال", + "Channel suspended.": "کانال تعلیق شد.", + "Channel updated.": "کانال به‌روزرسانی شد.", + "Chat": "گفتگو", + "Chat appears when the channel is live.": "گفتگو زمانی نمایش داده می‌شود که کانال زنده باشد.", + "Chat is available only while the channel is live.": "گفتگو فقط هنگام زنده بودن کانال در دسترس است.", + "Clear search or switch categories.": "جستجو را پاک کنید یا دسته‌بندی را تغییر دهید.", + "Click here to resend the verification email.": "برای ارسال دوباره ایمیل تأیید اینجا کلیک کنید.", + "Close": "بستن", + "Closed": "بسته", + "Confirm": "تأیید", + "Confirm password": "تأیید رمز عبور", + "Confirm your password": "رمز عبور خود را تأیید کنید", + "Contact": "تماس", + "Contact admin": "تماس با مدیر", + "Contact threads from users and channel request notes.": "گفتگوهای تماس کاربران و یادداشت‌های درخواست کانال.", + "Continue": "ادامه", + "Continue setup": "ادامه راه‌اندازی", + "Control who can create a creator channel.": "کنترل کنید چه کسی می‌تواند کانال سازنده ایجاد کند.", + "Conversations": "گفتگوها", + "Create a pending session before starting OBS.": "قبل از شروع OBS یک نشست در انتظار ایجاد کنید.", + "Create account": "ایجاد حساب", + "Create an account": "ایجاد یک حساب", + "Create channel": "ایجاد کانال", + "Create your channel": "کانال خود را ایجاد کنید", + "Creator Studio": "استودیوی سازنده", + "Creator access grants stay in the moderation console.": "مجوزهای دسترسی سازنده در کنسول مدیریت باقی می‌مانند.", + "Creator grants": "مجوزهای سازنده", + "Creators can prepare a broadcast in the studio and go live from OBS when ready.": "سازندگان می‌توانند پخش را در استودیو آماده کنند و هنگام آماده بودن از OBS زنده شوند.", + "Current password": "رمز عبور فعلی", + "Current session": "نشست فعلی", + "Current viewers": "بینندگان فعلی", + "DEFAULT": "پیش‌فرض", + "Dark": "تیره", + "Delete account": "حذف حساب", + "Delete your account and all of its resources": "حساب خود و همه منابع آن را حذف کنید", + "Deleted user": "کاربر حذف‌شده", + "Description": "توضیحات", + "Disable 2FA": "غیرفعال کردن 2FA", + "Displays the mobile sidebar.": "نوار کناری موبایل را نمایش می‌دهد.", + "Display name": "نام نمایشی", + "Don't have an account?": "حساب ندارید؟", + "Each account can own one public channel in this version.": "در این نسخه هر حساب می‌تواند یک کانال عمومی داشته باشد.", + "Each recovery code can be used once to access your account and will be removed after use. If you need more, click": "هر کد بازیابی فقط یک‌بار برای دسترسی به حساب شما قابل استفاده است و پس از استفاده حذف می‌شود. اگر به کدهای بیشتری نیاز دارید، کلیک کنید", + "Email": "ایمیل", + "Email address": "آدرس ایمیل", + "Email password reset link": "ارسال لینک بازنشانی رمز عبور", + "Email verification": "تأیید ایمیل", + "Enable 2FA": "فعال کردن 2FA", + "Enable two-factor authentication": "فعال کردن احراز هویت دومرحله‌ای", + "Ensure your account is using a long, random password to stay secure": "برای امنیت بیشتر مطمئن شوید حساب شما از رمز عبور طولانی و تصادفی استفاده می‌کند", + "Enter recovery code": "کد بازیابی را وارد کنید", + "Enter the 6-digit code from your authenticator app": "کد ۶ رقمی برنامه احراز هویت خود را وارد کنید", + "Enter the authentication code provided by your authenticator application.": "کد احراز هویتی را که برنامه احراز هویت ارائه کرده وارد کنید.", + "Enter your details below to create your account": "برای ایجاد حساب، اطلاعات خود را در زیر وارد کنید", + "Enter your email and password below to log in": "برای ورود، ایمیل و رمز عبور خود را در زیر وارد کنید", + "Enter your email to receive a password reset link": "برای دریافت لینک بازنشانی رمز عبور، ایمیل خود را وارد کنید", + "Expires :date": "انقضا در :date", + "Featured live": "زنده ویژه", + "Finish the current broadcast before creating another.": "قبل از ایجاد پخش دیگر، پخش فعلی را تمام کنید.", + "Finish the current broadcast before preparing another session.": "قبل از آماده‌سازی نشست دیگر، پخش فعلی را تمام کنید.", + "Follow": "دنبال کردن", + "Following": "دنبال می‌کنید", + "Forgot password": "فراموشی رمز عبور", + "Forgot password?": "رمز عبور را فراموش کرده‌اید؟", + "Full name": "نام کامل", + "Grant": "اعطا", + "Hide password": "پنهان کردن رمز عبور", + "Hide recovery codes": "پنهان کردن کدهای بازیابی", + "Insert :emoji": "درج :emoji", + "JPG, PNG, or WebP up to 2 MB.": "JPG، PNG یا WebP تا ۲ مگابایت.", + "Light": "روشن", + "LIVE": "زنده", + "LIVE - :count viewers": "زنده - :count بیننده", + "Live": "زنده", + "Live broadcasts": "پخش‌های زنده", + "Live channels": "کانال‌های زنده", + "Live directory": "فهرست زنده", + "Live ingest signal": "سیگنال ورودی زنده", + "Live only": "فقط زنده", + "Live preview": "پیش‌نمایش زنده", + "Live signal": "سیگنال زنده", + "Loading": "در حال بارگذاری", + "Loading recovery codes": "در حال بارگذاری کدهای بازیابی", + "Log in": "ورود", + "Log in to chat": "برای گفتگو وارد شوید", + "Log in to follow": "برای دنبال کردن وارد شوید", + "Log in to your account": "به حساب خود وارد شوید", + "Log out": "خروج", + "Manage your profile and account settings": "پروفایل و تنظیمات حساب خود را مدیریت کنید", + "Manage your two-factor authentication settings": "تنظیمات احراز هویت دومرحله‌ای خود را مدیریت کنید", + "Message": "پیام", + "Message sent to admin.": "پیام برای مدیر ارسال شد.", + "Moderation console": "کنسول مدیریت", + "More": "بیشتر", + "NEEDS APPROVAL": "نیازمند تأیید", + "NEEDS CHANNEL GRANT": "نیازمند مجوز کانال", + "Name": "نام", + "Navigation menu": "منوی ناوبری", + "Never": "هرگز", + "New conversation": "گفتگوی جدید", + "New password": "رمز عبور جدید", + "No active session.": "نشست فعالی وجود ندارد.", + "No broadcasts yet.": "هنوز پخشی وجود ندارد.", + "No category": "بدون دسته‌بندی", + "No channels are live.": "هیچ کانالی زنده نیست.", + "No conversations yet.": "هنوز گفتگویی وجود ندارد.", + "No messages yet.": "هنوز پیامی وجود ندارد.", + "No public recordings yet.": "هنوز ضبط عمومی وجود ندارد.", + "No streams match this view": "هیچ پخشی با این نما مطابقت ندارد", + "No support conversations found.": "هیچ گفتگوی پشتیبانی یافت نشد.", + "No thumbnail": "بدون تصویر بندانگشتی", + "No users found.": "هیچ کاربری یافت نشد.", + "Nyone Live": "Nyone Live", + "OBS readiness": "آمادگی OBS", + "OBS stream key": "کلید پخش OBS", + "OFFLINE": "آفلاین", + "OPEN": "باز", + "Offline": "آفلاین", + "Once your account is deleted, all of its resources and data will also be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "پس از حذف حساب، همه منابع و داده‌های آن نیز برای همیشه حذف خواهند شد. برای تأیید حذف دائمی حساب، رمز عبور خود را وارد کنید.", + "Open": "باز کردن", + "Open channel": "باز کردن کانال", + "Open emoji picker": "باز کردن انتخابگر ایموجی", + "Open moderation": "باز کردن مدیریت", + "Open studio": "باز کردن استودیو", + "Open to all": "باز برای همه", + "Or, return to": "یا بازگشت به", + "Other": "دیگر", + "Owner": "مالک", + "Password": "رمز عبور", + "Password updated.": "رمز عبور به‌روزرسانی شد.", + "Platform": "پلتفرم", + "Please confirm access to your account by entering one of your emergency recovery codes.": "برای تأیید دسترسی به حساب خود، یکی از کدهای بازیابی اضطراری را وارد کنید.", + "Please enter your new password below": "رمز عبور جدید خود را در زیر وارد کنید", + "Please proceed with caution, this cannot be undone.": "با احتیاط ادامه دهید؛ این کار قابل بازگشت نیست.", + "Please verify your email address by clicking on the link we just emailed to you.": "لطفاً با کلیک روی لینکی که برایتان ایمیل کردیم، آدرس ایمیل خود را تأیید کنید.", + "Prepare": "آماده‌سازی", + "Prepare broadcasts, secure your stream key, and monitor the current session.": "پخش‌ها را آماده کنید، کلید پخش خود را امن نگه دارید و نشست فعلی را پایش کنید.", + "Prepared broadcast": "پخش آماده‌شده", + "Private stream key": "کلید پخش خصوصی", + "Processing recording": "در حال پردازش ضبط", + "Profile": "پروفایل", + "Profile information": "اطلاعات پروفایل", + "Profile settings": "تنظیمات پروفایل", + "Profile updated.": "پروفایل به‌روزرسانی شد.", + "Public channels broadcasting now": "کانال‌های عمومی که اکنون در حال پخش هستند", + "Public metadata used in the directory and watch page.": "فراداده عمومی استفاده‌شده در فهرست و صفحه تماشا.", + "READY": "آماده", + "Recent VODs": "VODهای اخیر", + "Recent broadcasts": "پخش‌های اخیر", + "Recent channels": "کانال‌های اخیر", + "Recent recordings remain available below when the creator publishes VODs.": "وقتی سازنده VODها را منتشر کند، ضبط‌های اخیر در پایین در دسترس می‌مانند.", + "Record VOD": "ضبط VOD", + "Recording enabled": "ضبط فعال است", + "Recovery code": "کد بازیابی", + "Recovery codes": "کدهای بازیابی", + "Recovery codes let you regain access if you lose your 2FA device. Store them in a secure password manager.": "کدهای بازیابی در صورت از دست دادن دستگاه 2FA به شما کمک می‌کنند دوباره دسترسی بگیرید. آن‌ها را در یک مدیر رمز عبور امن نگهداری کنید.", + "Regenerate codes": "بازسازی کدها", + "Register": "ثبت‌نام", + "Remember me": "مرا به خاطر بسپار", + "Remove grant": "حذف مجوز", + "Reopen": "بازگشایی", + "Reopen the conversation to reply.": "برای پاسخ دادن، گفتگو را دوباره باز کنید.", + "Reopen this conversation before replying.": "قبل از پاسخ دادن، این گفتگو را دوباره باز کنید.", + "Reply": "پاسخ", + "Reply sent.": "پاسخ ارسال شد.", + "Resend verification email": "ارسال دوباره ایمیل تأیید", + "Reset password": "بازنشانی رمز عبور", + "Restore": "بازیابی", + "Restore channel?": "کانال بازیابی شود؟", + "Review live streams, suspended states, users, and recordings.": "پخش‌های زنده، وضعیت‌های تعلیق، کاربران و ضبط‌ها را بررسی کنید.", + "Rotate immediately if it was exposed.": "اگر افشا شده است فوراً آن را بچرخانید.", + "Rotate key": "چرخاندن کلید", + "Rotate the key to reveal it once.": "کلید را بچرخانید تا یک‌بار نمایش داده شود.", + "Save": "ذخیره", + "Save channel": "ذخیره کانال", + "Save password": "ذخیره رمز عبور", + "Search live channels": "جستجوی کانال‌های زنده", + "Security": "امنیت", + "Security settings": "تنظیمات امنیتی", + "Send": "ارسال", + "Send a message": "ارسال پیام", + "Send message": "ارسال پیام", + "Send reply": "ارسال پاسخ", + "Server": "سرور", + "Settings": "تنظیمات", + "Show password": "نمایش رمز عبور", + "Sidebar": "نوار کناری", + "Sign up": "ثبت‌نام", + "Slug": "اسلاگ", + "Something went wrong.": "مشکلی پیش آمد.", + "Start OBS to push the session live.": "OBS را شروع کنید تا نشست زنده شود.", + "Start a channel": "شروع یک کانال", + "Stop": "توقف", + "Stop broadcast": "توقف پخش", + "Stop broadcast?": "پخش متوقف شود؟", + "Stop live broadcast?": "پخش زنده متوقف شود؟", + "Stream key": "کلید پخش", + "Stream key rotated. Update OBS before going live again.": "کلید پخش چرخانده شد. پیش از پخش زنده دوباره، OBS را به‌روزرسانی کنید.", + "Stream key used": "کلید پخش استفاده شد", + "Stream keys can publish to this channel. Rotate immediately if it was exposed.": "کلیدهای پخش می‌توانند در این کانال منتشر کنند. اگر افشا شدند فوراً آن‌ها را بچرخانید.", + "Stream title": "عنوان پخش", + "Studio": "استودیو", + "Subject": "موضوع", + "Suggestion": "پیشنهاد", + "Support": "پشتیبانی", + "Support inbox": "صندوق پشتیبانی", + "SUSPENDED": "تعلیق‌شده", + "Suspend": "تعلیق", + "Suspend channel?": "کانال تعلیق شود؟", + "System": "سیستم", + "The channel will be hidden from public browsing and watch pages.": "کانال از مرور عمومی و صفحات تماشا پنهان خواهد شد.", + "The channel will be visible again if the owning account is active.": "اگر حساب مالک فعال باشد، کانال دوباره قابل مشاهده خواهد شد.", + "The live floor is quiet": "فضای زنده آرام است", + "The live session will end immediately and the channel will move offline.": "نشست زنده فوراً پایان می‌یابد و کانال آفلاین می‌شود.", + "This account already owns a channel.": "این حساب از قبل یک کانال دارد.", + "This account can browse and follow channels, but cannot create a channel yet.": "این حساب می‌تواند کانال‌ها را مرور و دنبال کند، اما هنوز نمی‌تواند کانال ایجاد کند.", + "This conversation is closed.": "این گفتگو بسته است.", + "This ends the live session and clears the channel's live state. Viewers will see the offline screen.": "این کار نشست زنده را پایان می‌دهد و وضعیت زنده کانال را پاک می‌کند. بینندگان صفحه آفلاین را خواهند دید.", + "This key is shown once. Store it in OBS before leaving this page.": "این کلید فقط یک‌بار نمایش داده می‌شود. قبل از خروج از این صفحه آن را در OBS ذخیره کنید.", + "To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app": "برای تکمیل فعال‌سازی احراز هویت دومرحله‌ای، کد QR را اسکن کنید یا کلید راه‌اندازی را در برنامه احراز هویت خود وارد کنید", + "Toggle sidebar": "تغییر وضعیت نوار کناری", + "Two-factor authentication": "احراز هویت دومرحله‌ای", + "Two-factor authentication enabled": "احراز هویت دومرحله‌ای فعال شد", + "Uncategorized": "بدون دسته‌بندی", + "Unsupported attachment type. Upload media, PDF, text/data, Office, OpenDocument, or archive files.": "نوع پیوست پشتیبانی نمی‌شود. فایل رسانه‌ای، PDF، متن/داده، Office، OpenDocument یا فایل فشرده بارگذاری کنید.", + "Update password": "به‌روزرسانی رمز عبور", + "Update your account's appearance settings": "تنظیمات ظاهر حساب خود را به‌روزرسانی کنید", + "Update your name, email address, and avatar": "نام، آدرس ایمیل و آواتار خود را به‌روزرسانی کنید", + "Use a 16:9 JPG, PNG, or WebP up to 4 MB.": "از JPG، PNG یا WebP با نسبت ۱۶:۹ تا ۴ مگابایت استفاده کنید.", + "USER": "کاربر", + "User": "کاربر", + "User can create a channel.": "کاربر می‌تواند کانال ایجاد کند.", + "User channel creation access removed.": "دسترسی ایجاد کانال کاربر حذف شد.", + "User suspended.": "کاربر تعلیق شد.", + "Users": "کاربران", + "Verify email": "تأیید ایمیل", + "View channel": "مشاهده کانال", + "View recovery codes": "مشاهده کدهای بازیابی", + "Viewers": "بینندگان", + "VOD deleted.": "VOD حذف شد.", + "VOD ready": "VOD آماده است", + "VODs": "VODها", + "Warning": "هشدار", + "Watch": "تماشا", + "Watch VOD": "تماشای VOD", + "When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.": "وقتی احراز هویت دومرحله‌ای را فعال کنید، هنگام ورود از شما یک PIN امن درخواست می‌شود. این PIN را می‌توانید از یک برنامه پشتیبانی‌کننده TOTP روی تلفن خود دریافت کنید.", + "You": "شما", + "YOU": "شما", + "You will be prompted for a secure, random pin during login, which you can retrieve from the TOTP-supported application on your phone.": "هنگام ورود از شما یک PIN امن و تصادفی درخواست می‌شود که می‌توانید آن را از برنامه پشتیبانی‌کننده TOTP روی تلفن خود دریافت کنید.", + "Your email address is unverified.": "آدرس ایمیل شما تأیید نشده است.", + "above.": "بالا.", + "breadcrumb": "مسیر راهنما", + "email@example.com": "email@example.com", + "later": "بعداً", + "live only": "فقط زنده", + "log in": "ورود", + "login using a recovery code": "ورود با کد بازیابی", + "login using an authentication code": "ورود با کد احراز هویت", + "nyone-live": "nyone-live", + "or you can": "یا می‌توانید", + "or, enter the code manually": "یا کد را دستی وارد کنید", + "pending": "در انتظار", + "recording": "در حال ضبط", + "Arabic": "عربی", + "Change language": "تغییر زبان", + "Choose the language used across Nyone.": "زبان استفاده‌شده در Nyone را انتخاب کنید.", + "English": "انگلیسی", + "Interface language": "زبان رابط کاربری", + "Language": "زبان", + "Persian": "فارسی", + "Spanish": "اسپانیایی", + "unknown": "نامشخص" +} diff --git a/lang/fa/auth.php b/lang/fa/auth.php new file mode 100644 index 0000000..27f335b --- /dev/null +++ b/lang/fa/auth.php @@ -0,0 +1,20 @@ + 'این اطلاعات با سوابق ما مطابقت ندارد.', + 'password' => 'رمز عبور وارد شده نادرست است.', + 'throttle' => 'تلاش‌های ورود بیش از حد مجاز است. لطفاً پس از :seconds ثانیه دوباره تلاش کنید.', + +]; diff --git a/lang/fa/pagination.php b/lang/fa/pagination.php new file mode 100644 index 0000000..e00adbf --- /dev/null +++ b/lang/fa/pagination.php @@ -0,0 +1,19 @@ + '« قبلی', + 'next' => 'بعدی »', + +]; diff --git a/lang/fa/passwords.php b/lang/fa/passwords.php new file mode 100644 index 0000000..58fbd63 --- /dev/null +++ b/lang/fa/passwords.php @@ -0,0 +1,22 @@ + 'رمز عبور شما بازنشانی شد.', + 'sent' => 'لینک بازنشانی رمز عبور را به ایمیل شما ارسال کردیم.', + 'throttled' => 'لطفاً قبل از تلاش دوباره کمی صبر کنید.', + 'token' => 'این توکن بازنشانی رمز عبور نامعتبر است.', + 'user' => 'کاربری با این آدرس ایمیل پیدا نشد.', + +]; diff --git a/lang/fa/validation.php b/lang/fa/validation.php new file mode 100644 index 0000000..fcaba14 --- /dev/null +++ b/lang/fa/validation.php @@ -0,0 +1,200 @@ + 'فیلد :attribute باید پذیرفته شود.', + 'accepted_if' => 'فیلد :attribute وقتی :other برابر :value است باید پذیرفته شود.', + 'active_url' => 'فیلد :attribute باید یک نشانی اینترنتی معتبر باشد.', + 'after' => 'فیلد :attribute باید تاریخی بعد از :date باشد.', + 'after_or_equal' => 'فیلد :attribute باید تاریخی بعد از یا برابر با :date باشد.', + 'alpha' => 'فیلد :attribute فقط باید شامل حروف باشد.', + 'alpha_dash' => 'فیلد :attribute فقط باید شامل حروف، اعداد، خط تیره و زیرخط باشد.', + 'alpha_num' => 'فیلد :attribute فقط باید شامل حروف و اعداد باشد.', + 'any_of' => 'فیلد :attribute نامعتبر است.', + 'array' => 'فیلد :attribute باید آرایه باشد.', + 'ascii' => 'فیلد :attribute فقط باید شامل حروف، اعداد و نمادهای تک‌بایتی باشد.', + 'before' => 'فیلد :attribute باید تاریخی قبل از :date باشد.', + 'before_or_equal' => 'فیلد :attribute باید تاریخی قبل از یا برابر با :date باشد.', + 'between' => [ + 'array' => 'فیلد :attribute باید بین :min و :max آیتم داشته باشد.', + 'file' => 'فیلد :attribute باید بین :min و :max کیلوبایت باشد.', + 'numeric' => 'فیلد :attribute باید بین :min و :max باشد.', + 'string' => 'فیلد :attribute باید بین :min و :max کاراکتر باشد.', + ], + 'boolean' => 'فیلد :attribute باید درست یا نادرست باشد.', + 'can' => 'فیلد :attribute شامل مقدار غیرمجاز است.', + 'confirmed' => 'تأیید فیلد :attribute مطابقت ندارد.', + 'contains' => 'فیلد :attribute یک مقدار الزامی را ندارد.', + 'current_password' => 'رمز عبور نادرست است.', + 'date' => 'فیلد :attribute باید یک تاریخ معتبر باشد.', + 'date_equals' => 'فیلد :attribute باید تاریخی برابر با :date باشد.', + 'date_format' => 'فیلد :attribute باید با قالب :format مطابقت داشته باشد.', + 'decimal' => 'فیلد :attribute باید :decimal رقم اعشار داشته باشد.', + 'declined' => 'فیلد :attribute باید رد شود.', + 'declined_if' => 'فیلد :attribute وقتی :other برابر :value است باید رد شود.', + 'different' => 'فیلد :attribute و :other باید متفاوت باشند.', + 'digits' => 'فیلد :attribute باید :digits رقم باشد.', + 'digits_between' => 'فیلد :attribute باید بین :min و :max رقم باشد.', + 'dimensions' => 'فیلد :attribute ابعاد تصویر نامعتبر دارد.', + 'distinct' => 'فیلد :attribute مقدار تکراری دارد.', + 'doesnt_contain' => 'فیلد :attribute نباید شامل هیچ‌یک از این مقادیر باشد: :values.', + 'doesnt_end_with' => 'فیلد :attribute نباید با یکی از این مقادیر پایان یابد: :values.', + 'doesnt_start_with' => 'فیلد :attribute نباید با یکی از این مقادیر شروع شود: :values.', + 'email' => 'فیلد :attribute باید یک آدرس ایمیل معتبر باشد.', + 'encoding' => 'فیلد :attribute باید با :encoding کدگذاری شده باشد.', + 'ends_with' => 'فیلد :attribute باید با یکی از این مقادیر پایان یابد: :values.', + 'enum' => ':attribute انتخاب‌شده نامعتبر است.', + 'exists' => ':attribute انتخاب‌شده نامعتبر است.', + 'extensions' => 'فیلد :attribute باید یکی از این پسوندها را داشته باشد: :values.', + 'file' => 'فیلد :attribute باید یک فایل باشد.', + 'filled' => 'فیلد :attribute باید مقدار داشته باشد.', + 'gt' => [ + 'array' => 'فیلد :attribute باید بیشتر از :value آیتم داشته باشد.', + 'file' => 'فیلد :attribute باید بزرگ‌تر از :value کیلوبایت باشد.', + 'numeric' => 'فیلد :attribute باید بزرگ‌تر از :value باشد.', + 'string' => 'فیلد :attribute باید بیشتر از :value کاراکتر باشد.', + ], + 'gte' => [ + 'array' => 'فیلد :attribute باید :value آیتم یا بیشتر داشته باشد.', + 'file' => 'فیلد :attribute باید بزرگ‌تر یا برابر با :value کیلوبایت باشد.', + 'numeric' => 'فیلد :attribute باید بزرگ‌تر یا برابر با :value باشد.', + 'string' => 'فیلد :attribute باید :value کاراکتر یا بیشتر باشد.', + ], + 'hex_color' => 'فیلد :attribute باید یک رنگ هگزادسیمال معتبر باشد.', + 'image' => 'فیلد :attribute باید یک تصویر باشد.', + 'in' => ':attribute انتخاب‌شده نامعتبر است.', + 'in_array' => 'فیلد :attribute باید در :other وجود داشته باشد.', + 'in_array_keys' => 'فیلد :attribute باید حداقل یکی از این کلیدها را داشته باشد: :values.', + 'integer' => 'فیلد :attribute باید یک عدد صحیح باشد.', + 'ip' => 'فیلد :attribute باید یک آدرس IP معتبر باشد.', + 'ipv4' => 'فیلد :attribute باید یک آدرس IPv4 معتبر باشد.', + 'ipv6' => 'فیلد :attribute باید یک آدرس IPv6 معتبر باشد.', + 'json' => 'فیلد :attribute باید یک رشته JSON معتبر باشد.', + 'list' => 'فیلد :attribute باید یک فهرست باشد.', + 'lowercase' => 'فیلد :attribute باید با حروف کوچک باشد.', + 'lt' => [ + 'array' => 'فیلد :attribute باید کمتر از :value آیتم داشته باشد.', + 'file' => 'فیلد :attribute باید کمتر از :value کیلوبایت باشد.', + 'numeric' => 'فیلد :attribute باید کمتر از :value باشد.', + 'string' => 'فیلد :attribute باید کمتر از :value کاراکتر باشد.', + ], + 'lte' => [ + 'array' => 'فیلد :attribute نباید بیشتر از :value آیتم داشته باشد.', + 'file' => 'فیلد :attribute باید کمتر یا برابر با :value کیلوبایت باشد.', + 'numeric' => 'فیلد :attribute باید کمتر یا برابر با :value باشد.', + 'string' => 'فیلد :attribute باید :value کاراکتر یا کمتر باشد.', + ], + 'mac_address' => 'فیلد :attribute باید یک آدرس MAC معتبر باشد.', + 'max' => [ + 'array' => 'فیلد :attribute نباید بیشتر از :max آیتم داشته باشد.', + 'file' => 'فیلد :attribute نباید بزرگ‌تر از :max کیلوبایت باشد.', + 'numeric' => 'فیلد :attribute نباید بزرگ‌تر از :max باشد.', + 'string' => 'فیلد :attribute نباید بیشتر از :max کاراکتر باشد.', + ], + 'max_digits' => 'فیلد :attribute نباید بیشتر از :max رقم داشته باشد.', + 'mimes' => 'فیلد :attribute باید فایلی از نوع :values باشد.', + 'mimetypes' => 'فیلد :attribute باید فایلی از نوع :values باشد.', + 'min' => [ + 'array' => 'فیلد :attribute باید حداقل :min آیتم داشته باشد.', + 'file' => 'فیلد :attribute باید حداقل :min کیلوبایت باشد.', + 'numeric' => 'فیلد :attribute باید حداقل :min باشد.', + 'string' => 'فیلد :attribute باید حداقل :min کاراکتر باشد.', + ], + 'min_digits' => 'فیلد :attribute باید حداقل :min رقم داشته باشد.', + 'missing' => 'فیلد :attribute باید وجود نداشته باشد.', + 'missing_if' => 'فیلد :attribute وقتی :other برابر :value است باید وجود نداشته باشد.', + 'missing_unless' => 'فیلد :attribute باید وجود نداشته باشد مگر اینکه :other برابر :value باشد.', + 'missing_with' => 'فیلد :attribute وقتی :values حاضر است باید وجود نداشته باشد.', + 'missing_with_all' => 'فیلد :attribute وقتی :values حاضر هستند باید وجود نداشته باشد.', + 'multiple_of' => 'فیلد :attribute باید مضربی از :value باشد.', + 'not_in' => ':attribute انتخاب‌شده نامعتبر است.', + 'not_regex' => 'قالب فیلد :attribute نامعتبر است.', + 'numeric' => 'فیلد :attribute باید عدد باشد.', + 'password' => [ + 'letters' => 'فیلد :attribute باید حداقل یک حرف داشته باشد.', + 'mixed' => 'فیلد :attribute باید حداقل یک حرف بزرگ و یک حرف کوچک داشته باشد.', + 'numbers' => 'فیلد :attribute باید حداقل یک عدد داشته باشد.', + 'symbols' => 'فیلد :attribute باید حداقل یک نماد داشته باشد.', + 'uncompromised' => ':attribute واردشده در یک نشت داده دیده شده است. لطفاً :attribute دیگری انتخاب کنید.', + ], + 'present' => 'فیلد :attribute باید حاضر باشد.', + 'present_if' => 'فیلد :attribute وقتی :other برابر :value است باید حاضر باشد.', + 'present_unless' => 'فیلد :attribute باید حاضر باشد مگر اینکه :other برابر :value باشد.', + 'present_with' => 'فیلد :attribute وقتی :values حاضر است باید حاضر باشد.', + 'present_with_all' => 'فیلد :attribute وقتی :values حاضر هستند باید حاضر باشد.', + 'prohibited' => 'فیلد :attribute ممنوع است.', + 'prohibited_if' => 'فیلد :attribute وقتی :other برابر :value است ممنوع است.', + 'prohibited_if_accepted' => 'فیلد :attribute وقتی :other پذیرفته شده است ممنوع است.', + 'prohibited_if_declined' => 'فیلد :attribute وقتی :other رد شده است ممنوع است.', + 'prohibited_unless' => 'فیلد :attribute ممنوع است مگر اینکه :other در :values باشد.', + 'prohibits' => 'فیلد :attribute اجازه حضور :other را نمی‌دهد.', + 'regex' => 'قالب فیلد :attribute نامعتبر است.', + 'required' => 'فیلد :attribute الزامی است.', + 'required_array_keys' => 'فیلد :attribute باید ورودی‌هایی برای :values داشته باشد.', + 'required_if' => 'فیلد :attribute وقتی :other برابر :value است الزامی است.', + 'required_if_accepted' => 'فیلد :attribute وقتی :other پذیرفته شده است الزامی است.', + 'required_if_declined' => 'فیلد :attribute وقتی :other رد شده است الزامی است.', + 'required_unless' => 'فیلد :attribute الزامی است مگر اینکه :other در :values باشد.', + 'required_with' => 'فیلد :attribute وقتی :values حاضر است الزامی است.', + 'required_with_all' => 'فیلد :attribute وقتی :values حاضر هستند الزامی است.', + 'required_without' => 'فیلد :attribute وقتی :values حاضر نیست الزامی است.', + 'required_without_all' => 'فیلد :attribute وقتی هیچ‌یک از :values حاضر نیستند الزامی است.', + 'same' => 'فیلد :attribute باید با :other مطابقت داشته باشد.', + 'size' => [ + 'array' => 'فیلد :attribute باید :size آیتم داشته باشد.', + 'file' => 'فیلد :attribute باید :size کیلوبایت باشد.', + 'numeric' => 'فیلد :attribute باید :size باشد.', + 'string' => 'فیلد :attribute باید :size کاراکتر باشد.', + ], + 'starts_with' => 'فیلد :attribute باید با یکی از این مقادیر شروع شود: :values.', + 'string' => 'فیلد :attribute باید رشته باشد.', + 'timezone' => 'فیلد :attribute باید یک منطقه زمانی معتبر باشد.', + 'unique' => 'مقدار :attribute قبلاً استفاده شده است.', + 'uploaded' => 'بارگذاری :attribute ناموفق بود.', + 'uppercase' => 'فیلد :attribute باید با حروف بزرگ باشد.', + 'url' => 'فیلد :attribute باید یک نشانی اینترنتی معتبر باشد.', + 'ulid' => 'فیلد :attribute باید یک ULID معتبر باشد.', + 'uuid' => 'فیلد :attribute باید یک UUID معتبر باشد.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'پیام-سفارشی', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +]; diff --git a/public/fonts/IRANSansWeb/IRANSansWeb.eot b/public/fonts/IRANSansWeb/IRANSansWeb.eot new file mode 100644 index 0000000..349cff7 Binary files /dev/null and b/public/fonts/IRANSansWeb/IRANSansWeb.eot differ diff --git a/public/fonts/IRANSansWeb/IRANSansWeb.svg b/public/fonts/IRANSansWeb/IRANSansWeb.svg new file mode 100644 index 0000000..3b41822 --- /dev/null +++ b/public/fonts/IRANSansWeb/IRANSansWeb.svg @@ -0,0 +1,1575 @@ + + + + + Created by FontForge 20201107 at Tue Sep 5 17:23:22 2017 + By + Copyright (c) 2017 by www.fontiran.com (Moslem Ebrahimi). All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/IRANSansWeb/IRANSansWeb.ttf b/public/fonts/IRANSansWeb/IRANSansWeb.ttf new file mode 100644 index 0000000..8986db0 Binary files /dev/null and b/public/fonts/IRANSansWeb/IRANSansWeb.ttf differ diff --git a/public/fonts/IRANSansWeb/IRANSansWeb.woff b/public/fonts/IRANSansWeb/IRANSansWeb.woff new file mode 100644 index 0000000..d9043a0 Binary files /dev/null and b/public/fonts/IRANSansWeb/IRANSansWeb.woff differ diff --git a/public/fonts/IRANSansWeb/IRANSansWeb.woff2 b/public/fonts/IRANSansWeb/IRANSansWeb.woff2 new file mode 100644 index 0000000..1374f96 Binary files /dev/null and b/public/fonts/IRANSansWeb/IRANSansWeb.woff2 differ diff --git a/public/fonts/IRANSansWeb/stylesheet.css b/public/fonts/IRANSansWeb/stylesheet.css new file mode 100644 index 0000000..e96ad7d --- /dev/null +++ b/public/fonts/IRANSansWeb/stylesheet.css @@ -0,0 +1,14 @@ +@font-face { + font-family: 'IRANSansWeb'; + src: url('IRANSansWeb.eot'); + src: local('IRANSansWeb'), + url('IRANSansWeb.eot?#iefix') format('embedded-opentype'), + url('IRANSansWeb.woff2') format('woff2'), + url('IRANSansWeb.woff') format('woff'), + url('IRANSansWeb.ttf') format('truetype'), + url('IRANSansWeb.svg#IRANSansWeb') format('svg'); + font-weight: normal; + font-style: normal; + font-display: swap; +} + diff --git a/resources/css/app.css b/resources/css/app.css index 9d484db..aa17e81 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -7,6 +7,17 @@ @custom-variant dark (&:is(.dark *)); +@font-face { + font-family: 'IRANSansWeb'; + src: + url('/fonts/IRANSansWeb/IRANSansWeb.woff2') format('woff2'), + url('/fonts/IRANSansWeb/IRANSansWeb.woff') format('woff'), + url('/fonts/IRANSansWeb/IRANSansWeb.ttf') format('truetype'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + @theme { --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, @@ -61,6 +72,13 @@ --color-sidebar-ring: var(--sidebar-ring); } +html[dir='rtl'] { + --font-sans: + 'IRANSansWeb', ui-sans-serif, system-ui, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; +} + :root { --background: oklch(0.982 0.012 315); --foreground: oklch(0.18 0.045 312); diff --git a/resources/js/app.tsx b/resources/js/app.tsx index 504acab..308a0b9 100644 --- a/resources/js/app.tsx +++ b/resources/js/app.tsx @@ -1,10 +1,12 @@ -import { createInertiaApp } from '@inertiajs/react'; +import { createInertiaApp, router } from '@inertiajs/react'; import { Toaster } from '@/components/ui/sonner'; import { TooltipProvider } from '@/components/ui/tooltip'; import { initializeTheme } from '@/hooks/use-appearance'; import AppLayout from '@/layouts/app-layout'; import AuthLayout from '@/layouts/auth-layout'; import SettingsLayout from '@/layouts/settings/layout'; +import { applyDocumentLocalization } from '@/lib/localization'; +import type { Localization } from '@/types'; const appName = import.meta.env.VITE_APP_NAME || 'Nyone'; @@ -38,3 +40,15 @@ createInertiaApp({ // This will set light / dark mode on load... initializeTheme(); + +router.on('navigate', (event) => { + applyDocumentLocalization( + event.detail.page.props.localization as Localization | undefined, + ); +}); + +router.on('success', (event) => { + applyDocumentLocalization( + event.detail.page.props.localization as Localization | undefined, + ); +}); diff --git a/resources/js/components/alert-error.tsx b/resources/js/components/alert-error.tsx index a9eacd3..fe58888 100644 --- a/resources/js/components/alert-error.tsx +++ b/resources/js/components/alert-error.tsx @@ -1,5 +1,6 @@ import { AlertCircleIcon } from 'lucide-react'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { useTranslation } from '@/lib/translations'; export default function AlertError({ errors, @@ -8,10 +9,12 @@ export default function AlertError({ errors: string[]; title?: string; }) { + const { t } = useTranslation(); + return ( - {title || 'Something went wrong.'} + {t(title || 'Something went wrong.')}
    {Array.from(new Set(errors)).map((error, index) => ( diff --git a/resources/js/components/app-header.tsx b/resources/js/components/app-header.tsx index 86ec274..68ac31c 100644 --- a/resources/js/components/app-header.tsx +++ b/resources/js/components/app-header.tsx @@ -10,6 +10,7 @@ import { import AppLogo from '@/components/app-logo'; import AppLogoIcon from '@/components/app-logo-icon'; import { Breadcrumbs } from '@/components/breadcrumbs'; +import { LanguageSwitcher } from '@/components/language-switcher'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; import { @@ -38,6 +39,7 @@ import { import { UserMenuContent } from '@/components/user-menu-content'; import { useCurrentUrl } from '@/hooks/use-current-url'; import { useInitials } from '@/hooks/use-initials'; +import { useTranslation } from '@/lib/translations'; import { cn } from '@/lib/utils'; import { dashboard, home } from '@/routes'; import { dashboard as adminDashboard } from '@/routes/admin'; @@ -53,9 +55,10 @@ const activeItemStyles = 'bg-accent text-accent-foreground'; export function AppHeader({ breadcrumbs = [] }: Props) { const page = usePage(); - const { auth } = page.props; + const { auth, localization } = page.props; const getInitials = useInitials(); const { isCurrentUrl, whenCurrentUrl } = useCurrentUrl(); + const { t } = useTranslation(); const mainNavItems: NavItem[] = [ { title: 'Live', @@ -68,7 +71,7 @@ export function AppHeader({ breadcrumbs = [] }: Props) { icon: LayoutGrid, }, { - title: 'Contact', + title: 'Contact admin', href: supportIndex(), icon: LifeBuoy, }, @@ -98,19 +101,19 @@ export function AppHeader({ breadcrumbs = [] }: Props) { - Navigation menu + {t('Navigation menu')} - +
    @@ -120,12 +123,12 @@ export function AppHeader({ breadcrumbs = [] }: Props) { {item.icon && ( )} - {item.title} + {t(item.title)} ))}
    @@ -140,15 +143,15 @@ export function AppHeader({ breadcrumbs = [] }: Props) { {/* Desktop Navigation */} -
    +
    - + {mainNavItems.map((item, index) => ( {item.icon && ( - + )} - {item.title} + {t(item.title)} {isCurrentUrl(item.href) && (
    @@ -179,8 +182,9 @@ export function AppHeader({ breadcrumbs = [] }: Props) {
    -
    -
    +
    + +
    -
    +
    - Live directory + {t('Live directory')} -

    Live directory

    +

    {t('Live directory')}

    diff --git a/resources/js/components/app-logo.tsx b/resources/js/components/app-logo.tsx index eeb038f..480008f 100644 --- a/resources/js/components/app-logo.tsx +++ b/resources/js/components/app-logo.tsx @@ -6,7 +6,7 @@ export default function AppLogo() {
    -
    +
    Nyone diff --git a/resources/js/components/app-sidebar-header.tsx b/resources/js/components/app-sidebar-header.tsx index 9d53d6c..c098a03 100644 --- a/resources/js/components/app-sidebar-header.tsx +++ b/resources/js/components/app-sidebar-header.tsx @@ -10,7 +10,7 @@ export function AppSidebarHeader({ return (
    - +
    diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index ffee8b5..14b0d6d 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -1,6 +1,7 @@ import { Link, usePage } from '@inertiajs/react'; import { LayoutGrid, LifeBuoy, Radio, Shield } from 'lucide-react'; import AppLogo from '@/components/app-logo'; +import { LanguageSwitcher } from '@/components/language-switcher'; import { NavMain } from '@/components/nav-main'; import { NavUser } from '@/components/nav-user'; import { @@ -19,7 +20,7 @@ import { index as supportIndex } from '@/routes/support'; import type { NavItem } from '@/types'; export function AppSidebar() { - const { auth } = usePage().props; + const { auth, localization } = usePage().props; const mainNavItems: NavItem[] = [ { title: 'Live', @@ -32,7 +33,7 @@ export function AppSidebar() { icon: LayoutGrid, }, { - title: 'Contact', + title: 'Contact admin', href: supportIndex(), icon: LifeBuoy, }, @@ -52,7 +53,11 @@ export function AppSidebar() { } return ( - + @@ -70,6 +75,7 @@ export function AppSidebar() { + diff --git a/resources/js/components/appearance-tabs.tsx b/resources/js/components/appearance-tabs.tsx index b013862..644081c 100644 --- a/resources/js/components/appearance-tabs.tsx +++ b/resources/js/components/appearance-tabs.tsx @@ -3,6 +3,7 @@ import { Monitor, Moon, Sun } from 'lucide-react'; import type { HTMLAttributes } from 'react'; import type { Appearance } from '@/hooks/use-appearance'; import { useAppearance } from '@/hooks/use-appearance'; +import { useTranslation } from '@/lib/translations'; import { cn } from '@/lib/utils'; export default function AppearanceToggleTab({ @@ -10,6 +11,7 @@ export default function AppearanceToggleTab({ ...props }: HTMLAttributes) { const { appearance, updateAppearance } = useAppearance(); + const { t } = useTranslation(); const tabs: { value: Appearance; icon: LucideIcon; label: string }[] = [ { value: 'light', icon: Sun, label: 'Light' }, @@ -36,8 +38,8 @@ export default function AppearanceToggleTab({ : 'text-neutral-500 hover:bg-neutral-200/60 hover:text-black dark:text-neutral-400 dark:hover:bg-neutral-700/60', )} > - - {label} + + {t(label)} ))}
    diff --git a/resources/js/components/breadcrumbs.tsx b/resources/js/components/breadcrumbs.tsx index 3420084..9b49085 100644 --- a/resources/js/components/breadcrumbs.tsx +++ b/resources/js/components/breadcrumbs.tsx @@ -8,6 +8,7 @@ import { BreadcrumbPage, BreadcrumbSeparator, } from '@/components/ui/breadcrumb'; +import { useTranslation } from '@/lib/translations'; import type { BreadcrumbItem as BreadcrumbItemType } from '@/types'; export function Breadcrumbs({ @@ -15,6 +16,8 @@ export function Breadcrumbs({ }: { breadcrumbs: BreadcrumbItemType[]; }) { + const { t } = useTranslation(); + return ( <> {breadcrumbs.length > 0 && ( @@ -28,12 +31,12 @@ export function Breadcrumbs({ {isLast ? ( - {item.title} + {t(item.title)} ) : ( - {item.title} + {t(item.title)} )} diff --git a/resources/js/components/delete-user.tsx b/resources/js/components/delete-user.tsx index 213e76f..a0b2780 100644 --- a/resources/js/components/delete-user.tsx +++ b/resources/js/components/delete-user.tsx @@ -15,9 +15,11 @@ import { DialogTrigger, } from '@/components/ui/dialog'; import { Label } from '@/components/ui/label'; +import { useTranslation } from '@/lib/translations'; export default function DeleteUser() { const passwordInput = useRef(null); + const { t } = useTranslation(); return (
    @@ -28,9 +30,11 @@ export default function DeleteUser() { />
    -

    Warning

    +

    {t('Warning')}

    - Please proceed with caution, this cannot be undone. + {t( + 'Please proceed with caution, this cannot be undone.', + )}

    @@ -45,13 +49,12 @@ export default function DeleteUser() { - Are you sure you want to delete your account? + {t('Are you sure you want to delete your account?')} - Once your account is deleted, all of its resources - and data will also be permanently deleted. Please - enter your password to confirm you would like to - permanently delete your account. + {t( + 'Once your account is deleted, all of its resources and data will also be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.', + )}
    diff --git a/resources/js/components/heading.tsx b/resources/js/components/heading.tsx index 515242d..f457007 100644 --- a/resources/js/components/heading.tsx +++ b/resources/js/components/heading.tsx @@ -1,3 +1,5 @@ +import { useTranslation } from '@/lib/translations'; + export default function Heading({ title, description, @@ -7,6 +9,8 @@ export default function Heading({ description?: string; variant?: 'default' | 'small'; }) { + const { t } = useTranslation(); + return (

    - {title} + {t(title)}

    {description && ( -

    {description}

    +

    + {t(description)} +

    )}
    ); diff --git a/resources/js/components/language-switcher.tsx b/resources/js/components/language-switcher.tsx new file mode 100644 index 0000000..ee947a8 --- /dev/null +++ b/resources/js/components/language-switcher.tsx @@ -0,0 +1,224 @@ +import { router, usePage } from '@inertiajs/react'; +import { Check, Globe2, Languages } from 'lucide-react'; +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { applyDocumentLocalization } from '@/lib/localization'; +import { useTranslation } from '@/lib/translations'; +import { cn } from '@/lib/utils'; +import { update as updateLanguage } from '@/routes/language'; +import type { Localization, LocaleOption } from '@/types'; + +type LanguageSwitcherProps = { + align?: 'center' | 'end' | 'start'; + className?: string; + variant?: 'compact' | 'settings' | 'sidebar'; +}; + +export function LanguageSwitcher({ + align = 'end', + className, + variant = 'compact', +}: LanguageSwitcherProps) { + const { localization } = usePage().props; + const { t } = useTranslation(); + const [pendingLocale, setPendingLocale] = useState(null); + const currentLocale = + localization.locales.find( + (locale) => locale.code === localization.locale, + ) ?? localization.locales[0]; + + if (!currentLocale) { + return null; + } + + const updateLocale = (locale: string): void => { + if (locale === localization.locale || pendingLocale) { + return; + } + + const nextLocale = localization.locales.find( + (option) => option.code === locale, + ); + + if (!nextLocale) { + return; + } + + setPendingLocale(locale); + applyDocumentLocalization({ + direction: nextLocale.direction, + locale: nextLocale.code, + }); + + router + .optimistic((props) => ({ + locale: nextLocale.code, + localization: { + ...(props as { localization: Localization }).localization, + direction: nextLocale.direction, + isRtl: nextLocale.direction === 'rtl', + locale: nextLocale.code, + }, + })) + .post( + updateLanguage.url(), + { locale }, + { + preserveScroll: true, + preserveState: false, + onCancel: () => applyDocumentLocalization(localization), + onError: () => applyDocumentLocalization(localization), + onFinish: () => setPendingLocale(null), + onNetworkError: () => + applyDocumentLocalization(localization), + onSuccess: (page) => + applyDocumentLocalization( + page.props.localization as Localization | undefined, + ), + }, + ); + }; + + if (variant === 'settings') { + return ( +
    +
    + + + +
    +

    + {t('Interface language')} +

    +

    + {t('Choose the language used across Nyone.')} +

    +
    +
    + +
    + {localization.locales.map((locale) => ( + + ))} +
    +
    + ); + } + + return ( + + + + + + + + {t('Language')} + + + {localization.locales.map((locale) => ( + updateLocale(locale.code)} + > + + + + {locale.nativeName} + + + {t(locale.name)} + + + {locale.code === localization.locale && ( + + )} + + ))} + + + ); +} + +type LanguageOptionButtonProps = { + isActive: boolean; + isPending: boolean; + locale: LocaleOption; + onSelect: (locale: string) => void; +}; + +function LanguageOptionButton({ + isActive, + isPending, + locale, + onSelect, +}: LanguageOptionButtonProps) { + const { t } = useTranslation(); + + return ( + + ); +} + +function LocaleBadge({ locale }: { locale: LocaleOption }) { + return ( + + {locale.code} + + ); +} diff --git a/resources/js/components/nav-main.tsx b/resources/js/components/nav-main.tsx index 54095ac..823cc04 100644 --- a/resources/js/components/nav-main.tsx +++ b/resources/js/components/nav-main.tsx @@ -7,25 +7,27 @@ import { SidebarMenuItem, } from '@/components/ui/sidebar'; import { useCurrentUrl } from '@/hooks/use-current-url'; +import { useTranslation } from '@/lib/translations'; import type { NavItem } from '@/types'; export function NavMain({ items = [] }: { items: NavItem[] }) { const { isCurrentUrl } = useCurrentUrl(); + const { t } = useTranslation(); return ( - Platform + {t('Platform')} {items.map((item) => ( {item.icon && } - {item.title} + {t(item.title)} diff --git a/resources/js/components/nav-user.tsx b/resources/js/components/nav-user.tsx index a407239..f16fd2a 100644 --- a/resources/js/components/nav-user.tsx +++ b/resources/js/components/nav-user.tsx @@ -35,7 +35,7 @@ export function NavUser() { data-test="sidebar-menu-button" > - + , 'type'> & { ref?: Ref }) { const [showPassword, setShowPassword] = useState(false); + const { t } = useTranslation(); return (
    {canRegenerateCodes && ( @@ -92,7 +97,7 @@ export default function TwoFactorRecoveryCodes({ disabled={processing} aria-describedby="regenerate-warning" > - Regenerate codes + {t('Regenerate codes')} )} @@ -112,7 +117,7 @@ export default function TwoFactorRecoveryCodes({ ref={codesSectionRef} className="grid gap-1 rounded-lg bg-muted p-4 font-mono text-sm" role="list" - aria-label="Recovery codes" + aria-label={t('Recovery codes')} > {recoveryCodesList.length ? ( recoveryCodesList.map((code, index) => ( @@ -127,7 +132,9 @@ export default function TwoFactorRecoveryCodes({ ) : (
    {Array.from( { length: 8 }, @@ -145,13 +152,13 @@ export default function TwoFactorRecoveryCodes({

    - Each recovery code can be used once to - access your account and will be removed - after use. If you need more, click{' '} + {t( + 'Each recovery code can be used once to access your account and will be removed after use. If you need more, click', + )}{' '} - Regenerate codes + {t('Regenerate codes')} {' '} - above. + {t('above.')}

    diff --git a/resources/js/components/two-factor-setup-modal.tsx b/resources/js/components/two-factor-setup-modal.tsx index a968858..cce5664 100644 --- a/resources/js/components/two-factor-setup-modal.tsx +++ b/resources/js/components/two-factor-setup-modal.tsx @@ -21,6 +21,7 @@ import { Spinner } from '@/components/ui/spinner'; import { useAppearance } from '@/hooks/use-appearance'; import { useClipboard } from '@/hooks/use-clipboard'; import { OTP_MAX_LENGTH } from '@/hooks/use-two-factor-auth'; +import { useTranslation } from '@/lib/translations'; import { confirm } from '@/routes/two-factor'; function GridScanIcon() { @@ -31,7 +32,7 @@ function GridScanIcon() { {Array.from({ length: 5 }, (_, i) => (
    ))}
    @@ -64,6 +65,7 @@ function TwoFactorSetupStep({ }) { const { resolvedAppearance } = useAppearance(); const [copiedText, copy] = useClipboard(); + const { t } = useTranslation(); const IconComponent = copiedText === manualSetupKey ? Check : Copy; return ( @@ -95,7 +97,7 @@ function TwoFactorSetupStep({
    -
    +
    @@ -104,11 +106,11 @@ function TwoFactorSetupStep({
    - or, enter the code manually + {t('or, enter the code manually')}
    -
    +
    {!manualSetupKey ? (
    @@ -124,7 +126,7 @@ function TwoFactorSetupStep({ /> @@ -147,6 +149,7 @@ function TwoFactorVerificationStep({ }) { const [code, setCode] = useState(''); const pinInputContainerRef = useRef(null); + const { t } = useTranslation(); useEffect(() => { setTimeout(() => { @@ -202,7 +205,7 @@ function TwoFactorVerificationStep({ />
    -
    +
    @@ -254,6 +257,7 @@ export default function TwoFactorSetupModal({ }: Props) { const [showVerificationStep, setShowVerificationStep] = useState(false); + const { t } = useTranslation(); const modalConfig = useMemo<{ title: string; @@ -262,29 +266,32 @@ export default function TwoFactorSetupModal({ }>(() => { if (twoFactorEnabled) { return { - title: 'Two-factor authentication enabled', - description: + title: t('Two-factor authentication enabled'), + description: t( 'Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.', - buttonText: 'Close', + ), + buttonText: t('Close'), }; } if (showVerificationStep) { return { - title: 'Verify authentication code', - description: + title: t('Verify authentication code'), + description: t( 'Enter the 6-digit code from your authenticator app', - buttonText: 'Continue', + ), + buttonText: t('Continue'), }; } return { - title: 'Enable two-factor authentication', - description: + title: t('Enable two-factor authentication'), + description: t( 'To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app', - buttonText: 'Continue', + ), + buttonText: t('Continue'), }; - }, [twoFactorEnabled, showVerificationStep]); + }, [showVerificationStep, t, twoFactorEnabled]); const resetModalState = useCallback(() => { setShowVerificationStep(false); diff --git a/resources/js/components/ui/breadcrumb.tsx b/resources/js/components/ui/breadcrumb.tsx index 12a631e..15355a1 100644 --- a/resources/js/components/ui/breadcrumb.tsx +++ b/resources/js/components/ui/breadcrumb.tsx @@ -2,10 +2,13 @@ import { Slot } from "@radix-ui/react-slot" import { ChevronRight, MoreHorizontal } from "lucide-react" import * as React from "react" +import { useTranslation } from "@/lib/translations" import { cn } from "@/lib/utils" function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { - return
    @@ -667,10 +678,11 @@ export default function Dashboard({ > - Stop broadcast? + {t('Stop broadcast?')} - This ends the live session and clears the channel's - live state. Viewers will see the offline screen. + {t( + "This ends the live session and clears the channel's live state. Viewers will see the offline screen.", + )} @@ -709,13 +721,15 @@ function Panel({ description?: string; children: ReactNode; }) { + const { t } = useTranslation(); + return (
    -

    {title}

    +

    {t(title)}

    {description && (

    - {description} + {t(description)}

    )}
    @@ -753,13 +767,15 @@ function CategorySelect({ value: string; onChange: (value: string) => void; }) { + const { t } = useTranslation(); + return ( setQuery(event.target.value) } - placeholder="Search live channels" - className="pl-9 sm:w-72" + placeholder={t('Search live channels')} + className="ps-9 sm:w-72" />