localize settings
This commit is contained in:
parent
351cd5eda9
commit
335c82e2c0
28 changed files with 727 additions and 332 deletions
|
|
@ -1,73 +0,0 @@
|
|||
{
|
||||
"Organisms": {
|
||||
"RoomCommon": {
|
||||
"changed_room_name": " hat den Raum Name geändert"
|
||||
}
|
||||
},
|
||||
"Auth": {
|
||||
"title_login": "Anmelden",
|
||||
"title_register": "Registrieren",
|
||||
"title_reset_password": "Passwort zurücksetzen",
|
||||
"homeserver": "Homeserver",
|
||||
"homeserver_edit": "Bearbeiten",
|
||||
"homeserver_dialog_title": "Homeserver",
|
||||
"homeserver_dialog_desc": "Geben Sie die Adresse des Matrix-Homeservers ein, mit dem Sie sich verbinden möchten.",
|
||||
"homeserver_dialog_placeholder": "example.com",
|
||||
"homeserver_dialog_cancel": "Abbrechen",
|
||||
"homeserver_dialog_confirm": "Weiter",
|
||||
"username_placeholder": "Benutzername",
|
||||
"password_placeholder": "Passwort",
|
||||
"forgot_password": "Passwort vergessen?",
|
||||
"login_button": "Anmelden",
|
||||
"new_here": "Neu hier?",
|
||||
"create_account": "Konto erstellen",
|
||||
"already_have_account": "Bereits ein Konto?",
|
||||
"remember_password": "Passwort wieder eingefallen?",
|
||||
"loading_server": "Server wird gesucht...",
|
||||
"loading_connecting": "Verbindung zu {{url}}...",
|
||||
"loading_auth_flows": "Laden...",
|
||||
"error_server_not_found": "Server konnte nicht gefunden werden.",
|
||||
"error_server_config_invalid": "Verbindung fehlgeschlagen. Die Konfiguration des Servers {{host}} ist ungültig.",
|
||||
"error_server_base_url_invalid": "Verbindung fehlgeschlagen. Ungültige base_url des Servers.",
|
||||
"error_server_unavailable": "Verbindung zum Server konnte nicht hergestellt werden.",
|
||||
"error_auth_flows": "Autorisierungsabläufe konnten nicht geladen werden.",
|
||||
"error_client_unsupported": "Dieser Client unterstützt keine Autorisierung auf dem Server \"{{server}}\".",
|
||||
"error_custom_server_not_allowed": "Anmeldung mit benutzerdefiniertem Server ist nicht erlaubt.",
|
||||
"error_matrix_id_server": "Matrix-ID-Server konnte nicht gefunden werden.",
|
||||
"error_invalid_credentials": "Ungültiger Benutzername oder Passwort.",
|
||||
"error_account_deactivated": "Dieses Konto wurde deaktiviert.",
|
||||
"error_invalid_request": "Anmeldung fehlgeschlagen. Ein Teil der Anfragedaten ist ungültig.",
|
||||
"error_rate_limited": "Zu viele Versuche. Bitte versuchen Sie es später erneut.",
|
||||
"error_unknown": "Anmeldung fehlgeschlagen. Unbekannter Fehler.",
|
||||
"register_disabled": "Die Registrierung ist auf diesem Server deaktiviert.",
|
||||
"register_rate_limited": "Zu viele Versuche. Bitte versuchen Sie es später erneut.",
|
||||
"register_invalid_request": "Ungültige Anfrage. Registrierungsparameter konnten nicht abgerufen werden.",
|
||||
"register_unsupported": "Diese Anwendung unterstützt keine Registrierung auf diesem Server.",
|
||||
"register_username_label": "Benutzername",
|
||||
"register_password_label": "Passwort",
|
||||
"register_confirm_password_label": "Passwort bestätigen",
|
||||
"register_button": "Registrieren",
|
||||
"register_token_label": "Registrierungstoken",
|
||||
"register_token_optional_label": "Registrierungstoken (optional)",
|
||||
"register_email_label": "E-Mail",
|
||||
"register_email_optional_label": "E-Mail (optional)",
|
||||
"register_terms": "Ich akzeptiere die <termsLink>Nutzungsbedingungen</termsLink> des Servers.",
|
||||
"register_error_user_taken": "Dieser Benutzername ist bereits vergeben.",
|
||||
"register_error_user_invalid": "Dieser Benutzername enthält ungültige Zeichen.",
|
||||
"register_error_user_exclusive": "Dieser Benutzername ist reserviert.",
|
||||
"register_error_password_weak": "Schwaches Passwort. Vom Server abgelehnt, bitte wählen Sie ein stärkeres.",
|
||||
"register_error_password_short": "Kurzes Passwort. Vom Server abgelehnt, bitte wählen Sie ein längeres.",
|
||||
"register_error_rate_limited": "Registrierung fehlgeschlagen. Zu viele Anfragen, bitte versuchen Sie es später erneut.",
|
||||
"register_error_forbidden": "Registrierung fehlgeschlagen. Der Server erlaubt keine Registrierung.",
|
||||
"register_error_invalid_request": "Registrierung fehlgeschlagen. Ungültige Anfrage.",
|
||||
"register_error_unknown": "Registrierung fehlgeschlagen. Unbekannter Fehler.",
|
||||
"reset_description": "Der Homeserver <strong>{{server}}</strong> sendet Ihnen eine E-Mail, um Ihr Passwort zurückzusetzen.",
|
||||
"reset_email_label": "E-Mail",
|
||||
"reset_new_password": "Neues Passwort",
|
||||
"reset_confirm_password": "Passwort bestätigen",
|
||||
"reset_button": "Passwort zurücksetzen",
|
||||
"reset_error_fallback": "Passwort konnte nicht zurückgesetzt werden.",
|
||||
"reset_success_message": "Das Passwort wurde erfolgreich zurückgesetzt. Bitte melden Sie sich mit Ihrem neuen Passwort an.",
|
||||
"reset_success_login": "Anmelden"
|
||||
}
|
||||
}
|
||||
|
|
@ -69,5 +69,215 @@
|
|||
"reset_error_fallback": "Failed to reset password.",
|
||||
"reset_success_message": "Password has been reset successfully. Please login with your new password.",
|
||||
"reset_success_login": "Login"
|
||||
},
|
||||
"Settings": {
|
||||
"title": "Settings",
|
||||
"menu_general": "General",
|
||||
"menu_account": "Account",
|
||||
"menu_notifications": "Notifications",
|
||||
"menu_devices": "Devices",
|
||||
"menu_emojis_stickers": "Emojis & Stickers",
|
||||
"menu_developer_tools": "Developer Tools",
|
||||
"menu_about": "About",
|
||||
"logout": "Logout",
|
||||
|
||||
"general_title": "General",
|
||||
"appearance": "Appearance",
|
||||
"system_theme": "System Theme",
|
||||
"system_theme_desc": "Choose between light and dark theme based on system preference.",
|
||||
"light_theme": "Light Theme:",
|
||||
"dark_theme": "Dark Theme:",
|
||||
"theme": "Theme",
|
||||
"theme_desc": "Theme to use when system theme is not enabled.",
|
||||
"monochrome_mode": "Monochrome Mode",
|
||||
"twitter_emoji": "Twitter Emoji",
|
||||
"page_zoom": "Page Zoom",
|
||||
"date_time": "Date & Time",
|
||||
"hour_24": "24-Hour Time Format",
|
||||
"date_format": "Date Format",
|
||||
"custom": "Custom",
|
||||
"formatting": "Formatting",
|
||||
"year": "Year",
|
||||
"two_digit_year": "Two-digit year",
|
||||
"four_digit_year": "Four-digit year",
|
||||
"month": "Month",
|
||||
"the_month": "The month",
|
||||
"two_digit_month": "Two-digit month",
|
||||
"short_month_name": "Short month name",
|
||||
"full_month_name": "Full month name",
|
||||
"day_of_month": "Day of the Month",
|
||||
"day_of_month_val": "Day of the month",
|
||||
"two_digit_day": "Two-digit day of the month",
|
||||
"day_of_week": "Day of the Week",
|
||||
"day_of_week_sunday": "Day of the week (Sunday = 0)",
|
||||
"two_letter_day": "Two-letter day name",
|
||||
"short_day_name": "Short day name",
|
||||
"full_day_name": "Full day name",
|
||||
"save": "Save",
|
||||
"editor": "Editor",
|
||||
"enter_newline": "ENTER for Newline",
|
||||
"enter_newline_desc": "Use {{key}} + ENTER to send message and ENTER for newline.",
|
||||
"markdown": "Markdown Formatting",
|
||||
"hide_activity": "Hide Typing & Read Receipts",
|
||||
"hide_activity_desc": "Turn off both typing status and read receipts to keep your activity private.",
|
||||
"messages": "Messages",
|
||||
"message_layout": "Message Layout",
|
||||
"message_spacing": "Message Spacing",
|
||||
"legacy_username_color": "Legacy Username Color",
|
||||
"hide_membership": "Hide Membership Change",
|
||||
"hide_profile": "Hide Profile Change",
|
||||
"disable_media_auto_load": "Disable Media Auto Load",
|
||||
"url_preview": "Url Preview",
|
||||
"url_preview_encrypted": "Url Preview in Encrypted Room",
|
||||
"show_hidden_events": "Show Hidden Events",
|
||||
|
||||
"account_title": "Account",
|
||||
"profile": "Profile",
|
||||
"avatar": "Avatar",
|
||||
"upload": "Upload",
|
||||
"remove": "Remove",
|
||||
"remove_avatar": "Remove Avatar",
|
||||
"remove_avatar_confirm": "Are you sure you want to remove profile avatar?",
|
||||
"display_name": "Display Name",
|
||||
"matrix_id": "Matrix ID",
|
||||
"copy": "Copy",
|
||||
"contact_info": "Contact Information",
|
||||
"email_address": "Email Address",
|
||||
"email_address_desc": "Email address attached to your account.",
|
||||
"blocked_users": "Blocked Users",
|
||||
"select_user": "Select User",
|
||||
"select_user_desc": "Prevent receiving messages or invites from user by adding their userId.",
|
||||
"block": "Block",
|
||||
"users": "Users",
|
||||
|
||||
"notifications_title": "Notifications",
|
||||
"block_messages": "Block Messages",
|
||||
"block_messages_moved": "This option has been moved to \"Account > Block Users\" section.",
|
||||
"system": "System",
|
||||
"desktop_notifications": "Desktop Notifications",
|
||||
"notif_permission_blocked": "Notification permission is blocked. Please allow notification permission from browser address bar.",
|
||||
"notif_not_supported": "Notifications are not supported by the system.",
|
||||
"notif_show_desktop": "Show desktop notifications when a message arrives.",
|
||||
"enable": "Enable",
|
||||
"notification_sound": "Notification Sound",
|
||||
"notification_sound_desc": "Play sound when a new message arrives.",
|
||||
"email_notification": "Email Notification",
|
||||
"email_no_email": "Your account does not have any email attached.",
|
||||
"email_send_notif": "Send notification to your email.",
|
||||
"email_send_notif_to": "Send notification to your email. (\"{{email}}\")",
|
||||
"unexpected_error": "Unexpected Error!",
|
||||
"all_messages": "All Messages",
|
||||
"badge": "Badge: ",
|
||||
"one_to_one": "1-to-1 Chats",
|
||||
"one_to_one_encrypted": "1-to-1 Chats (Encrypted)",
|
||||
"rooms": "Rooms",
|
||||
"rooms_encrypted": "Rooms (Encrypted)",
|
||||
"special_messages": "Special Messages",
|
||||
"mention_user_id": "Mention User ID (\"{{userId}}\")",
|
||||
"contains_displayname": "Contains Display Name",
|
||||
"contains_displayname_value": "Contains Display Name (\"{{displayName}}\")",
|
||||
"contains_username": "Contains Username (\"{{username}}\")",
|
||||
"mention_room": "Mention @room",
|
||||
"contains_room": "Contains @room",
|
||||
"keyword_messages": "Keyword Messages",
|
||||
"select_keyword": "Select Keyword",
|
||||
"select_keyword_desc": "Set a notification preference for message containing given keyword.",
|
||||
"notif_disable": "Disable",
|
||||
"notif_silent": "Notify Silent",
|
||||
"notif_loud": "Notify Loud",
|
||||
|
||||
"devices_title": "Devices",
|
||||
"security": "Security",
|
||||
"device_verification": "Device Verification",
|
||||
"device_verification_desc": "To verify device identity and grant access to encrypted messages.",
|
||||
"current": "Current",
|
||||
"last_activity": "Last activity: ",
|
||||
"today": "Today",
|
||||
"yesterday": "Yesterday",
|
||||
"device_id": "Device ID: ",
|
||||
"ip_address": "IP Address: ",
|
||||
"device_key": "Device Key: ",
|
||||
"loading": "loading...",
|
||||
"device_name": "Device Name",
|
||||
"device_name_public": "Device names are visible to public.",
|
||||
"cancel": "Cancel",
|
||||
"edit": "Edit",
|
||||
"undo": "Undo",
|
||||
"others": "Others",
|
||||
"device_dashboard": "Device Dashboard",
|
||||
"device_dashboard_desc": "Manage your devices on OIDC dashboard.",
|
||||
"open": "Open",
|
||||
"delete_devices_error": "Failed to logout devices! Please try again. {{message}}",
|
||||
"delete_devices_confirm": "Logout from selected devices. ({{count}} selected)",
|
||||
"delete_devices_unsupported": "Authentication steps to perform this action are not supported by the client.",
|
||||
"unverified": "Unverified",
|
||||
"unverified_count": "{{count}} Unverified",
|
||||
"verified": "Verified",
|
||||
"verify_steps_title": "Steps to verify from another device.",
|
||||
"verify_step_1": "Open your other verified device.",
|
||||
"verify_step_2_settings": "Settings",
|
||||
"verify_step_3_section": "Devices/Sessions",
|
||||
"verify_step_4": "Initiate verification.",
|
||||
"verify_no_device": "If you do not have any verified device, press the \"Verify Manually\" button.",
|
||||
"verify_current_title": "Unverified",
|
||||
"verify_current_desc": "Start verification from other device or verify manually.",
|
||||
"view_less": "View Less",
|
||||
"learn_more": "Learn More",
|
||||
"verify_manually": "Verify Manually",
|
||||
"verify_other_title": "Unverified",
|
||||
"verify_other_desc": "Verify device identity and grant access to encrypted messages.",
|
||||
"verify": "Verify",
|
||||
"reset": "Reset",
|
||||
|
||||
"local_backup": "Local Backup",
|
||||
"new_password": "New Password",
|
||||
"confirm_password": "Confirm Password",
|
||||
"password": "Password",
|
||||
"export": "Export",
|
||||
"export_title": "Export Messages Data",
|
||||
"export_desc": "Save password protected copy of encryption data on your device to decrypt messages later.",
|
||||
"collapse": "Collapse",
|
||||
"expand": "Expand",
|
||||
"import_title": "Import Messages Data",
|
||||
"import_desc": "Load password protected copy of encryption data from device to decrypt your messages.",
|
||||
"import": "Import",
|
||||
"decrypt": "Decrypt",
|
||||
|
||||
"emojis_stickers_title": "Emojis & Stickers",
|
||||
"default_pack": "Default Pack",
|
||||
"unknown": "Unknown",
|
||||
"view": "View",
|
||||
"favorite_packs": "Favorite Packs",
|
||||
"select_pack": "Select Pack",
|
||||
"select_pack_desc": "Pick emoji and sticker packs from rooms to use globally.",
|
||||
"select": "Select",
|
||||
"room_packs": "Room Packs",
|
||||
"close": "Close",
|
||||
"select_all": "Select All",
|
||||
"unselect_all": "Unselect All",
|
||||
"no_packs": "No Packs",
|
||||
"no_packs_desc": "Packs from rooms will appear here. You do not have any rooms with packs yet.",
|
||||
"apply_error": "Failed to apply changes! Please try again.",
|
||||
"apply_ready": "Changes saved! Apply when ready.",
|
||||
"apply_changes": "Apply Changes",
|
||||
|
||||
"about_title": "About",
|
||||
"about_tagline": "Yet another matrix client.",
|
||||
"options": "Options",
|
||||
"clear_cache_title": "Clear Cache & Reload",
|
||||
"clear_cache_desc": "Clear all your locally stored data and reload from server.",
|
||||
"clear_cache": "Clear Cache",
|
||||
"credits": "Credits",
|
||||
|
||||
"devtools_title": "Developer Tools",
|
||||
"enable_devtools": "Enable Developer Tools",
|
||||
"access_token": "Access Token",
|
||||
"access_token_desc": "Copy access token to clipboard.",
|
||||
"account_data": "Account Data",
|
||||
"account_data_global": "Global",
|
||||
"account_data_desc": "Data stored in your global account data.",
|
||||
"events": "Events",
|
||||
"total": "Total: {{count}}",
|
||||
"add_new": "Add New"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@
|
|||
"register_button": "Зарегистрироваться",
|
||||
"register_token_label": "Токен регистрации",
|
||||
"register_token_optional_label": "Токен регистрации (необязательно)",
|
||||
"register_email_label": "Email",
|
||||
"register_email_optional_label": "Email (необязательно)",
|
||||
"register_email_label": "Эл. почта",
|
||||
"register_email_optional_label": "Эл. почта (необязательно)",
|
||||
"register_terms": "Я принимаю <termsLink>Условия использования</termsLink> сервера.",
|
||||
"register_error_user_taken": "Это имя пользователя уже занято.",
|
||||
"register_error_user_invalid": "Имя пользователя содержит недопустимые символы.",
|
||||
|
|
@ -69,5 +69,215 @@
|
|||
"reset_error_fallback": "Не удалось сбросить пароль.",
|
||||
"reset_success_message": "Пароль успешно сброшен. Войдите с новым паролем.",
|
||||
"reset_success_login": "Войти"
|
||||
},
|
||||
"Settings": {
|
||||
"title": "Настройки",
|
||||
"menu_general": "Общие",
|
||||
"menu_account": "Аккаунт",
|
||||
"menu_notifications": "Уведомления",
|
||||
"menu_devices": "Устройства",
|
||||
"menu_emojis_stickers": "Эмодзи и стикеры",
|
||||
"menu_developer_tools": "Инструменты разработчика",
|
||||
"menu_about": "О приложении",
|
||||
"logout": "Выйти",
|
||||
|
||||
"general_title": "Общие",
|
||||
"appearance": "Внешний вид",
|
||||
"system_theme": "Системная тема",
|
||||
"system_theme_desc": "Выбор между светлой и тёмной темой на основе системных настроек.",
|
||||
"light_theme": "Светлая тема:",
|
||||
"dark_theme": "Тёмная тема:",
|
||||
"theme": "Тема",
|
||||
"theme_desc": "Тема при отключённой системной теме.",
|
||||
"monochrome_mode": "Монохромный режим",
|
||||
"twitter_emoji": "Эмодзи Twitter",
|
||||
"page_zoom": "Масштаб страницы",
|
||||
"date_time": "Дата и время",
|
||||
"hour_24": "24-часовой формат",
|
||||
"date_format": "Формат даты",
|
||||
"custom": "Пользовательский",
|
||||
"formatting": "Форматирование",
|
||||
"year": "Год",
|
||||
"two_digit_year": "Двузначный год",
|
||||
"four_digit_year": "Четырёхзначный год",
|
||||
"month": "Месяц",
|
||||
"the_month": "Месяц",
|
||||
"two_digit_month": "Двузначный месяц",
|
||||
"short_month_name": "Краткое название месяца",
|
||||
"full_month_name": "Полное название месяца",
|
||||
"day_of_month": "День месяца",
|
||||
"day_of_month_val": "День месяца",
|
||||
"two_digit_day": "Двузначный день месяца",
|
||||
"day_of_week": "День недели",
|
||||
"day_of_week_sunday": "День недели (воскресенье = 0)",
|
||||
"two_letter_day": "Двухбуквенное название дня",
|
||||
"short_day_name": "Краткое название дня",
|
||||
"full_day_name": "Полное название дня",
|
||||
"save": "Сохранить",
|
||||
"editor": "Редактор",
|
||||
"enter_newline": "ENTER для новой строки",
|
||||
"enter_newline_desc": "Используйте {{key}} + ENTER для отправки сообщения и ENTER для новой строки.",
|
||||
"markdown": "Форматирование Markdown",
|
||||
"hide_activity": "Скрыть набор текста и уведомления о прочтении",
|
||||
"hide_activity_desc": "Отключить статус набора и отчёты о прочтении для сохранения приватности.",
|
||||
"messages": "Сообщения",
|
||||
"message_layout": "Макет сообщений",
|
||||
"message_spacing": "Интервал сообщений",
|
||||
"legacy_username_color": "Классический цвет имени",
|
||||
"hide_membership": "Скрыть изменения участников",
|
||||
"hide_profile": "Скрыть изменения профиля",
|
||||
"disable_media_auto_load": "Отключить автозагрузку медиа",
|
||||
"url_preview": "Предпросмотр ссылок",
|
||||
"url_preview_encrypted": "Предпросмотр ссылок в зашифрованных комнатах",
|
||||
"show_hidden_events": "Показывать скрытые события",
|
||||
|
||||
"account_title": "Аккаунт",
|
||||
"profile": "Профиль",
|
||||
"avatar": "Аватар",
|
||||
"upload": "Загрузить",
|
||||
"remove": "Удалить",
|
||||
"remove_avatar": "Удалить аватар",
|
||||
"remove_avatar_confirm": "Вы уверены, что хотите удалить аватар профиля?",
|
||||
"display_name": "Отображаемое имя",
|
||||
"matrix_id": "Matrix ID",
|
||||
"copy": "Копировать",
|
||||
"contact_info": "Контактная информация",
|
||||
"email_address": "Адрес электронной почты",
|
||||
"email_address_desc": "Электронная почта, привязанная к вашему аккаунту.",
|
||||
"blocked_users": "Заблокированные пользователи",
|
||||
"select_user": "Выбрать пользователя",
|
||||
"select_user_desc": "Заблокируйте получение сообщений и приглашений от пользователя, добавив его идентификатор.",
|
||||
"block": "Заблокировать",
|
||||
"users": "Пользователи",
|
||||
|
||||
"notifications_title": "Уведомления",
|
||||
"block_messages": "Блокировка сообщений",
|
||||
"block_messages_moved": "Эта опция перенесена в раздел «Аккаунт > Заблокированные пользователи».",
|
||||
"system": "Система",
|
||||
"desktop_notifications": "Уведомления на рабочем столе",
|
||||
"notif_permission_blocked": "Разрешение на уведомления заблокировано. Разрешите уведомления в адресной строке браузера.",
|
||||
"notif_not_supported": "Уведомления не поддерживаются системой.",
|
||||
"notif_show_desktop": "Показывать уведомления на рабочем столе при получении сообщений.",
|
||||
"enable": "Включить",
|
||||
"notification_sound": "Звук уведомлений",
|
||||
"notification_sound_desc": "Воспроизводить звук при получении нового сообщения.",
|
||||
"email_notification": "Уведомления по почте",
|
||||
"email_no_email": "К вашему аккаунту не привязана электронная почта.",
|
||||
"email_send_notif": "Отправлять уведомления на вашу почту.",
|
||||
"email_send_notif_to": "Отправлять уведомления на вашу почту. (\"{{email}}\")",
|
||||
"unexpected_error": "Непредвиденная ошибка!",
|
||||
"all_messages": "Все сообщения",
|
||||
"badge": "Значок: ",
|
||||
"one_to_one": "Личные чаты",
|
||||
"one_to_one_encrypted": "Личные чаты (зашифрованные)",
|
||||
"rooms": "Комнаты",
|
||||
"rooms_encrypted": "Комнаты (зашифрованные)",
|
||||
"special_messages": "Специальные сообщения",
|
||||
"mention_user_id": "Упоминание ID пользователя (\"{{userId}}\")",
|
||||
"contains_displayname": "Содержит отображаемое имя",
|
||||
"contains_displayname_value": "Содержит отображаемое имя (\"{{displayName}}\")",
|
||||
"contains_username": "Содержит имя пользователя (\"{{username}}\")",
|
||||
"mention_room": "Упоминание @room",
|
||||
"contains_room": "Содержит @room",
|
||||
"keyword_messages": "Ключевые слова",
|
||||
"select_keyword": "Выбрать ключевое слово",
|
||||
"select_keyword_desc": "Настройте уведомление для сообщений, содержащих указанное ключевое слово.",
|
||||
"notif_disable": "Отключить",
|
||||
"notif_silent": "Тихое уведомление",
|
||||
"notif_loud": "Громкое уведомление",
|
||||
|
||||
"devices_title": "Устройства",
|
||||
"security": "Безопасность",
|
||||
"device_verification": "Верификация устройства",
|
||||
"device_verification_desc": "Для подтверждения идентичности устройства и доступа к зашифрованным сообщениям.",
|
||||
"current": "Текущее",
|
||||
"last_activity": "Последняя активность: ",
|
||||
"today": "Сегодня",
|
||||
"yesterday": "Вчера",
|
||||
"device_id": "ID устройства: ",
|
||||
"ip_address": "IP-адрес: ",
|
||||
"device_key": "Ключ устройства: ",
|
||||
"loading": "загрузка...",
|
||||
"device_name": "Имя устройства",
|
||||
"device_name_public": "Имена устройств видны всем.",
|
||||
"cancel": "Отмена",
|
||||
"edit": "Редактировать",
|
||||
"undo": "Отменить",
|
||||
"others": "Другие",
|
||||
"device_dashboard": "Панель устройств",
|
||||
"device_dashboard_desc": "Управление устройствами через панель OIDC.",
|
||||
"open": "Открыть",
|
||||
"delete_devices_error": "Не удалось выйти с устройств! Попробуйте снова. {{message}}",
|
||||
"delete_devices_confirm": "Выйти с выбранных устройств. ({{count}} выбрано)",
|
||||
"delete_devices_unsupported": "Шаги аутентификации для этого действия не поддерживаются клиентом.",
|
||||
"unverified": "Не верифицировано",
|
||||
"unverified_count": "{{count}} не верифицировано",
|
||||
"verified": "Верифицировано",
|
||||
"verify_steps_title": "Шаги для верификации с другого устройства.",
|
||||
"verify_step_1": "Откройте другое верифицированное устройство.",
|
||||
"verify_step_2_settings": "Настройки",
|
||||
"verify_step_3_section": "Устройства/Сессии",
|
||||
"verify_step_4": "Начните верификацию.",
|
||||
"verify_no_device": "Если у вас нет верифицированного устройства, нажмите кнопку «Верифицировать вручную».",
|
||||
"verify_current_title": "Не верифицировано",
|
||||
"verify_current_desc": "Начните верификацию с другого устройства или верифицируйте вручную.",
|
||||
"view_less": "Свернуть",
|
||||
"learn_more": "Подробнее",
|
||||
"verify_manually": "Верифицировать вручную",
|
||||
"verify_other_title": "Не верифицировано",
|
||||
"verify_other_desc": "Подтвердите идентичность устройства и получите доступ к зашифрованным сообщениям.",
|
||||
"verify": "Верифицировать",
|
||||
"reset": "Сбросить",
|
||||
|
||||
"local_backup": "Локальная копия",
|
||||
"new_password": "Новый пароль",
|
||||
"confirm_password": "Подтвердите пароль",
|
||||
"password": "Пароль",
|
||||
"export": "Экспорт",
|
||||
"export_title": "Экспорт данных сообщений",
|
||||
"export_desc": "Сохраните защищённую паролем копию ключей шифрования на устройстве для расшифровки сообщений позже.",
|
||||
"collapse": "Свернуть",
|
||||
"expand": "Развернуть",
|
||||
"import_title": "Импорт данных сообщений",
|
||||
"import_desc": "Загрузите защищённую паролем копию ключей шифрования с устройства для расшифровки сообщений.",
|
||||
"import": "Импорт",
|
||||
"decrypt": "Расшифровать",
|
||||
|
||||
"emojis_stickers_title": "Эмодзи и стикеры",
|
||||
"default_pack": "Пакет по умолчанию",
|
||||
"unknown": "Неизвестно",
|
||||
"view": "Открыть",
|
||||
"favorite_packs": "Избранные пакеты",
|
||||
"select_pack": "Выбрать пакет",
|
||||
"select_pack_desc": "Выберите пакеты эмодзи и стикеров из комнат для использования во всех комнатах.",
|
||||
"select": "Выбрать",
|
||||
"room_packs": "Пакеты комнат",
|
||||
"close": "Закрыть",
|
||||
"select_all": "Выбрать все",
|
||||
"unselect_all": "Снять выделение",
|
||||
"no_packs": "Нет пакетов",
|
||||
"no_packs_desc": "Здесь появятся пакеты из комнат. У вас пока нет комнат с пакетами.",
|
||||
"apply_error": "Не удалось применить изменения! Попробуйте снова.",
|
||||
"apply_ready": "Изменения сохранены! Примените, когда будете готовы.",
|
||||
"apply_changes": "Применить изменения",
|
||||
|
||||
"about_title": "О приложении",
|
||||
"about_tagline": "Ещё один клиент для Matrix.",
|
||||
"options": "Параметры",
|
||||
"clear_cache_title": "Очистить кэш и перезагрузить",
|
||||
"clear_cache_desc": "Удалить все локально сохранённые данные и загрузить заново с сервера.",
|
||||
"clear_cache": "Очистить кэш",
|
||||
"credits": "Благодарности",
|
||||
|
||||
"devtools_title": "Инструменты разработчика",
|
||||
"enable_devtools": "Включить инструменты разработчика",
|
||||
"access_token": "Токен доступа",
|
||||
"access_token_desc": "Скопировать токен доступа в буфер обмена.",
|
||||
"account_data": "Данные аккаунта",
|
||||
"account_data_global": "Глобальные",
|
||||
"account_data_desc": "Данные, хранящиеся в глобальных данных вашего аккаунта.",
|
||||
"events": "События",
|
||||
"total": "Всего: {{count}}",
|
||||
"add_new": "Добавить"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
|
|
@ -46,57 +47,26 @@ export enum SettingsPages {
|
|||
|
||||
type SettingsMenuItem = {
|
||||
page: SettingsPages;
|
||||
name: string;
|
||||
nameKey: string;
|
||||
icon: IconSrc;
|
||||
};
|
||||
|
||||
const useSettingsMenuItems = (): SettingsMenuItem[] =>
|
||||
useMemo(
|
||||
() => [
|
||||
{
|
||||
page: SettingsPages.GeneralPage,
|
||||
name: 'General',
|
||||
icon: Icons.Setting,
|
||||
},
|
||||
{
|
||||
page: SettingsPages.AccountPage,
|
||||
name: 'Account',
|
||||
icon: Icons.User,
|
||||
},
|
||||
{
|
||||
page: SettingsPages.NotificationPage,
|
||||
name: 'Notifications',
|
||||
icon: Icons.Bell,
|
||||
},
|
||||
{
|
||||
page: SettingsPages.DevicesPage,
|
||||
name: 'Devices',
|
||||
icon: Icons.Monitor,
|
||||
},
|
||||
{
|
||||
page: SettingsPages.EmojisStickersPage,
|
||||
name: 'Emojis & Stickers',
|
||||
icon: Icons.Smile,
|
||||
},
|
||||
{
|
||||
page: SettingsPages.DeveloperToolsPage,
|
||||
name: 'Developer Tools',
|
||||
icon: Icons.Terminal,
|
||||
},
|
||||
{
|
||||
page: SettingsPages.AboutPage,
|
||||
name: 'About',
|
||||
icon: Icons.Info,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
const SETTINGS_MENU_ITEMS: SettingsMenuItem[] = [
|
||||
{ page: SettingsPages.GeneralPage, nameKey: 'Settings.menu_general', icon: Icons.Setting },
|
||||
{ page: SettingsPages.AccountPage, nameKey: 'Settings.menu_account', icon: Icons.User },
|
||||
{ page: SettingsPages.NotificationPage, nameKey: 'Settings.menu_notifications', icon: Icons.Bell },
|
||||
{ page: SettingsPages.DevicesPage, nameKey: 'Settings.menu_devices', icon: Icons.Monitor },
|
||||
{ page: SettingsPages.EmojisStickersPage, nameKey: 'Settings.menu_emojis_stickers', icon: Icons.Smile },
|
||||
{ page: SettingsPages.DeveloperToolsPage, nameKey: 'Settings.menu_developer_tools', icon: Icons.Terminal },
|
||||
{ page: SettingsPages.AboutPage, nameKey: 'Settings.menu_about', icon: Icons.Info },
|
||||
];
|
||||
|
||||
type SettingsProps = {
|
||||
initialPage?: SettingsPages;
|
||||
requestClose: () => void;
|
||||
};
|
||||
export function Settings({ initialPage, requestClose }: SettingsProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const userId = mx.getUserId()!;
|
||||
|
|
@ -111,7 +81,7 @@ export function Settings({ initialPage, requestClose }: SettingsProps) {
|
|||
if (initialPage) return initialPage;
|
||||
return screenSize === ScreenSize.Mobile ? undefined : SettingsPages.GeneralPage;
|
||||
});
|
||||
const menuItems = useSettingsMenuItems();
|
||||
const menuItems = SETTINGS_MENU_ITEMS;
|
||||
|
||||
const handlePageRequestClose = () => {
|
||||
if (screenSize === ScreenSize.Mobile) {
|
||||
|
|
@ -136,7 +106,7 @@ export function Settings({ initialPage, requestClose }: SettingsProps) {
|
|||
/>
|
||||
</Avatar>
|
||||
<Text size="H4" truncate>
|
||||
Settings
|
||||
{t('Settings.title')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No">
|
||||
|
|
@ -152,7 +122,7 @@ export function Settings({ initialPage, requestClose }: SettingsProps) {
|
|||
<div style={{ flexGrow: 1 }}>
|
||||
{menuItems.map((item) => (
|
||||
<MenuItem
|
||||
key={item.name}
|
||||
key={item.nameKey}
|
||||
variant="Background"
|
||||
radii="400"
|
||||
aria-pressed={activePage === item.page}
|
||||
|
|
@ -166,7 +136,7 @@ export function Settings({ initialPage, requestClose }: SettingsProps) {
|
|||
size="T300"
|
||||
truncate
|
||||
>
|
||||
{item.name}
|
||||
{t(item.nameKey)}
|
||||
</Text>
|
||||
</MenuItem>
|
||||
))}
|
||||
|
|
@ -184,7 +154,7 @@ export function Settings({ initialPage, requestClose }: SettingsProps) {
|
|||
before={<Icon src={Icons.Power} size="100" />}
|
||||
onClick={() => setLogout(true)}
|
||||
>
|
||||
<Text size="B400">Logout</Text>
|
||||
<Text size="B400">{t('Settings.logout')}</Text>
|
||||
</Button>
|
||||
{logout && (
|
||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Text, IconButton, Icon, Icons, Scroll, Button, config, toRem } from 'folds';
|
||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
|
|
@ -12,6 +13,7 @@ type AboutProps = {
|
|||
requestClose: () => void;
|
||||
};
|
||||
export function About({ requestClose }: AboutProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
|
||||
return (
|
||||
|
|
@ -20,7 +22,7 @@ export function About({ requestClose }: AboutProps) {
|
|||
<Box grow="Yes" gap="200">
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<Text size="H3" truncate>
|
||||
About
|
||||
{t('Settings.about_title')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No">
|
||||
|
|
@ -48,13 +50,13 @@ export function About({ requestClose }: AboutProps) {
|
|||
<Text size="H3">Vojo</Text>
|
||||
<Text size="T200">v4.11.1</Text>
|
||||
</Box>
|
||||
<Text>Yet another matrix client.</Text>
|
||||
<Text>{t('Settings.about_tagline')}</Text>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Options</Text>
|
||||
<Text size="L400">{t('Settings.options')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
@ -62,8 +64,8 @@ export function About({ requestClose }: AboutProps) {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Clear Cache & Reload"
|
||||
description="Clear all your locally stored data and reload from server."
|
||||
title={t('Settings.clear_cache_title')}
|
||||
description={t('Settings.clear_cache_desc')}
|
||||
after={
|
||||
<Button
|
||||
onClick={() => clearCacheAndReload(mx)}
|
||||
|
|
@ -73,14 +75,14 @@ export function About({ requestClose }: AboutProps) {
|
|||
radii="300"
|
||||
outlined
|
||||
>
|
||||
<Text size="B300">Clear Cache</Text>
|
||||
<Text size="B300">{t('Settings.clear_cache')}</Text>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</SequenceCard>
|
||||
</Box>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Credits</Text>
|
||||
<Text size="L400">{t('Settings.credits')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Text, IconButton, Icon, Icons, Scroll } from 'folds';
|
||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||
import { MatrixId } from './MatrixId';
|
||||
|
|
@ -10,13 +11,14 @@ type AccountProps = {
|
|||
requestClose: () => void;
|
||||
};
|
||||
export function Account({ requestClose }: AccountProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader outlined={false}>
|
||||
<Box grow="Yes" gap="200">
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<Text size="H3" truncate>
|
||||
Account
|
||||
{t('Settings.account_title')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useCallback, useEffect } from 'react';
|
||||
import { Box, Text, Chip } from 'folds';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
import { SequenceCardStyle } from '../styles.css';
|
||||
import { SettingTile } from '../../../components/setting-tile';
|
||||
|
|
@ -7,6 +8,7 @@ import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
|||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||
|
||||
export function ContactInformation() {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const [threePIdsState, loadThreePIds] = useAsyncCallback(
|
||||
useCallback(() => mx.getThreePids(), [mx])
|
||||
|
|
@ -22,14 +24,14 @@ export function ContactInformation() {
|
|||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Contact Information</Text>
|
||||
<Text size="L400">{t('Settings.contact_info')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
direction="Column"
|
||||
gap="400"
|
||||
>
|
||||
<SettingTile title="Email Address" description="Email address attached to your account.">
|
||||
<SettingTile title={t('Settings.email_address')} description={t('Settings.email_address_desc')}>
|
||||
<Box>
|
||||
{emailIds?.map((email) => (
|
||||
<Chip key={email.address} as="span" variant="Secondary" radii="Pill">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { ChangeEventHandler, FormEventHandler, useCallback, useState } from 'react';
|
||||
import { Box, Button, Chip, Icon, IconButton, Icons, Input, Spinner, Text, config } from 'folds';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
import { SequenceCardStyle } from '../styles.css';
|
||||
import { SettingTile } from '../../../components/setting-tile';
|
||||
|
|
@ -10,6 +11,7 @@ import { useIgnoredUsers } from '../../../hooks/useIgnoredUsers';
|
|||
import { useAlive } from '../../../hooks/useAlive';
|
||||
|
||||
function IgnoreUserInput({ userList }: { userList: string[] }) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const [userId, setUserId] = useState<string>('');
|
||||
const alive = useAlive();
|
||||
|
|
@ -89,7 +91,7 @@ function IgnoreUserInput({ userList }: { userList: string[] }) {
|
|||
disabled={ignoring}
|
||||
>
|
||||
{ignoring && <Spinner variant="Secondary" size="300" />}
|
||||
<Text size="B400">Block</Text>
|
||||
<Text size="B400">{t('Settings.block')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
|
|
@ -129,12 +131,13 @@ function IgnoredUserChip({ userId, userList }: { userId: string; userList: strin
|
|||
}
|
||||
|
||||
export function IgnoredUserList() {
|
||||
const { t } = useTranslation();
|
||||
const ignoredUsers = useIgnoredUsers();
|
||||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Box alignItems="Center" justifyContent="SpaceBetween" gap="200">
|
||||
<Text size="L400">Blocked Users</Text>
|
||||
<Text size="L400">{t('Settings.blocked_users')}</Text>
|
||||
</Box>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
|
|
@ -143,14 +146,14 @@ export function IgnoredUserList() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Select User"
|
||||
description="Prevent receiving messages or invites from user by adding their userId."
|
||||
title={t('Settings.select_user')}
|
||||
description={t('Settings.select_user_desc')}
|
||||
>
|
||||
<Box direction="Column" gap="300">
|
||||
<IgnoreUserInput userList={ignoredUsers} />
|
||||
{ignoredUsers.length > 0 && (
|
||||
<Box direction="Inherit" gap="100">
|
||||
<Text size="L400">Users</Text>
|
||||
<Text size="L400">{t('Settings.users')}</Text>
|
||||
<Box wrap="Wrap" gap="200">
|
||||
{ignoredUsers.map((userId) => (
|
||||
<IgnoredUserChip key={userId} userId={userId} userList={ignoredUsers} />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Box, Text, Chip } from 'folds';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
import { SequenceCardStyle } from '../styles.css';
|
||||
|
|
@ -7,12 +8,13 @@ import { SettingTile } from '../../../components/setting-tile';
|
|||
import { copyToClipboard } from '../../../utils/dom';
|
||||
|
||||
export function MatrixId() {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const userId = mx.getUserId()!;
|
||||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Matrix ID</Text>
|
||||
<Text size="L400">{t('Settings.matrix_id')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
@ -23,7 +25,7 @@ export function MatrixId() {
|
|||
title={userId}
|
||||
after={
|
||||
<Chip variant="Secondary" radii="Pill" onClick={() => copyToClipboard(userId)}>
|
||||
<Text size="T200">Copy</Text>
|
||||
<Text size="T200">{t('Settings.copy')}</Text>
|
||||
</Chip>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
Spinner,
|
||||
} from 'folds';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
import { SequenceCardStyle } from '../styles.css';
|
||||
import { SettingTile } from '../../../components/setting-tile';
|
||||
|
|
@ -49,6 +50,7 @@ type ProfileProps = {
|
|||
userId: string;
|
||||
};
|
||||
function ProfileAvatar({ profile, userId }: ProfileProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const capabilities = useCapabilities();
|
||||
|
|
@ -91,7 +93,7 @@ function ProfileAvatar({ profile, userId }: ProfileProps) {
|
|||
<SettingTile
|
||||
title={
|
||||
<Text as="span" size="L400">
|
||||
Avatar
|
||||
{t('Settings.avatar')}
|
||||
</Text>
|
||||
}
|
||||
after={
|
||||
|
|
@ -123,7 +125,7 @@ function ProfileAvatar({ profile, userId }: ProfileProps) {
|
|||
radii="300"
|
||||
disabled={disableSetAvatar}
|
||||
>
|
||||
<Text size="B300">Upload</Text>
|
||||
<Text size="B300">{t('Settings.upload')}</Text>
|
||||
</Button>
|
||||
{avatarUrl && (
|
||||
<Button
|
||||
|
|
@ -134,7 +136,7 @@ function ProfileAvatar({ profile, userId }: ProfileProps) {
|
|||
disabled={disableSetAvatar}
|
||||
onClick={() => setAlertRemove(true)}
|
||||
>
|
||||
<Text size="B300">Remove</Text>
|
||||
<Text size="B300">{t('Settings.remove')}</Text>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
|
|
@ -183,7 +185,7 @@ function ProfileAvatar({ profile, userId }: ProfileProps) {
|
|||
size="500"
|
||||
>
|
||||
<Box grow="Yes">
|
||||
<Text size="H4">Remove Avatar</Text>
|
||||
<Text size="H4">{t('Settings.remove_avatar')}</Text>
|
||||
</Box>
|
||||
<IconButton size="300" onClick={() => setAlertRemove(false)} radii="300">
|
||||
<Icon src={Icons.Cross} />
|
||||
|
|
@ -191,10 +193,10 @@ function ProfileAvatar({ profile, userId }: ProfileProps) {
|
|||
</Header>
|
||||
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
|
||||
<Box direction="Column" gap="200">
|
||||
<Text priority="400">Are you sure you want to remove profile avatar?</Text>
|
||||
<Text priority="400">{t('Settings.remove_avatar_confirm')}</Text>
|
||||
</Box>
|
||||
<Button variant="Critical" onClick={handleRemoveAvatar}>
|
||||
<Text size="B400">Remove</Text>
|
||||
<Text size="B400">{t('Settings.remove')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Dialog>
|
||||
|
|
@ -206,6 +208,7 @@ function ProfileAvatar({ profile, userId }: ProfileProps) {
|
|||
}
|
||||
|
||||
function ProfileDisplayName({ profile, userId }: ProfileProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const capabilities = useCapabilities();
|
||||
const disableSetDisplayname = capabilities['m.set_displayname']?.enabled === false;
|
||||
|
|
@ -248,7 +251,7 @@ function ProfileDisplayName({ profile, userId }: ProfileProps) {
|
|||
<SettingTile
|
||||
title={
|
||||
<Text as="span" size="L400">
|
||||
Display Name
|
||||
{t('Settings.display_name')}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
|
|
@ -295,7 +298,7 @@ function ProfileDisplayName({ profile, userId }: ProfileProps) {
|
|||
type="submit"
|
||||
>
|
||||
{changingDisplayName && <Spinner variant="Success" fill="Solid" size="300" />}
|
||||
<Text size="B400">Save</Text>
|
||||
<Text size="B400">{t('Settings.save')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
@ -304,13 +307,14 @@ function ProfileDisplayName({ profile, userId }: ProfileProps) {
|
|||
}
|
||||
|
||||
export function Profile() {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const userId = mx.getUserId()!;
|
||||
const profile = useUserProfile(userId);
|
||||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Profile</Text>
|
||||
<Text size="L400">{t('Settings.profile')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Text, Icon, Icons, Button, MenuItem } from 'folds';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
import { SequenceCardStyle } from '../styles.css';
|
||||
|
|
@ -13,6 +14,7 @@ type AccountDataProps = {
|
|||
onSelect: (type: string | null) => void;
|
||||
};
|
||||
export function AccountData({ expand, onExpandToggle, onSelect }: AccountDataProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const [accountDataTypes, setAccountDataKeys] = useState(() =>
|
||||
Array.from(mx.store.accountData.keys())
|
||||
|
|
@ -27,7 +29,7 @@ export function AccountData({ expand, onExpandToggle, onSelect }: AccountDataPro
|
|||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Account Data</Text>
|
||||
<Text size="L400">{t('Settings.account_data')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
@ -35,8 +37,8 @@ export function AccountData({ expand, onExpandToggle, onSelect }: AccountDataPro
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Global"
|
||||
description="Data stored in your global account data."
|
||||
title={t('Settings.account_data_global')}
|
||||
description={t('Settings.account_data_desc')}
|
||||
after={
|
||||
<Button
|
||||
onClick={() => onExpandToggle(!expand)}
|
||||
|
|
@ -49,15 +51,15 @@ export function AccountData({ expand, onExpandToggle, onSelect }: AccountDataPro
|
|||
<Icon src={expand ? Icons.ChevronTop : Icons.ChevronBottom} size="100" filled />
|
||||
}
|
||||
>
|
||||
<Text size="B300">{expand ? 'Collapse' : 'Expand'}</Text>
|
||||
<Text size="B300">{expand ? t('Settings.collapse') : t('Settings.expand')}</Text>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
{expand && (
|
||||
<Box direction="Column" gap="100">
|
||||
<Box justifyContent="SpaceBetween">
|
||||
<Text size="L400">Events</Text>
|
||||
<Text size="L400">Total: {accountDataTypes.length}</Text>
|
||||
<Text size="L400">{t('Settings.events')}</Text>
|
||||
<Text size="L400">{t('Settings.total', { count: accountDataTypes.length })}</Text>
|
||||
</Box>
|
||||
<CutoutCard>
|
||||
<MenuItem
|
||||
|
|
@ -70,7 +72,7 @@ export function AccountData({ expand, onExpandToggle, onSelect }: AccountDataPro
|
|||
>
|
||||
<Box grow="Yes">
|
||||
<Text size="T200" truncate>
|
||||
Add New
|
||||
{t('Settings.add_new')}
|
||||
</Text>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Text, IconButton, Icon, Icons, Scroll, Switch, Button } from 'folds';
|
||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
|
|
@ -18,6 +19,7 @@ type DeveloperToolsProps = {
|
|||
requestClose: () => void;
|
||||
};
|
||||
export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const [developerTools, setDeveloperTools] = useSetting(settingsAtom, 'developerTools');
|
||||
const [expand, setExpend] = useState(false);
|
||||
|
|
@ -47,7 +49,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
|
|||
<Box grow="Yes" gap="200">
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<Text size="H3" truncate>
|
||||
Developer Tools
|
||||
{t('Settings.devtools_title')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No">
|
||||
|
|
@ -62,7 +64,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
|
|||
<PageContent>
|
||||
<Box direction="Column" gap="700">
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Options</Text>
|
||||
<Text size="L400">{t('Settings.options')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
@ -70,7 +72,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Enable Developer Tools"
|
||||
title={t('Settings.enable_devtools')}
|
||||
after={
|
||||
<Switch
|
||||
variant="Primary"
|
||||
|
|
@ -88,8 +90,8 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Access Token"
|
||||
description="Copy access token to clipboard."
|
||||
title={t('Settings.access_token')}
|
||||
description={t('Settings.access_token_desc')}
|
||||
after={
|
||||
<Button
|
||||
onClick={() =>
|
||||
|
|
@ -101,7 +103,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
|
|||
radii="300"
|
||||
outlined
|
||||
>
|
||||
<Text size="B300">Copy</Text>
|
||||
<Text size="B300">{t('Settings.copy')}</Text>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
import { CryptoApi } from 'matrix-js-sdk/lib/crypto-api';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { IMyDevice, MatrixError } from 'matrix-js-sdk';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SettingTile } from '../../../components/setting-tile';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { timeDayMonYear, timeHourMinute, today, yesterday } from '../../../utils/time';
|
||||
|
|
@ -43,17 +44,18 @@ export function DeviceTilePlaceholder() {
|
|||
}
|
||||
|
||||
function DeviceActiveTime({ ts }: { ts: number }) {
|
||||
const { t } = useTranslation();
|
||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
||||
|
||||
return (
|
||||
<Text className={BreakWord} size="T200">
|
||||
<Text size="Inherit" as="span" priority="300">
|
||||
{'Last activity: '}
|
||||
{t('Settings.last_activity')}
|
||||
</Text>
|
||||
<>
|
||||
{today(ts) && 'Today'}
|
||||
{yesterday(ts) && 'Yesterday'}
|
||||
{today(ts) && t('Settings.today')}
|
||||
{yesterday(ts) && t('Settings.yesterday')}
|
||||
{!today(ts) && !yesterday(ts) && timeDayMonYear(ts, dateFormatString)}{' '}
|
||||
{timeHourMinute(ts, hour24Clock)}
|
||||
</>
|
||||
|
|
@ -62,16 +64,17 @@ function DeviceActiveTime({ ts }: { ts: number }) {
|
|||
}
|
||||
|
||||
function DeviceDetails({ device }: { device: IMyDevice }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
{typeof device.device_id === 'string' && (
|
||||
<Text className={BreakWord} size="T200" priority="300">
|
||||
Device ID: <i>{device.device_id}</i>
|
||||
{t('Settings.device_id')}<i>{device.device_id}</i>
|
||||
</Text>
|
||||
)}
|
||||
{typeof device.last_seen_ip === 'string' && (
|
||||
<Text className={BreakWord} size="T200" priority="300">
|
||||
IP Address: <i>{device.last_seen_ip}</i>
|
||||
{t('Settings.ip_address')}<i>{device.last_seen_ip}</i>
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
|
|
@ -82,6 +85,7 @@ type DeviceKeyDetailsProps = {
|
|||
crypto: CryptoApi;
|
||||
};
|
||||
export function DeviceKeyDetails({ crypto }: DeviceKeyDetailsProps) {
|
||||
const { t } = useTranslation();
|
||||
const [keysState, loadKeys] = useAsyncCallback(
|
||||
useCallback(() => {
|
||||
const keys = crypto.getOwnDeviceKeys();
|
||||
|
|
@ -97,8 +101,8 @@ export function DeviceKeyDetails({ crypto }: DeviceKeyDetailsProps) {
|
|||
|
||||
return (
|
||||
<Text className={BreakWord} size="T200" priority="300">
|
||||
Device Key:{' '}
|
||||
<i>{keysState.status === AsyncStatus.Success ? keysState.data.ed25519 : 'loading...'}</i>
|
||||
{t('Settings.device_key')}
|
||||
<i>{keysState.status === AsyncStatus.Success ? keysState.data.ed25519 : t('Settings.loading')}</i>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
|
@ -110,6 +114,7 @@ type DeviceRenameProps = {
|
|||
refreshDeviceList: () => Promise<void>;
|
||||
};
|
||||
function DeviceRename({ device, onCancel, onRename, refreshDeviceList }: DeviceRenameProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
|
||||
const [renameState, rename] = useAsyncCallback<void, MatrixError, [string]>(
|
||||
|
|
@ -145,7 +150,7 @@ function DeviceRename({ device, onCancel, onRename, refreshDeviceList }: DeviceR
|
|||
|
||||
return (
|
||||
<Box as="form" onSubmit={handleSubmit} direction="Column" gap="100">
|
||||
<Text size="L400">Device Name</Text>
|
||||
<Text size="L400">{t('Settings.device_name')}</Text>
|
||||
<Box gap="200">
|
||||
<Box grow="Yes" direction="Column">
|
||||
<Input
|
||||
|
|
@ -169,7 +174,7 @@ function DeviceRename({ device, onCancel, onRename, refreshDeviceList }: DeviceR
|
|||
disabled={renaming}
|
||||
before={renaming && <Spinner size="100" variant="Success" fill="Solid" />}
|
||||
>
|
||||
<Text size="B300">Save</Text>
|
||||
<Text size="B300">{t('Settings.save')}</Text>
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
|
|
@ -180,7 +185,7 @@ function DeviceRename({ device, onCancel, onRename, refreshDeviceList }: DeviceR
|
|||
onClick={onCancel}
|
||||
disabled={renaming}
|
||||
>
|
||||
<Text size="B300">Cancel</Text>
|
||||
<Text size="B300">{t('Settings.cancel')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
@ -189,13 +194,14 @@ function DeviceRename({ device, onCancel, onRename, refreshDeviceList }: DeviceR
|
|||
{renameState.error.message}
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="T200">Device names are visible to public.</Text>
|
||||
<Text size="T200">{t('Settings.device_name_public')}</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function DeviceLogoutBtn() {
|
||||
const { t } = useTranslation();
|
||||
const [prompt, setPrompt] = useState(false);
|
||||
|
||||
const handleClose = () => setPrompt(false);
|
||||
|
|
@ -203,7 +209,7 @@ export function DeviceLogoutBtn() {
|
|||
return (
|
||||
<>
|
||||
<Chip variant="Secondary" fill="Soft" radii="Pill" onClick={() => setPrompt(true)}>
|
||||
<Text size="B300">Logout</Text>
|
||||
<Text size="B300">{t('Settings.logout')}</Text>
|
||||
</Chip>
|
||||
{prompt && (
|
||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||
|
|
@ -236,6 +242,7 @@ export function DeviceDeleteBtn({
|
|||
onDeleteToggle,
|
||||
disabled,
|
||||
}: DeviceDeleteBtnProps) {
|
||||
const { t } = useTranslation();
|
||||
return deleted ? (
|
||||
<Chip
|
||||
variant="Critical"
|
||||
|
|
@ -244,7 +251,7 @@ export function DeviceDeleteBtn({
|
|||
onClick={() => onDeleteToggle(deviceId)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Text size="B300">Undo</Text>
|
||||
<Text size="B300">{t('Settings.undo')}</Text>
|
||||
</Chip>
|
||||
) : (
|
||||
<Chip
|
||||
|
|
@ -275,6 +282,7 @@ export function DeviceTile({
|
|||
options,
|
||||
children,
|
||||
}: DeviceTileProps) {
|
||||
const { t } = useTranslation();
|
||||
const activeTs = device.last_seen_ts;
|
||||
const [details, setDetails] = useState(false);
|
||||
const [edit, setEdit] = useState(false);
|
||||
|
|
@ -307,7 +315,7 @@ export function DeviceTile({
|
|||
onClick={() => setEdit(true)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Text size="B300">Edit</Text>
|
||||
<Text size="B300">{t('Settings.edit')}</Text>
|
||||
</Chip>
|
||||
)}
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Text, IconButton, Icon, Icons, Scroll } from 'folds';
|
||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
|
|
@ -40,6 +41,7 @@ type DevicesProps = {
|
|||
requestClose: () => void;
|
||||
};
|
||||
export function Devices({ requestClose }: DevicesProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const crypto = mx.getCrypto();
|
||||
const crossSigningActive = useCrossSigningActive();
|
||||
|
|
@ -70,7 +72,7 @@ export function Devices({ requestClose }: DevicesProps) {
|
|||
<Box grow="Yes" gap="200">
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<Text size="H3" truncate>
|
||||
Devices
|
||||
{t('Settings.devices_title')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No">
|
||||
|
|
@ -85,7 +87,7 @@ export function Devices({ requestClose }: DevicesProps) {
|
|||
<PageContent>
|
||||
<Box direction="Column" gap="700">
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Security</Text>
|
||||
<Text size="L400">{t('Settings.security')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
@ -93,8 +95,8 @@ export function Devices({ requestClose }: DevicesProps) {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Device Verification"
|
||||
description="To verify device identity and grant access to encrypted messages."
|
||||
title={t('Settings.device_verification')}
|
||||
description={t('Settings.device_verification_desc')}
|
||||
after={
|
||||
<>
|
||||
<EnableVerification visible={!crossSigningActive} />
|
||||
|
|
@ -113,7 +115,7 @@ export function Devices({ requestClose }: DevicesProps) {
|
|||
</SequenceCard>
|
||||
</Box>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Current</Text>
|
||||
<Text size="L400">{t('Settings.current')}</Text>
|
||||
{currentDevice ? (
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { FormEventHandler, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Button, color, Icon, Icons, Spinner, Text, toRem } from 'folds';
|
||||
import FileSaver from 'file-saver';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
|
|
@ -13,6 +14,7 @@ import { useAlive } from '../../../hooks/useAlive';
|
|||
import { useFilePicker } from '../../../hooks/useFilePicker';
|
||||
|
||||
function ExportKeys() {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const alive = useAlive();
|
||||
|
||||
|
|
@ -66,7 +68,7 @@ function ExportKeys() {
|
|||
{(match, doMatch, passRef, confPassRef) => (
|
||||
<>
|
||||
<Box grow="Yes" direction="Column" gap="100">
|
||||
<Text size="L400">New Password</Text>
|
||||
<Text size="L400">{t('Settings.new_password')}</Text>
|
||||
<PasswordInput
|
||||
ref={passRef}
|
||||
name="passwordInput"
|
||||
|
|
@ -80,7 +82,7 @@ function ExportKeys() {
|
|||
/>
|
||||
</Box>
|
||||
<Box grow="Yes" direction="Column" gap="100">
|
||||
<Text size="L400">Confirm Password</Text>
|
||||
<Text size="L400">{t('Settings.confirm_password')}</Text>
|
||||
<PasswordInput
|
||||
ref={confPassRef}
|
||||
style={{ color: match ? undefined : color.Critical.Main }}
|
||||
|
|
@ -107,7 +109,7 @@ function ExportKeys() {
|
|||
before={exporting ? <Spinner size="200" variant="Secondary" fill="Soft" /> : undefined}
|
||||
>
|
||||
<Text as="span" size="B400">
|
||||
Export
|
||||
{t('Settings.export')}
|
||||
</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
|
|
@ -122,13 +124,14 @@ function ExportKeys() {
|
|||
}
|
||||
|
||||
function ExportKeysTile() {
|
||||
const { t } = useTranslation();
|
||||
const [expand, setExpand] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingTile
|
||||
title="Export Messages Data"
|
||||
description="Save password protected copy of encryption data on your device to decrypt messages later."
|
||||
title={t('Settings.export_title')}
|
||||
description={t('Settings.export_desc')}
|
||||
after={
|
||||
<Box>
|
||||
<Button
|
||||
|
|
@ -144,7 +147,7 @@ function ExportKeysTile() {
|
|||
}
|
||||
>
|
||||
<Text as="span" size="B300" truncate>
|
||||
{expand ? 'Collapse' : 'Expand'}
|
||||
{expand ? t('Settings.collapse') : t('Settings.expand')}
|
||||
</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
|
|
@ -160,6 +163,7 @@ type ImportKeysProps = {
|
|||
onDone?: () => void;
|
||||
};
|
||||
function ImportKeys({ file, onDone }: ImportKeysProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const alive = useAlive();
|
||||
|
||||
|
|
@ -209,7 +213,7 @@ function ImportKeys({ file, onDone }: ImportKeysProps) {
|
|||
<Box as="form" onSubmit={handleSubmit} direction="Column" gap="100">
|
||||
<Box gap="200" alignItems="End">
|
||||
<Box grow="Yes" direction="Column" gap="100">
|
||||
<Text size="L400">Password</Text>
|
||||
<Text size="L400">{t('Settings.password')}</Text>
|
||||
<PasswordInput
|
||||
name="passwordInput"
|
||||
size="400"
|
||||
|
|
@ -231,7 +235,7 @@ function ImportKeys({ file, onDone }: ImportKeysProps) {
|
|||
before={decrypting ? <Spinner size="200" variant="Secondary" fill="Soft" /> : undefined}
|
||||
>
|
||||
<Text as="span" size="B400">
|
||||
Decrypt
|
||||
{t('Settings.decrypt')}
|
||||
</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
|
|
@ -246,6 +250,7 @@ function ImportKeys({ file, onDone }: ImportKeysProps) {
|
|||
}
|
||||
|
||||
function ImportKeysTile() {
|
||||
const { t } = useTranslation();
|
||||
const [file, setFile] = useState<File>();
|
||||
const pickFile = useFilePicker(setFile);
|
||||
|
||||
|
|
@ -256,8 +261,8 @@ function ImportKeysTile() {
|
|||
return (
|
||||
<>
|
||||
<SettingTile
|
||||
title="Import Messages Data"
|
||||
description="Load password protected copy of encryption data from device to decrypt your messages."
|
||||
title={t('Settings.import_title')}
|
||||
description={t('Settings.import_desc')}
|
||||
after={
|
||||
<Box>
|
||||
{file ? (
|
||||
|
|
@ -288,7 +293,7 @@ function ImportKeysTile() {
|
|||
before={<Icon size="100" src={Icons.ArrowRight} />}
|
||||
>
|
||||
<Text as="span" size="B300">
|
||||
Import
|
||||
{t('Settings.import')}
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
|
|
@ -301,9 +306,10 @@ function ImportKeysTile() {
|
|||
}
|
||||
|
||||
export function LocalBackup() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Local Backup</Text>
|
||||
<Text size="L400">{t('Settings.local_backup')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Button, config, Menu, Spinner, Text } from 'folds';
|
||||
import { AuthDict, IMyDevice, MatrixError } from 'matrix-js-sdk';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
|
|
@ -22,6 +23,7 @@ type OtherDevicesProps = {
|
|||
showVerification?: boolean;
|
||||
};
|
||||
export function OtherDevices({ devices, refreshDeviceList, showVerification }: OtherDevicesProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const crypto = mx.getCrypto();
|
||||
const authMetadata = useAuthMetadata();
|
||||
|
|
@ -104,7 +106,7 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O
|
|||
return devices.length > 0 ? (
|
||||
<>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Others</Text>
|
||||
<Text size="L400">{t('Settings.others')}</Text>
|
||||
{authMetadata && (
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
|
|
@ -113,8 +115,8 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Device Dashboard"
|
||||
description="Manage your devices on OIDC dashboard."
|
||||
title={t('Settings.device_dashboard')}
|
||||
description={t('Settings.device_dashboard_desc')}
|
||||
after={
|
||||
<Button
|
||||
size="300"
|
||||
|
|
@ -124,7 +126,7 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O
|
|||
outlined
|
||||
onClick={handleDashboardOIDC}
|
||||
>
|
||||
<Text size="B300">Open</Text>
|
||||
<Text size="B300">{t('Settings.open')}</Text>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
|
@ -198,11 +200,11 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O
|
|||
<Box grow="Yes" direction="Column">
|
||||
{deleteError ? (
|
||||
<Text size="T200">
|
||||
<b>Failed to logout devices! Please try again. {deleteError.message}</b>
|
||||
<b>{t('Settings.delete_devices_error', { message: deleteError.message })}</b>
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="T200">
|
||||
<b>Logout from selected devices. ({deleted.size} selected)</b>
|
||||
<b>{t('Settings.delete_devices_confirm', { count: deleted.size })}</b>
|
||||
</Text>
|
||||
)}
|
||||
{authData && (
|
||||
|
|
@ -210,7 +212,7 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O
|
|||
authData={authData}
|
||||
unsupported={() => (
|
||||
<Text size="T200">
|
||||
Authentication steps to perform this action are not supported by client.
|
||||
{t('Settings.delete_devices_unsupported')}
|
||||
</Text>
|
||||
)}
|
||||
>
|
||||
|
|
@ -234,7 +236,7 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O
|
|||
disabled={deleting}
|
||||
onClick={handleCancelDelete}
|
||||
>
|
||||
<Text size="B300">Cancel</Text>
|
||||
<Text size="B300">{t('Settings.cancel')}</Text>
|
||||
</Button>
|
||||
<Button
|
||||
size="300"
|
||||
|
|
@ -244,7 +246,7 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O
|
|||
before={deleting && <Spinner variant="Critical" fill="Solid" size="100" />}
|
||||
onClick={() => deleteDevices()}
|
||||
>
|
||||
<Text size="B300">Logout</Text>
|
||||
<Text size="B300">{t('Settings.logout')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
MenuItem,
|
||||
} from 'folds';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CryptoApi, VerificationRequest } from 'matrix-js-sdk/lib/crypto-api';
|
||||
import { VerificationStatus } from '../../../hooks/useDeviceVerificationStatus';
|
||||
import { InfoCard } from '../../../components/info-card';
|
||||
|
|
@ -44,6 +45,7 @@ export function VerificationStatusBadge({
|
|||
verificationStatus,
|
||||
otherUnverifiedCount,
|
||||
}: VerificationStatusBadgeProps) {
|
||||
const { t } = useTranslation();
|
||||
if (
|
||||
verificationStatus === VerificationStatus.Unknown ||
|
||||
typeof otherUnverifiedCount !== 'number'
|
||||
|
|
@ -53,7 +55,7 @@ export function VerificationStatusBadge({
|
|||
if (verificationStatus === VerificationStatus.Unverified) {
|
||||
return (
|
||||
<Badge variant="Critical" fill="Solid" size="500">
|
||||
<Text size="L400">Unverified</Text>
|
||||
<Text size="L400">{t('Settings.unverified')}</Text>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
|
@ -61,36 +63,37 @@ export function VerificationStatusBadge({
|
|||
if (otherUnverifiedCount > 0) {
|
||||
return (
|
||||
<Badge variant="Warning" fill="Solid" size="500">
|
||||
<Text size="L400">{otherUnverifiedCount} Unverified</Text>
|
||||
<Text size="L400">{t('Settings.unverified_count', { count: otherUnverifiedCount })}</Text>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge variant="Success" fill="Solid" size="500">
|
||||
<Text size="L400">Verified</Text>
|
||||
<Text size="L400">{t('Settings.verified')}</Text>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
function LearnStartVerificationFromOtherDevice() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box direction="Column">
|
||||
<Text size="T200">Steps to verify from other device.</Text>
|
||||
<Text size="T200">{t('Settings.verify_steps_title')}</Text>
|
||||
<Text as="div" size="T200">
|
||||
<ul style={{ margin: `${config.space.S100} 0` }}>
|
||||
<li>Open your other verified device.</li>
|
||||
<li>{t('Settings.verify_step_1')}</li>
|
||||
<li>
|
||||
Open <i>Settings</i>.
|
||||
Open <i>{t('Settings.verify_step_2_settings')}</i>.
|
||||
</li>
|
||||
<li>
|
||||
Find this device in <i>Devices/Sessions</i> section.
|
||||
Find this device in <i>{t('Settings.verify_step_3_section')}</i> section.
|
||||
</li>
|
||||
<li>Initiate verification.</li>
|
||||
<li>{t('Settings.verify_step_4')}</li>
|
||||
</ul>
|
||||
</Text>
|
||||
<Text size="T200">
|
||||
If you do not have any verified device press the <i>"Verify Manually"</i> button.
|
||||
{t('Settings.verify_no_device')}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
|
@ -104,6 +107,7 @@ export function VerifyCurrentDeviceTile({
|
|||
secretStorageKeyId,
|
||||
secretStorageKeyContent,
|
||||
}: VerifyCurrentDeviceTileProps) {
|
||||
const { t } = useTranslation();
|
||||
const [learnMore, setLearnMore] = useState(false);
|
||||
|
||||
const [manualVerification, setManualVerification] = useState(false);
|
||||
|
|
@ -113,12 +117,12 @@ export function VerifyCurrentDeviceTile({
|
|||
<>
|
||||
<InfoCard
|
||||
variant="Critical"
|
||||
title="Unverified"
|
||||
title={t('Settings.verify_current_title')}
|
||||
description={
|
||||
<>
|
||||
Start verification from other device or verify manually.{' '}
|
||||
{t('Settings.verify_current_desc')}{' '}
|
||||
<Text as="a" size="T200" onClick={() => setLearnMore(!learnMore)}>
|
||||
<b>{learnMore ? 'View Less' : 'Learn More'}</b>
|
||||
<b>{learnMore ? t('Settings.view_less') : t('Settings.learn_more')}</b>
|
||||
</Text>
|
||||
</>
|
||||
}
|
||||
|
|
@ -133,7 +137,7 @@ export function VerifyCurrentDeviceTile({
|
|||
onClick={() => setManualVerification(true)}
|
||||
>
|
||||
<Text as="span" size="B300">
|
||||
Verify Manually
|
||||
{t('Settings.verify_manually')}
|
||||
</Text>
|
||||
</Button>
|
||||
)
|
||||
|
|
@ -167,6 +171,7 @@ type VerifyOtherDeviceTileProps = {
|
|||
deviceId: string;
|
||||
};
|
||||
export function VerifyOtherDeviceTile({ crypto, deviceId }: VerifyOtherDeviceTileProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const [requestState, setRequestState] = useState<AsyncState<VerificationRequest, Error>>({
|
||||
status: AsyncStatus.Idle,
|
||||
|
|
@ -190,8 +195,8 @@ export function VerifyOtherDeviceTile({ crypto, deviceId }: VerifyOtherDeviceTil
|
|||
return (
|
||||
<InfoCard
|
||||
variant="Warning"
|
||||
title="Unverified"
|
||||
description="Verify device identity and grant access to encrypted messages."
|
||||
title={t('Settings.verify_other_title')}
|
||||
description={t('Settings.verify_other_desc')}
|
||||
after={
|
||||
<Button
|
||||
size="300"
|
||||
|
|
@ -202,7 +207,7 @@ export function VerifyOtherDeviceTile({ crypto, deviceId }: VerifyOtherDeviceTil
|
|||
disabled={requesting}
|
||||
>
|
||||
<Text as="span" size="B300">
|
||||
Verify
|
||||
{t('Settings.verify')}
|
||||
</Text>
|
||||
</Button>
|
||||
}
|
||||
|
|
@ -221,6 +226,7 @@ type EnableVerificationProps = {
|
|||
visible: boolean;
|
||||
};
|
||||
export function EnableVerification({ visible }: EnableVerificationProps) {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleCancel = useCallback(() => setOpen(false), []);
|
||||
|
|
@ -230,7 +236,7 @@ export function EnableVerification({ visible }: EnableVerificationProps) {
|
|||
{visible && (
|
||||
<Button size="300" radii="300" onClick={() => setOpen(true)}>
|
||||
<Text as="span" size="B300">
|
||||
Enable
|
||||
{t('Settings.enable')}
|
||||
</Text>
|
||||
</Button>
|
||||
)}
|
||||
|
|
@ -254,6 +260,7 @@ export function EnableVerification({ visible }: EnableVerificationProps) {
|
|||
}
|
||||
|
||||
export function DeviceVerificationOptions() {
|
||||
const { t } = useTranslation();
|
||||
const [menuCords, setMenuCords] = useState<RectCords>();
|
||||
const authMetadata = useAuthMetadata();
|
||||
const accountManagementActions = useAccountManagementActions();
|
||||
|
|
@ -324,7 +331,7 @@ export function DeviceVerificationOptions() {
|
|||
fill="None"
|
||||
>
|
||||
<Text as="span" size="T300" truncate>
|
||||
Reset
|
||||
{t('Settings.reset')}
|
||||
</Text>
|
||||
</MenuItem>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Text, IconButton, Icon, Icons, Scroll } from 'folds';
|
||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||
import { GlobalPacks } from './GlobalPacks';
|
||||
|
|
@ -10,6 +11,7 @@ type EmojisStickersProps = {
|
|||
requestClose: () => void;
|
||||
};
|
||||
export function EmojisStickers({ requestClose }: EmojisStickersProps) {
|
||||
const { t } = useTranslation();
|
||||
const [imagePack, setImagePack] = useState<ImagePack>();
|
||||
|
||||
const handleImagePackViewClose = () => {
|
||||
|
|
@ -26,7 +28,7 @@ export function EmojisStickers({ requestClose }: EmojisStickersProps) {
|
|||
<Box grow="Yes" gap="200">
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<Text size="H3" truncate>
|
||||
Emojis & Stickers
|
||||
{t('Settings.emojis_stickers_title')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { MouseEventHandler, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Box,
|
||||
Text,
|
||||
|
|
@ -53,6 +54,7 @@ function GlobalPackSelector({
|
|||
useAuthentication: boolean;
|
||||
onSelect: (addresses: PackAddress[]) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const roomToPacks = useMemo(() => {
|
||||
const rToP = new Map<string, ImagePack[]>();
|
||||
|
|
@ -107,7 +109,7 @@ function GlobalPackSelector({
|
|||
<Header size="400" variant="Surface" style={{ padding: `0 ${config.space.S300}` }}>
|
||||
<Box grow="Yes">
|
||||
<Text size="L400" truncate>
|
||||
Room Packs
|
||||
{t('Settings.room_packs')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No">
|
||||
|
|
@ -117,7 +119,7 @@ function GlobalPackSelector({
|
|||
outlined={hasSelected}
|
||||
onClick={() => onSelect(selected)}
|
||||
>
|
||||
<Text size="B300">{hasSelected ? 'Save' : 'Close'}</Text>
|
||||
<Text size="B300">{hasSelected ? t('Settings.save') : t('Settings.close')}</Text>
|
||||
</Chip>
|
||||
</Box>
|
||||
</Header>
|
||||
|
|
@ -162,7 +164,7 @@ function GlobalPackSelector({
|
|||
addSelected(roomPackAddresses);
|
||||
}}
|
||||
>
|
||||
<Text size="B300">{allSelected ? 'Unselect All' : 'Select All'}</Text>
|
||||
<Text size="B300">{allSelected ? t('Settings.unselect_all') : t('Settings.select_all')}</Text>
|
||||
</Chip>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
@ -184,7 +186,7 @@ function GlobalPackSelector({
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title={pack.meta.name ?? 'Unknown'}
|
||||
title={pack.meta.name ?? t('Settings.unknown')}
|
||||
description={<span className={LineClamp2}>{pack.meta.attribution}</span>}
|
||||
before={
|
||||
<Box alignItems="Center" gap="300">
|
||||
|
|
@ -232,10 +234,10 @@ function GlobalPackSelector({
|
|||
}}
|
||||
>
|
||||
<Text size="H5" align="Center">
|
||||
No Packs
|
||||
{t('Settings.no_packs')}
|
||||
</Text>
|
||||
<Text size="T200" align="Center">
|
||||
Pack from rooms will appear here. You do not have any room with packs yet.
|
||||
{t('Settings.no_packs_desc')}
|
||||
</Text>
|
||||
</Box>
|
||||
</SequenceCard>
|
||||
|
|
@ -251,6 +253,7 @@ type GlobalPacksProps = {
|
|||
onViewPack: (imagePack: ImagePack) => void;
|
||||
};
|
||||
export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const globalPacks = useGlobalImagePacks();
|
||||
|
|
@ -360,7 +363,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
|
|||
<SettingTile
|
||||
title={
|
||||
<span style={{ textDecoration: removed ? 'line-through' : undefined }}>
|
||||
{pack.meta.name ?? 'Unknown'}
|
||||
{pack.meta.name ?? t('Settings.unknown')}
|
||||
</span>
|
||||
}
|
||||
description={<span className={LineClamp2}>{pack.meta.attribution}</span>}
|
||||
|
|
@ -408,7 +411,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
|
|||
outlined
|
||||
onClick={() => onViewPack(pack)}
|
||||
>
|
||||
<Text size="B300">View</Text>
|
||||
<Text size="B300">{t('Settings.view')}</Text>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
|
@ -420,7 +423,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
|
|||
return (
|
||||
<>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Favorite Packs</Text>
|
||||
<Text size="L400">{t('Settings.favorite_packs')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
@ -428,8 +431,8 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Select Pack"
|
||||
description="Pick emojis and stickers pack from rooms to use in all rooms."
|
||||
title={t('Settings.select_pack')}
|
||||
description={t('Settings.select_pack_desc')}
|
||||
after={
|
||||
<>
|
||||
<Button
|
||||
|
|
@ -440,7 +443,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
|
|||
radii="300"
|
||||
outlined
|
||||
>
|
||||
<Text size="B300">Select</Text>
|
||||
<Text size="B300">{t('Settings.select')}</Text>
|
||||
</Button>
|
||||
<PopOut
|
||||
anchor={menuCords}
|
||||
|
|
@ -502,11 +505,11 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
|
|||
<Box grow="Yes" direction="Column">
|
||||
{applyState.status === AsyncStatus.Error ? (
|
||||
<Text size="T200">
|
||||
<b>Failed to apply changes! Please try again.</b>
|
||||
<b>{t('Settings.apply_error')}</b>
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="T200">
|
||||
<b>Changes saved! Apply when ready.</b>
|
||||
<b>{t('Settings.apply_ready')}</b>
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
|
@ -519,7 +522,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
|
|||
disabled={applyingChanges}
|
||||
onClick={resetChanges}
|
||||
>
|
||||
<Text size="B300">Reset</Text>
|
||||
<Text size="B300">{t('Settings.reset')}</Text>
|
||||
</Button>
|
||||
<Button
|
||||
size="300"
|
||||
|
|
@ -529,7 +532,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) {
|
|||
before={applyingChanges && <Spinner variant="Success" fill="Solid" size="100" />}
|
||||
onClick={applyChanges}
|
||||
>
|
||||
<Text size="B300">Apply Changes</Text>
|
||||
<Text size="B300">{t('Settings.apply_changes')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Avatar, AvatarFallback, AvatarImage, Box, Button, Icon, Icons, Text } from 'folds';
|
||||
import { useUserImagePack } from '../../../hooks/useImagePacks';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
|
|
@ -13,6 +14,7 @@ type UserPackProps = {
|
|||
onViewPack: (imagePack: ImagePack) => void;
|
||||
};
|
||||
export function UserPack({ onViewPack }: UserPackProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
|
||||
|
|
@ -31,7 +33,7 @@ export function UserPack({ onViewPack }: UserPackProps) {
|
|||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Default Pack</Text>
|
||||
<Text size="L400">{t('Settings.default_pack')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
@ -39,7 +41,7 @@ export function UserPack({ onViewPack }: UserPackProps) {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title={userPack?.meta.name ?? 'Unknown'}
|
||||
title={userPack?.meta.name ?? t('Settings.unknown')}
|
||||
description={userPack?.meta.attribution}
|
||||
before={
|
||||
<Avatar size="300" radii="300">
|
||||
|
|
@ -61,7 +63,7 @@ export function UserPack({ onViewPack }: UserPackProps) {
|
|||
outlined
|
||||
onClick={handleView}
|
||||
>
|
||||
<Text size="B300">View</Text>
|
||||
<Text size="B300">{t('Settings.view')}</Text>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import {
|
|||
toRem,
|
||||
} from 'folds';
|
||||
import { isKeyHotkey } from 'is-hotkey';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
|
|
@ -139,6 +140,7 @@ function SelectTheme({ disabled }: { disabled?: boolean }) {
|
|||
}
|
||||
|
||||
function SystemThemePreferences() {
|
||||
const { t } = useTranslation();
|
||||
const themeKind = useSystemThemeKind();
|
||||
const themeNames = useThemeNames();
|
||||
const themes = useThemes();
|
||||
|
|
@ -174,7 +176,7 @@ function SystemThemePreferences() {
|
|||
return (
|
||||
<Box wrap="Wrap" gap="400">
|
||||
<SettingTile
|
||||
title="Light Theme:"
|
||||
title={t('Settings.light_theme')}
|
||||
after={
|
||||
<Chip
|
||||
variant={themeKind === ThemeKind.Light ? 'Primary' : 'Secondary'}
|
||||
|
|
@ -215,7 +217,7 @@ function SystemThemePreferences() {
|
|||
}
|
||||
/>
|
||||
<SettingTile
|
||||
title="Dark Theme:"
|
||||
title={t('Settings.dark_theme')}
|
||||
after={
|
||||
<Chip
|
||||
variant={themeKind === ThemeKind.Dark ? 'Primary' : 'Secondary'}
|
||||
|
|
@ -304,13 +306,14 @@ function PageZoomInput() {
|
|||
}
|
||||
|
||||
function Appearance() {
|
||||
const { t } = useTranslation();
|
||||
const [systemTheme, setSystemTheme] = useSetting(settingsAtom, 'useSystemTheme');
|
||||
const [monochromeMode, setMonochromeMode] = useSetting(settingsAtom, 'monochromeMode');
|
||||
const [twitterEmoji, setTwitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
|
||||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Appearance</Text>
|
||||
<Text size="L400">{t('Settings.appearance')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
@ -318,8 +321,8 @@ function Appearance() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="System Theme"
|
||||
description="Choose between light and dark theme based on system preference."
|
||||
title={t('Settings.system_theme')}
|
||||
description={t('Settings.system_theme_desc')}
|
||||
after={<Switch variant="Primary" value={systemTheme} onChange={setSystemTheme} />}
|
||||
/>
|
||||
{systemTheme && <SystemThemePreferences />}
|
||||
|
|
@ -327,28 +330,28 @@ function Appearance() {
|
|||
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Theme"
|
||||
description="Theme to use when system theme is not enabled."
|
||||
title={t('Settings.theme')}
|
||||
description={t('Settings.theme_desc')}
|
||||
after={<SelectTheme disabled={systemTheme} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Monochrome Mode"
|
||||
title={t('Settings.monochrome_mode')}
|
||||
after={<Switch variant="Primary" value={monochromeMode} onChange={setMonochromeMode} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Twitter Emoji"
|
||||
title={t('Settings.twitter_emoji')}
|
||||
after={<Switch variant="Primary" value={twitterEmoji} onChange={setTwitterEmoji} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile title="Page Zoom" after={<PageZoomInput />} />
|
||||
<SettingTile title={t('Settings.page_zoom')} after={<PageZoomInput />} />
|
||||
</SequenceCard>
|
||||
</Box>
|
||||
);
|
||||
|
|
@ -359,6 +362,7 @@ type DateHintProps = {
|
|||
handleReset: () => void;
|
||||
};
|
||||
function DateHint({ hasChanges, handleReset }: DateHintProps) {
|
||||
const { t } = useTranslation();
|
||||
const [anchor, setAnchor] = useState<RectCords>();
|
||||
const categoryPadding = { padding: config.space.S200, paddingTop: 0 };
|
||||
|
||||
|
|
@ -381,26 +385,26 @@ function DateHint({ hasChanges, handleReset }: DateHintProps) {
|
|||
>
|
||||
<Menu style={{ maxHeight: '85vh', overflowY: 'auto' }}>
|
||||
<Header size="300" style={{ padding: `0 ${config.space.S200}` }}>
|
||||
<Text size="L400">Formatting</Text>
|
||||
<Text size="L400">{t('Settings.formatting')}</Text>
|
||||
</Header>
|
||||
|
||||
<Box direction="Column">
|
||||
<Box style={categoryPadding} direction="Column">
|
||||
<Header size="300">
|
||||
<Text size="L400">Year</Text>
|
||||
<Text size="L400">{t('Settings.year')}</Text>
|
||||
</Header>
|
||||
<Box direction="Column" tabIndex={0} gap="100">
|
||||
<Text size="T300">
|
||||
YY
|
||||
<Text as="span" size="Inherit" priority="300">
|
||||
{': '}
|
||||
Two-digit year
|
||||
{t('Settings.two_digit_year')}
|
||||
</Text>{' '}
|
||||
</Text>
|
||||
<Text size="T300">
|
||||
YYYY
|
||||
<Text as="span" size="Inherit" priority="300">
|
||||
{': '}Four-digit year
|
||||
{': '}{t('Settings.four_digit_year')}
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
|
|
@ -408,31 +412,31 @@ function DateHint({ hasChanges, handleReset }: DateHintProps) {
|
|||
|
||||
<Box style={categoryPadding} direction="Column">
|
||||
<Header size="300">
|
||||
<Text size="L400">Month</Text>
|
||||
<Text size="L400">{t('Settings.month')}</Text>
|
||||
</Header>
|
||||
<Box direction="Column" tabIndex={0} gap="100">
|
||||
<Text size="T300">
|
||||
M
|
||||
<Text as="span" size="Inherit" priority="300">
|
||||
{': '}The month
|
||||
{': '}{t('Settings.the_month')}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text size="T300">
|
||||
MM
|
||||
<Text as="span" size="Inherit" priority="300">
|
||||
{': '}Two-digit month
|
||||
{': '}{t('Settings.two_digit_month')}
|
||||
</Text>{' '}
|
||||
</Text>
|
||||
<Text size="T300">
|
||||
MMM
|
||||
<Text as="span" size="Inherit" priority="300">
|
||||
{': '}Short month name
|
||||
{': '}{t('Settings.short_month_name')}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text size="T300">
|
||||
MMMM
|
||||
<Text as="span" size="Inherit" priority="300">
|
||||
{': '}Full month name
|
||||
{': '}{t('Settings.full_month_name')}
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
|
|
@ -440,50 +444,50 @@ function DateHint({ hasChanges, handleReset }: DateHintProps) {
|
|||
|
||||
<Box style={categoryPadding} direction="Column">
|
||||
<Header size="300">
|
||||
<Text size="L400">Day of the Month</Text>
|
||||
<Text size="L400">{t('Settings.day_of_month')}</Text>
|
||||
</Header>
|
||||
<Box direction="Column" tabIndex={0} gap="100">
|
||||
<Text size="T300">
|
||||
D
|
||||
<Text as="span" size="Inherit" priority="300">
|
||||
{': '}Day of the month
|
||||
{': '}{t('Settings.day_of_month_val')}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text size="T300">
|
||||
DD
|
||||
<Text as="span" size="Inherit" priority="300">
|
||||
{': '}Two-digit day of the month
|
||||
{': '}{t('Settings.two_digit_day')}
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box style={categoryPadding} direction="Column">
|
||||
<Header size="300">
|
||||
<Text size="L400">Day of the Week</Text>
|
||||
<Text size="L400">{t('Settings.day_of_week')}</Text>
|
||||
</Header>
|
||||
<Box direction="Column" tabIndex={0} gap="100">
|
||||
<Text size="T300">
|
||||
d
|
||||
<Text as="span" size="Inherit" priority="300">
|
||||
{': '}Day of the week (Sunday = 0)
|
||||
{': '}{t('Settings.day_of_week_sunday')}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text size="T300">
|
||||
dd
|
||||
<Text as="span" size="Inherit" priority="300">
|
||||
{': '}Two-letter day name
|
||||
{': '}{t('Settings.two_letter_day')}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text size="T300">
|
||||
ddd
|
||||
<Text as="span" size="Inherit" priority="300">
|
||||
{': '}Short day name
|
||||
{': '}{t('Settings.short_day_name')}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text size="T300">
|
||||
dddd
|
||||
<Text as="span" size="Inherit" priority="300">
|
||||
{': '}Full day name
|
||||
{': '}{t('Settings.full_day_name')}
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
|
|
@ -526,6 +530,7 @@ type CustomDateFormatProps = {
|
|||
onChange: (format: string) => void;
|
||||
};
|
||||
function CustomDateFormat({ value, onChange }: CustomDateFormatProps) {
|
||||
const { t } = useTranslation();
|
||||
const [dateFormatCustom, setDateFormatCustom] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -579,7 +584,7 @@ function CustomDateFormat({ value, onChange }: CustomDateFormatProps) {
|
|||
disabled={!hasChanges}
|
||||
type="submit"
|
||||
>
|
||||
<Text size="B400">Save</Text>
|
||||
<Text size="B400">{t('Settings.save')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</SettingTile>
|
||||
|
|
@ -591,11 +596,12 @@ type PresetDateFormatProps = {
|
|||
onChange: (format: string) => void;
|
||||
};
|
||||
function PresetDateFormat({ value, onChange }: PresetDateFormatProps) {
|
||||
const { t } = useTranslation();
|
||||
const [menuCords, setMenuCords] = useState<RectCords>();
|
||||
const dateFormatItems = useDateFormatItems();
|
||||
|
||||
const getDisplayDate = (format: string): string =>
|
||||
format !== '' ? dayjs().format(format) : 'Custom';
|
||||
format !== '' ? dayjs().format(format) : t('Settings.custom');
|
||||
|
||||
const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
setMenuCords(evt.currentTarget.getBoundingClientRect());
|
||||
|
|
@ -662,6 +668,7 @@ function PresetDateFormat({ value, onChange }: PresetDateFormatProps) {
|
|||
}
|
||||
|
||||
function SelectDateFormat() {
|
||||
const { t } = useTranslation();
|
||||
const [dateFormatString, setDateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
||||
const [selectedDateFormat, setSelectedDateFormat] = useState(dateFormatString);
|
||||
const customDateFormat = selectedDateFormat === '';
|
||||
|
|
@ -676,7 +683,7 @@ function SelectDateFormat() {
|
|||
return (
|
||||
<>
|
||||
<SettingTile
|
||||
title="Date Format"
|
||||
title={t('Settings.date_format')}
|
||||
description={customDateFormat ? dayjs().format(dateFormatString) : ''}
|
||||
after={<PresetDateFormat value={selectedDateFormat} onChange={handlePresetChange} />}
|
||||
/>
|
||||
|
|
@ -688,14 +695,15 @@ function SelectDateFormat() {
|
|||
}
|
||||
|
||||
function DateAndTime() {
|
||||
const { t } = useTranslation();
|
||||
const [hour24Clock, setHour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Date & Time</Text>
|
||||
<Text size="L400">{t('Settings.date_time')}</Text>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="24-Hour Time Format"
|
||||
title={t('Settings.hour_24')}
|
||||
after={<Switch variant="Primary" value={hour24Clock} onChange={setHour24Clock} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
|
|
@ -708,32 +716,31 @@ function DateAndTime() {
|
|||
}
|
||||
|
||||
function Editor() {
|
||||
const { t } = useTranslation();
|
||||
const [enterForNewline, setEnterForNewline] = useSetting(settingsAtom, 'enterForNewline');
|
||||
const [isMarkdown, setIsMarkdown] = useSetting(settingsAtom, 'isMarkdown');
|
||||
const [hideActivity, setHideActivity] = useSetting(settingsAtom, 'hideActivity');
|
||||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Editor</Text>
|
||||
<Text size="L400">{t('Settings.editor')}</Text>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="ENTER for Newline"
|
||||
description={`Use ${
|
||||
isMacOS() ? KeySymbol.Command : 'Ctrl'
|
||||
} + ENTER to send message and ENTER for newline.`}
|
||||
title={t('Settings.enter_newline')}
|
||||
description={t('Settings.enter_newline_desc', { key: isMacOS() ? KeySymbol.Command : 'Ctrl' })}
|
||||
after={<Switch variant="Primary" value={enterForNewline} onChange={setEnterForNewline} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Markdown Formatting"
|
||||
title={t('Settings.markdown')}
|
||||
after={<Switch variant="Primary" value={isMarkdown} onChange={setIsMarkdown} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Hide Typing & Read Receipts"
|
||||
description="Turn off both typing status and read receipts to keep your activity private."
|
||||
title={t('Settings.hide_activity')}
|
||||
description={t('Settings.hide_activity_desc')}
|
||||
after={<Switch variant="Primary" value={hideActivity} onChange={setHideActivity} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
|
|
@ -880,6 +887,7 @@ function SelectMessageSpacing() {
|
|||
}
|
||||
|
||||
function Messages() {
|
||||
const { t } = useTranslation();
|
||||
const [legacyUsernameColor, setLegacyUsernameColor] = useSetting(
|
||||
settingsAtom,
|
||||
'legacyUsernameColor'
|
||||
|
|
@ -899,16 +907,16 @@ function Messages() {
|
|||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Messages</Text>
|
||||
<Text size="L400">{t('Settings.messages')}</Text>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile title="Message Layout" after={<SelectMessageLayout />} />
|
||||
<SettingTile title={t('Settings.message_layout')} after={<SelectMessageLayout />} />
|
||||
</SequenceCard>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile title="Message Spacing" after={<SelectMessageSpacing />} />
|
||||
<SettingTile title={t('Settings.message_spacing')} after={<SelectMessageSpacing />} />
|
||||
</SequenceCard>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Legacy Username Color"
|
||||
title={t('Settings.legacy_username_color')}
|
||||
after={
|
||||
<Switch
|
||||
variant="Primary"
|
||||
|
|
@ -920,7 +928,7 @@ function Messages() {
|
|||
</SequenceCard>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Hide Membership Change"
|
||||
title={t('Settings.hide_membership')}
|
||||
after={
|
||||
<Switch
|
||||
variant="Primary"
|
||||
|
|
@ -932,7 +940,7 @@ function Messages() {
|
|||
</SequenceCard>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Hide Profile Change"
|
||||
title={t('Settings.hide_profile')}
|
||||
after={
|
||||
<Switch
|
||||
variant="Primary"
|
||||
|
|
@ -944,7 +952,7 @@ function Messages() {
|
|||
</SequenceCard>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Disable Media Auto Load"
|
||||
title={t('Settings.disable_media_auto_load')}
|
||||
after={
|
||||
<Switch
|
||||
variant="Primary"
|
||||
|
|
@ -956,19 +964,19 @@ function Messages() {
|
|||
</SequenceCard>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Url Preview"
|
||||
title={t('Settings.url_preview')}
|
||||
after={<Switch variant="Primary" value={urlPreview} onChange={setUrlPreview} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Url Preview in Encrypted Room"
|
||||
title={t('Settings.url_preview_encrypted')}
|
||||
after={<Switch variant="Primary" value={encUrlPreview} onChange={setEncUrlPreview} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
|
||||
<SettingTile
|
||||
title="Show Hidden Events"
|
||||
title={t('Settings.show_hidden_events')}
|
||||
after={
|
||||
<Switch variant="Primary" value={showHiddenEvents} onChange={setShowHiddenEvents} />
|
||||
}
|
||||
|
|
@ -982,13 +990,14 @@ type GeneralProps = {
|
|||
requestClose: () => void;
|
||||
};
|
||||
export function General({ requestClose }: GeneralProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader outlined={false}>
|
||||
<Box grow="Yes" gap="200">
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<Text size="H3" truncate>
|
||||
General
|
||||
{t('Settings.general_title')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Badge, Box, Text } from 'folds';
|
||||
import { ConditionKind, IPushRules, PushRuleCondition, PushRuleKind, RuleId } from 'matrix-js-sdk';
|
||||
import { useAccountData } from '../../../hooks/useAccountData';
|
||||
|
|
@ -73,6 +74,7 @@ function AllMessagesModeSwitcher({
|
|||
}
|
||||
|
||||
export function AllMessagesNotifications() {
|
||||
const { t } = useTranslation();
|
||||
const pushRulesEvt = useAccountData(AccountDataEvent.PushRules);
|
||||
const pushRules = useMemo(
|
||||
() => pushRulesEvt?.getContent<IPushRules>() ?? { global: {} },
|
||||
|
|
@ -82,9 +84,9 @@ export function AllMessagesNotifications() {
|
|||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Box alignItems="Center" justifyContent="SpaceBetween" gap="200">
|
||||
<Text size="L400">All Messages</Text>
|
||||
<Text size="L400">{t('Settings.all_messages')}</Text>
|
||||
<Box gap="100">
|
||||
<Text size="T200">Badge: </Text>
|
||||
<Text size="T200">{t('Settings.badge')}</Text>
|
||||
<Badge radii="300" variant="Secondary" fill="Solid">
|
||||
<Text size="L400">1</Text>
|
||||
</Badge>
|
||||
|
|
@ -97,7 +99,7 @@ export function AllMessagesNotifications() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="1-to-1 Chats"
|
||||
title={t('Settings.one_to_one')}
|
||||
after={<AllMessagesModeSwitcher pushRules={pushRules} ruleId={RuleId.DM} oneToOne />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
|
|
@ -108,7 +110,7 @@ export function AllMessagesNotifications() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="1-to-1 Chats (Encrypted)"
|
||||
title={t('Settings.one_to_one_encrypted')}
|
||||
after={
|
||||
<AllMessagesModeSwitcher
|
||||
pushRules={pushRules}
|
||||
|
|
@ -126,7 +128,7 @@ export function AllMessagesNotifications() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Rooms"
|
||||
title={t('Settings.rooms')}
|
||||
after={<AllMessagesModeSwitcher pushRules={pushRules} ruleId={RuleId.Message} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
|
|
@ -137,7 +139,7 @@ export function AllMessagesNotifications() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Rooms (Encrypted)"
|
||||
title={t('Settings.rooms_encrypted')}
|
||||
after={
|
||||
<AllMessagesModeSwitcher
|
||||
pushRules={pushRules}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { ChangeEventHandler, FormEventHandler, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IPushRule, IPushRules, PushRuleKind } from 'matrix-js-sdk';
|
||||
import { Box, Text, Badge, Button, Input, config, IconButton, Icons, Icon, Spinner } from 'folds';
|
||||
import { useAccountData } from '../../../hooks/useAccountData';
|
||||
|
|
@ -21,6 +22,7 @@ const NOTIFY_MODE_OPS: NotificationModeOptions = {
|
|||
};
|
||||
|
||||
function KeywordInput() {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const [keyword, setKeyword] = useState<string>('');
|
||||
|
||||
|
|
@ -97,7 +99,7 @@ function KeywordInput() {
|
|||
disabled={addingKeyword}
|
||||
>
|
||||
{addingKeyword && <Spinner variant="Secondary" size="300" />}
|
||||
<Text size="B400">Save</Text>
|
||||
<Text size="B400">{t('Settings.save')}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
|
|
@ -146,6 +148,7 @@ function KeywordModeSwitcher({ pushRule }: PushRulesProps) {
|
|||
}
|
||||
|
||||
export function KeywordMessagesNotifications() {
|
||||
const { t } = useTranslation();
|
||||
const pushRulesEvt = useAccountData(AccountDataEvent.PushRules);
|
||||
const pushRules = useMemo(
|
||||
() => pushRulesEvt?.getContent<IPushRules>() ?? { global: {} },
|
||||
|
|
@ -162,9 +165,9 @@ export function KeywordMessagesNotifications() {
|
|||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Box alignItems="Center" justifyContent="SpaceBetween" gap="200">
|
||||
<Text size="L400">Keyword Messages</Text>
|
||||
<Text size="L400">{t('Settings.keyword_messages')}</Text>
|
||||
<Box gap="100">
|
||||
<Text size="T200">Badge: </Text>
|
||||
<Text size="T200">{t('Settings.badge')}</Text>
|
||||
<Badge radii="300" variant="Success" fill="Solid">
|
||||
<Text size="L400">1</Text>
|
||||
</Badge>
|
||||
|
|
@ -177,8 +180,8 @@ export function KeywordMessagesNotifications() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Select Keyword"
|
||||
description="Set a notification preference for message containing given keyword."
|
||||
title={t('Settings.select_keyword')}
|
||||
description={t('Settings.select_keyword_desc')}
|
||||
>
|
||||
<KeywordInput />
|
||||
</SettingTile>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from 'folds';
|
||||
import { IPushRule } from 'matrix-js-sdk';
|
||||
import React, { MouseEventHandler, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { NotificationMode, useNotificationActionsMode } from '../../../hooks/useNotificationMode';
|
||||
import { stopPropagation } from '../../../utils/keyboard';
|
||||
|
|
@ -21,15 +22,17 @@ import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
|||
export const useNotificationModes = (): NotificationMode[] =>
|
||||
useMemo(() => [NotificationMode.NotifyLoud, NotificationMode.Notify, NotificationMode.OFF], []);
|
||||
|
||||
const useNotificationModeStr = (): Record<NotificationMode, string> =>
|
||||
useMemo(
|
||||
const useNotificationModeStr = (): Record<NotificationMode, string> => {
|
||||
const { t } = useTranslation();
|
||||
return useMemo(
|
||||
() => ({
|
||||
[NotificationMode.OFF]: 'Disable',
|
||||
[NotificationMode.Notify]: 'Notify Silent',
|
||||
[NotificationMode.NotifyLoud]: 'Notify Loud',
|
||||
[NotificationMode.OFF]: t('Settings.notif_disable'),
|
||||
[NotificationMode.Notify]: t('Settings.notif_silent'),
|
||||
[NotificationMode.NotifyLoud]: t('Settings.notif_loud'),
|
||||
}),
|
||||
[]
|
||||
[t]
|
||||
);
|
||||
};
|
||||
|
||||
type NotificationModeSwitcherProps = {
|
||||
pushRule: IPushRule;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Text, IconButton, Icon, Icons, Scroll } from 'folds';
|
||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||
import { SystemNotification } from './SystemNotification';
|
||||
|
|
@ -13,13 +14,14 @@ type NotificationsProps = {
|
|||
requestClose: () => void;
|
||||
};
|
||||
export function Notifications({ requestClose }: NotificationsProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader outlined={false}>
|
||||
<Box grow="Yes" gap="200">
|
||||
<Box grow="Yes" alignItems="Center" gap="200">
|
||||
<Text size="H3" truncate>
|
||||
Notifications
|
||||
{t('Settings.notifications_title')}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No">
|
||||
|
|
@ -38,7 +40,7 @@ export function Notifications({ requestClose }: NotificationsProps) {
|
|||
<SpecialMessagesNotifications />
|
||||
<KeywordMessagesNotifications />
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Block Messages</Text>
|
||||
<Text size="L400">{t('Settings.block_messages')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
@ -46,7 +48,7 @@ export function Notifications({ requestClose }: NotificationsProps) {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
description={'This option has been moved to "Account > Block Users" section.'}
|
||||
description={t('Settings.block_messages_moved')}
|
||||
/>
|
||||
</SequenceCard>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ConditionKind, IPushRules, PushRuleKind, RuleId } from 'matrix-js-sdk';
|
||||
import { Box, Text, Badge } from 'folds';
|
||||
import { useAccountData } from '../../../hooks/useAccountData';
|
||||
|
|
@ -112,6 +113,7 @@ function MentionModeSwitcher({ ruleId, pushRules, defaultPushRuleData }: PushRul
|
|||
}
|
||||
|
||||
export function SpecialMessagesNotifications() {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const userId = mx.getUserId()!;
|
||||
const { displayName } = useUserProfile(userId);
|
||||
|
|
@ -124,9 +126,9 @@ export function SpecialMessagesNotifications() {
|
|||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Box alignItems="Center" justifyContent="SpaceBetween" gap="200">
|
||||
<Text size="L400">Special Messages</Text>
|
||||
<Text size="L400">{t('Settings.special_messages')}</Text>
|
||||
<Box gap="100">
|
||||
<Text size="T200">Badge: </Text>
|
||||
<Text size="T200">{t('Settings.badge')}</Text>
|
||||
<Badge radii="300" variant="Success" fill="Solid">
|
||||
<Text size="L400">1</Text>
|
||||
</Badge>
|
||||
|
|
@ -139,7 +141,7 @@ export function SpecialMessagesNotifications() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title={`Mention User ID ("${userId}")`}
|
||||
title={t('Settings.mention_user_id', { userId })}
|
||||
after={
|
||||
<MentionModeSwitcher
|
||||
pushRules={pushRules}
|
||||
|
|
@ -156,7 +158,7 @@ export function SpecialMessagesNotifications() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title={`Contains Displayname ${displayName ? `("${displayName}")` : ''}`}
|
||||
title={displayName ? t('Settings.contains_displayname_value', { displayName }) : t('Settings.contains_displayname')}
|
||||
after={
|
||||
<MentionModeSwitcher
|
||||
pushRules={pushRules}
|
||||
|
|
@ -173,7 +175,7 @@ export function SpecialMessagesNotifications() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title={`Contains Username ("${getMxIdLocalPart(userId)}")`}
|
||||
title={t('Settings.contains_username', { username: getMxIdLocalPart(userId) })}
|
||||
after={
|
||||
<MentionModeSwitcher
|
||||
pushRules={pushRules}
|
||||
|
|
@ -190,7 +192,7 @@ export function SpecialMessagesNotifications() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Mention @room"
|
||||
title={t('Settings.mention_room')}
|
||||
after={
|
||||
<MentionModeSwitcher
|
||||
pushRules={pushRules}
|
||||
|
|
@ -207,7 +209,7 @@ export function SpecialMessagesNotifications() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Contains @room"
|
||||
title={t('Settings.contains_room')}
|
||||
after={
|
||||
<MentionModeSwitcher
|
||||
pushRules={pushRules}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Text, Switch, Button, color, Spinner } from 'folds';
|
||||
import { IPusherRequest } from 'matrix-js-sdk';
|
||||
import { SequenceCard } from '../../../components/sequence-card';
|
||||
|
|
@ -12,6 +13,7 @@ import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
|||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
|
||||
function EmailNotification() {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const [result, refreshResult] = useEmailNotifications();
|
||||
|
||||
|
|
@ -53,21 +55,21 @@ function EmailNotification() {
|
|||
|
||||
return (
|
||||
<SettingTile
|
||||
title="Email Notification"
|
||||
title={t('Settings.email_notification')}
|
||||
description={
|
||||
<>
|
||||
{result && !result.email && (
|
||||
<Text as="span" style={{ color: color.Critical.Main }} size="T200">
|
||||
Your account does not have any email attached.
|
||||
{t('Settings.email_no_email')}
|
||||
</Text>
|
||||
)}
|
||||
{result && result.email && <>Send notification to your email. {`("${result.email}")`}</>}
|
||||
{result && result.email && t('Settings.email_send_notif_to', { email: result.email })}
|
||||
{result === null && (
|
||||
<Text as="span" style={{ color: color.Critical.Main }} size="T200">
|
||||
Unexpected Error!
|
||||
{t('Settings.unexpected_error')}
|
||||
</Text>
|
||||
)}
|
||||
{result === undefined && 'Send notification to your email.'}
|
||||
{result === undefined && t('Settings.email_send_notif')}
|
||||
</>
|
||||
}
|
||||
after={
|
||||
|
|
@ -85,6 +87,7 @@ function EmailNotification() {
|
|||
}
|
||||
|
||||
export function SystemNotification() {
|
||||
const { t } = useTranslation();
|
||||
const notifPermission = usePermissionState('notifications', getNotificationState());
|
||||
const [showNotifications, setShowNotifications] = useSetting(settingsAtom, 'showNotifications');
|
||||
const [isNotificationSounds, setIsNotificationSounds] = useSetting(
|
||||
|
|
@ -98,7 +101,7 @@ export function SystemNotification() {
|
|||
|
||||
return (
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">System</Text>
|
||||
<Text size="L400">{t('Settings.system')}</Text>
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
variant="SurfaceVariant"
|
||||
|
|
@ -106,22 +109,22 @@ export function SystemNotification() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Desktop Notifications"
|
||||
title={t('Settings.desktop_notifications')}
|
||||
description={
|
||||
notifPermission === 'denied' ? (
|
||||
<Text as="span" style={{ color: color.Critical.Main }} size="T200">
|
||||
{'Notification' in window
|
||||
? 'Notification permission is blocked. Please allow notification permission from browser address bar.'
|
||||
: 'Notifications are not supported by the system.'}
|
||||
? t('Settings.notif_permission_blocked')
|
||||
: t('Settings.notif_not_supported')}
|
||||
</Text>
|
||||
) : (
|
||||
<span>Show desktop notifications when message arrive.</span>
|
||||
<span>{t('Settings.notif_show_desktop')}</span>
|
||||
)
|
||||
}
|
||||
after={
|
||||
notifPermission === 'prompt' ? (
|
||||
<Button size="300" radii="300" onClick={requestNotificationPermission}>
|
||||
<Text size="B300">Enable</Text>
|
||||
<Text size="B300">{t('Settings.enable')}</Text>
|
||||
</Button>
|
||||
) : (
|
||||
<Switch
|
||||
|
|
@ -140,8 +143,8 @@ export function SystemNotification() {
|
|||
gap="400"
|
||||
>
|
||||
<SettingTile
|
||||
title="Notification Sound"
|
||||
description="Play sound when new message arrive."
|
||||
title={t('Settings.notification_sound')}
|
||||
description={t('Settings.notification_sound_desc')}
|
||||
after={<Switch value={isNotificationSounds} onChange={setIsNotificationSounds} />}
|
||||
/>
|
||||
</SequenceCard>
|
||||
|
|
|
|||
|
|
@ -374,7 +374,10 @@ export function PasswordRegisterForm({
|
|||
<Text size="T300">
|
||||
<Trans
|
||||
i18nKey="Auth.register_terms"
|
||||
components={{ termsLink: <a href={termUrl} target="_blank" rel="noreferrer" /> }}
|
||||
components={{
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label -- Trans fills content at runtime
|
||||
termsLink: <a href={termUrl} target="_blank" rel="noreferrer" />,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
</Box>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue