diff --git a/public/locales/de.json b/public/locales/de.json deleted file mode 100644 index c7167438..00000000 --- a/public/locales/de.json +++ /dev/null @@ -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 Nutzungsbedingungen 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 {{server}} 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" - } -} diff --git a/public/locales/en.json b/public/locales/en.json index d7e0c1df..fa79e936 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -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" } } diff --git a/public/locales/ru.json b/public/locales/ru.json index 043d2a3d..f4dfbf59 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -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": "Я принимаю Условия использования сервера.", "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": "Добавить" } } diff --git a/src/app/features/settings/Settings.tsx b/src/app/features/settings/Settings.tsx index 5e1a20f4..7140f4bf 100644 --- a/src/app/features/settings/Settings.tsx +++ b/src/app/features/settings/Settings.tsx @@ -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) { /> - Settings + {t('Settings.title')} @@ -152,7 +122,7 @@ export function Settings({ initialPage, requestClose }: SettingsProps) {
{menuItems.map((item) => ( - {item.name} + {t(item.nameKey)} ))} @@ -184,7 +154,7 @@ export function Settings({ initialPage, requestClose }: SettingsProps) { before={} onClick={() => setLogout(true)} > - Logout + {t('Settings.logout')} {logout && ( }> diff --git a/src/app/features/settings/about/About.tsx b/src/app/features/settings/about/About.tsx index 19b29398..14cf50bf 100644 --- a/src/app/features/settings/about/About.tsx +++ b/src/app/features/settings/about/About.tsx @@ -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) { - About + {t('Settings.about_title')} @@ -48,13 +50,13 @@ export function About({ requestClose }: AboutProps) { Vojo v4.11.1 - Yet another matrix client. + {t('Settings.about_tagline')} - Options + {t('Settings.options')} clearCacheAndReload(mx)} @@ -73,14 +75,14 @@ export function About({ requestClose }: AboutProps) { radii="300" outlined > - Clear Cache + {t('Settings.clear_cache')} } /> - Credits + {t('Settings.credits')} void; }; export function Account({ requestClose }: AccountProps) { + const { t } = useTranslation(); return ( - Account + {t('Settings.account_title')} diff --git a/src/app/features/settings/account/ContactInfo.tsx b/src/app/features/settings/account/ContactInfo.tsx index cfde7e29..4a06b2a8 100644 --- a/src/app/features/settings/account/ContactInfo.tsx +++ b/src/app/features/settings/account/ContactInfo.tsx @@ -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 ( - Contact Information + {t('Settings.contact_info')} - + {emailIds?.map((email) => ( diff --git a/src/app/features/settings/account/IgnoredUserList.tsx b/src/app/features/settings/account/IgnoredUserList.tsx index 98db9459..5b0c1f56 100644 --- a/src/app/features/settings/account/IgnoredUserList.tsx +++ b/src/app/features/settings/account/IgnoredUserList.tsx @@ -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(''); const alive = useAlive(); @@ -89,7 +91,7 @@ function IgnoreUserInput({ userList }: { userList: string[] }) { disabled={ignoring} > {ignoring && } - Block + {t('Settings.block')} ); @@ -129,12 +131,13 @@ function IgnoredUserChip({ userId, userList }: { userId: string; userList: strin } export function IgnoredUserList() { + const { t } = useTranslation(); const ignoredUsers = useIgnoredUsers(); return ( - Blocked Users + {t('Settings.blocked_users')} {ignoredUsers.length > 0 && ( - Users + {t('Settings.users')} {ignoredUsers.map((userId) => ( diff --git a/src/app/features/settings/account/MatrixId.tsx b/src/app/features/settings/account/MatrixId.tsx index 0c06a75d..c8fea158 100644 --- a/src/app/features/settings/account/MatrixId.tsx +++ b/src/app/features/settings/account/MatrixId.tsx @@ -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 ( - Matrix ID + {t('Settings.matrix_id')} copyToClipboard(userId)}> - Copy + {t('Settings.copy')} } /> diff --git a/src/app/features/settings/account/Profile.tsx b/src/app/features/settings/account/Profile.tsx index e982a799..e68dfd21 100644 --- a/src/app/features/settings/account/Profile.tsx +++ b/src/app/features/settings/account/Profile.tsx @@ -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) { - Avatar + {t('Settings.avatar')} } after={ @@ -123,7 +125,7 @@ function ProfileAvatar({ profile, userId }: ProfileProps) { radii="300" disabled={disableSetAvatar} > - Upload + {t('Settings.upload')} {avatarUrl && ( )} @@ -183,7 +185,7 @@ function ProfileAvatar({ profile, userId }: ProfileProps) { size="500" > - Remove Avatar + {t('Settings.remove_avatar')} setAlertRemove(false)} radii="300"> @@ -191,10 +193,10 @@ function ProfileAvatar({ profile, userId }: ProfileProps) { - Are you sure you want to remove profile avatar? + {t('Settings.remove_avatar_confirm')} @@ -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) { - Display Name + {t('Settings.display_name')} } > @@ -295,7 +298,7 @@ function ProfileDisplayName({ profile, userId }: ProfileProps) { type="submit" > {changingDisplayName && } - Save + {t('Settings.save')} @@ -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 ( - Profile + {t('Settings.profile')} 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 ( - Account Data + {t('Settings.account_data')} onExpandToggle(!expand)} @@ -49,15 +51,15 @@ export function AccountData({ expand, onExpandToggle, onSelect }: AccountDataPro } > - {expand ? 'Collapse' : 'Expand'} + {expand ? t('Settings.collapse') : t('Settings.expand')} } /> {expand && ( - Events - Total: {accountDataTypes.length} + {t('Settings.events')} + {t('Settings.total', { count: accountDataTypes.length })} - Add New + {t('Settings.add_new')} diff --git a/src/app/features/settings/developer-tools/DevelopTools.tsx b/src/app/features/settings/developer-tools/DevelopTools.tsx index a3f04567..de6f202e 100644 --- a/src/app/features/settings/developer-tools/DevelopTools.tsx +++ b/src/app/features/settings/developer-tools/DevelopTools.tsx @@ -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) { - Developer Tools + {t('Settings.devtools_title')} @@ -62,7 +64,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) { - Options + {t('Settings.options')} @@ -101,7 +103,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) { radii="300" outlined > - Copy + {t('Settings.copy')} } /> diff --git a/src/app/features/settings/devices/DeviceTile.tsx b/src/app/features/settings/devices/DeviceTile.tsx index 71b684f5..601da394 100644 --- a/src/app/features/settings/devices/DeviceTile.tsx +++ b/src/app/features/settings/devices/DeviceTile.tsx @@ -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 ( - {'Last activity: '} + {t('Settings.last_activity')} <> - {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' && ( - Device ID: {device.device_id} + {t('Settings.device_id')}{device.device_id} )} {typeof device.last_seen_ip === 'string' && ( - IP Address: {device.last_seen_ip} + {t('Settings.ip_address')}{device.last_seen_ip} )} @@ -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 ( - Device Key:{' '} - {keysState.status === AsyncStatus.Success ? keysState.data.ed25519 : 'loading...'} + {t('Settings.device_key')} + {keysState.status === AsyncStatus.Success ? keysState.data.ed25519 : t('Settings.loading')} ); } @@ -110,6 +114,7 @@ type DeviceRenameProps = { refreshDeviceList: () => Promise; }; function DeviceRename({ device, onCancel, onRename, refreshDeviceList }: DeviceRenameProps) { + const { t } = useTranslation(); const mx = useMatrixClient(); const [renameState, rename] = useAsyncCallback( @@ -145,7 +150,7 @@ function DeviceRename({ device, onCancel, onRename, refreshDeviceList }: DeviceR return ( - Device Name + {t('Settings.device_name')} } > - Save + {t('Settings.save')} @@ -189,13 +194,14 @@ function DeviceRename({ device, onCancel, onRename, refreshDeviceList }: DeviceR {renameState.error.message} ) : ( - Device names are visible to public. + {t('Settings.device_name_public')} )} ); } export function DeviceLogoutBtn() { + const { t } = useTranslation(); const [prompt, setPrompt] = useState(false); const handleClose = () => setPrompt(false); @@ -203,7 +209,7 @@ export function DeviceLogoutBtn() { return ( <> setPrompt(true)}> - Logout + {t('Settings.logout')} {prompt && ( }> @@ -236,6 +242,7 @@ export function DeviceDeleteBtn({ onDeleteToggle, disabled, }: DeviceDeleteBtnProps) { + const { t } = useTranslation(); return deleted ? ( onDeleteToggle(deviceId)} disabled={disabled} > - Undo + {t('Settings.undo')} ) : ( setEdit(true)} disabled={disabled} > - Edit + {t('Settings.edit')} )} diff --git a/src/app/features/settings/devices/Devices.tsx b/src/app/features/settings/devices/Devices.tsx index c957ab31..83a9e8d9 100644 --- a/src/app/features/settings/devices/Devices.tsx +++ b/src/app/features/settings/devices/Devices.tsx @@ -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) { - Devices + {t('Settings.devices_title')} @@ -85,7 +87,7 @@ export function Devices({ requestClose }: DevicesProps) { - Security + {t('Settings.security')} @@ -113,7 +115,7 @@ export function Devices({ requestClose }: DevicesProps) { - Current + {t('Settings.current')} {currentDevice ? ( ( <> - New Password + {t('Settings.new_password')} - Confirm Password + {t('Settings.confirm_password')} : undefined} > - Export + {t('Settings.export')} @@ -122,13 +124,14 @@ function ExportKeys() { } function ExportKeysTile() { + const { t } = useTranslation(); const [expand, setExpand] = useState(false); return ( <> @@ -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) { - Password + {t('Settings.password')} : undefined} > - Decrypt + {t('Settings.decrypt')} @@ -246,6 +250,7 @@ function ImportKeys({ file, onDone }: ImportKeysProps) { } function ImportKeysTile() { + const { t } = useTranslation(); const [file, setFile] = useState(); const pickFile = useFilePicker(setFile); @@ -256,8 +261,8 @@ function ImportKeysTile() { return ( <> {file ? ( @@ -288,7 +293,7 @@ function ImportKeysTile() { before={} > - Import + {t('Settings.import')} )} @@ -301,9 +306,10 @@ function ImportKeysTile() { } export function LocalBackup() { + const { t } = useTranslation(); return ( - Local Backup + {t('Settings.local_backup')} 0 ? ( <> - Others + {t('Settings.others')} {authMetadata && ( - Open + {t('Settings.open')} } /> @@ -198,11 +200,11 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O {deleteError ? ( - Failed to logout devices! Please try again. {deleteError.message} + {t('Settings.delete_devices_error', { message: deleteError.message })} ) : ( - Logout from selected devices. ({deleted.size} selected) + {t('Settings.delete_devices_confirm', { count: deleted.size })} )} {authData && ( @@ -210,7 +212,7 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O authData={authData} unsupported={() => ( - Authentication steps to perform this action are not supported by client. + {t('Settings.delete_devices_unsupported')} )} > @@ -234,7 +236,7 @@ export function OtherDevices({ devices, refreshDeviceList, showVerification }: O disabled={deleting} onClick={handleCancelDelete} > - Cancel + {t('Settings.cancel')} diff --git a/src/app/features/settings/devices/Verification.tsx b/src/app/features/settings/devices/Verification.tsx index 6c7eab17..a7b3211c 100644 --- a/src/app/features/settings/devices/Verification.tsx +++ b/src/app/features/settings/devices/Verification.tsx @@ -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 ( - Unverified + {t('Settings.unverified')} ); } @@ -61,36 +63,37 @@ export function VerificationStatusBadge({ if (otherUnverifiedCount > 0) { return ( - {otherUnverifiedCount} Unverified + {t('Settings.unverified_count', { count: otherUnverifiedCount })} ); } return ( - Verified + {t('Settings.verified')} ); } function LearnStartVerificationFromOtherDevice() { + const { t } = useTranslation(); return ( - Steps to verify from other device. + {t('Settings.verify_steps_title')}
    -
  • Open your other verified device.
  • +
  • {t('Settings.verify_step_1')}
  • - Open Settings. + Open {t('Settings.verify_step_2_settings')}.
  • - Find this device in Devices/Sessions section. + Find this device in {t('Settings.verify_step_3_section')} section.
  • -
  • Initiate verification.
  • +
  • {t('Settings.verify_step_4')}
- If you do not have any verified device press the "Verify Manually" button. + {t('Settings.verify_no_device')}
); @@ -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({ <> - Start verification from other device or verify manually.{' '} + {t('Settings.verify_current_desc')}{' '} setLearnMore(!learnMore)}> - {learnMore ? 'View Less' : 'Learn More'} + {learnMore ? t('Settings.view_less') : t('Settings.learn_more')} } @@ -133,7 +137,7 @@ export function VerifyCurrentDeviceTile({ onClick={() => setManualVerification(true)} > - Verify Manually + {t('Settings.verify_manually')} ) @@ -167,6 +171,7 @@ type VerifyOtherDeviceTileProps = { deviceId: string; }; export function VerifyOtherDeviceTile({ crypto, deviceId }: VerifyOtherDeviceTileProps) { + const { t } = useTranslation(); const mx = useMatrixClient(); const [requestState, setRequestState] = useState>({ status: AsyncStatus.Idle, @@ -190,8 +195,8 @@ export function VerifyOtherDeviceTile({ crypto, deviceId }: VerifyOtherDeviceTil return ( - Verify + {t('Settings.verify')} } @@ -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 && ( )} @@ -254,6 +260,7 @@ export function EnableVerification({ visible }: EnableVerificationProps) { } export function DeviceVerificationOptions() { + const { t } = useTranslation(); const [menuCords, setMenuCords] = useState(); const authMetadata = useAuthMetadata(); const accountManagementActions = useAccountManagementActions(); @@ -324,7 +331,7 @@ export function DeviceVerificationOptions() { fill="None" > - Reset + {t('Settings.reset')}
diff --git a/src/app/features/settings/emojis-stickers/EmojisStickers.tsx b/src/app/features/settings/emojis-stickers/EmojisStickers.tsx index 93715120..5bb84b37 100644 --- a/src/app/features/settings/emojis-stickers/EmojisStickers.tsx +++ b/src/app/features/settings/emojis-stickers/EmojisStickers.tsx @@ -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(); const handleImagePackViewClose = () => { @@ -26,7 +28,7 @@ export function EmojisStickers({ requestClose }: EmojisStickersProps) { - Emojis & Stickers + {t('Settings.emojis_stickers_title')} diff --git a/src/app/features/settings/emojis-stickers/GlobalPacks.tsx b/src/app/features/settings/emojis-stickers/GlobalPacks.tsx index a9288728..29536ac4 100644 --- a/src/app/features/settings/emojis-stickers/GlobalPacks.tsx +++ b/src/app/features/settings/emojis-stickers/GlobalPacks.tsx @@ -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(); @@ -107,7 +109,7 @@ function GlobalPackSelector({
- Room Packs + {t('Settings.room_packs')} @@ -117,7 +119,7 @@ function GlobalPackSelector({ outlined={hasSelected} onClick={() => onSelect(selected)} > - {hasSelected ? 'Save' : 'Close'} + {hasSelected ? t('Settings.save') : t('Settings.close')}
@@ -162,7 +164,7 @@ function GlobalPackSelector({ addSelected(roomPackAddresses); }} > - {allSelected ? 'Unselect All' : 'Select All'} + {allSelected ? t('Settings.unselect_all') : t('Settings.select_all')}
@@ -184,7 +186,7 @@ function GlobalPackSelector({ gap="400" > {pack.meta.attribution}} before={ @@ -232,10 +234,10 @@ function GlobalPackSelector({ }} > - No Packs + {t('Settings.no_packs')} - Pack from rooms will appear here. You do not have any room with packs yet. + {t('Settings.no_packs_desc')}
@@ -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) { - {pack.meta.name ?? 'Unknown'} + {pack.meta.name ?? t('Settings.unknown')} } description={{pack.meta.attribution}} @@ -408,7 +411,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) { outlined onClick={() => onViewPack(pack)} > - View + {t('Settings.view')} ) } @@ -420,7 +423,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) { return ( <> - Favorite Packs + {t('Settings.favorite_packs')} {applyState.status === AsyncStatus.Error ? ( - Failed to apply changes! Please try again. + {t('Settings.apply_error')} ) : ( - Changes saved! Apply when ready. + {t('Settings.apply_ready')} )} @@ -519,7 +522,7 @@ export function GlobalPacks({ onViewPack }: GlobalPacksProps) { disabled={applyingChanges} onClick={resetChanges} > - Reset + {t('Settings.reset')}
diff --git a/src/app/features/settings/emojis-stickers/UserPack.tsx b/src/app/features/settings/emojis-stickers/UserPack.tsx index c41c8e18..33e8fa7f 100644 --- a/src/app/features/settings/emojis-stickers/UserPack.tsx +++ b/src/app/features/settings/emojis-stickers/UserPack.tsx @@ -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 ( - Default Pack + {t('Settings.default_pack')} @@ -61,7 +63,7 @@ export function UserPack({ onViewPack }: UserPackProps) { outlined onClick={handleView} > - View + {t('Settings.view')} } /> diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx index b861a060..a775f605 100644 --- a/src/app/features/settings/general/General.tsx +++ b/src/app/features/settings/general/General.tsx @@ -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 ( - Appearance + {t('Settings.appearance')} } /> {systemTheme && } @@ -327,28 +330,28 @@ function Appearance() { } /> } /> } /> - } /> + } /> ); @@ -359,6 +362,7 @@ type DateHintProps = { handleReset: () => void; }; function DateHint({ hasChanges, handleReset }: DateHintProps) { + const { t } = useTranslation(); const [anchor, setAnchor] = useState(); const categoryPadding = { padding: config.space.S200, paddingTop: 0 }; @@ -381,26 +385,26 @@ function DateHint({ hasChanges, handleReset }: DateHintProps) { >
- Formatting + {t('Settings.formatting')}
- Year + {t('Settings.year')}
YY {': '} - Two-digit year + {t('Settings.two_digit_year')} {' '} YYYY - {': '}Four-digit year + {': '}{t('Settings.four_digit_year')} @@ -408,31 +412,31 @@ function DateHint({ hasChanges, handleReset }: DateHintProps) {
- Month + {t('Settings.month')}
M - {': '}The month + {': '}{t('Settings.the_month')} MM - {': '}Two-digit month + {': '}{t('Settings.two_digit_month')} {' '} MMM - {': '}Short month name + {': '}{t('Settings.short_month_name')} MMMM - {': '}Full month name + {': '}{t('Settings.full_month_name')} @@ -440,50 +444,50 @@ function DateHint({ hasChanges, handleReset }: DateHintProps) {
- Day of the Month + {t('Settings.day_of_month')}
D - {': '}Day of the month + {': '}{t('Settings.day_of_month_val')} DD - {': '}Two-digit day of the month + {': '}{t('Settings.two_digit_day')}
- Day of the Week + {t('Settings.day_of_week')}
d - {': '}Day of the week (Sunday = 0) + {': '}{t('Settings.day_of_week_sunday')} dd - {': '}Two-letter day name + {': '}{t('Settings.two_letter_day')} ddd - {': '}Short day name + {': '}{t('Settings.short_day_name')} dddd - {': '}Full day name + {': '}{t('Settings.full_day_name')} @@ -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" > - Save + {t('Settings.save')}
@@ -591,11 +596,12 @@ type PresetDateFormatProps = { onChange: (format: string) => void; }; function PresetDateFormat({ value, onChange }: PresetDateFormatProps) { + const { t } = useTranslation(); const [menuCords, setMenuCords] = useState(); const dateFormatItems = useDateFormatItems(); const getDisplayDate = (format: string): string => - format !== '' ? dayjs().format(format) : 'Custom'; + format !== '' ? dayjs().format(format) : t('Settings.custom'); const handleMenu: MouseEventHandler = (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 ( <> } /> @@ -688,14 +695,15 @@ function SelectDateFormat() { } function DateAndTime() { + const { t } = useTranslation(); const [hour24Clock, setHour24Clock] = useSetting(settingsAtom, 'hour24Clock'); return ( - Date & Time + {t('Settings.date_time')} } /> @@ -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 ( - Editor + {t('Settings.editor')} } /> } /> } /> @@ -880,6 +887,7 @@ function SelectMessageSpacing() { } function Messages() { + const { t } = useTranslation(); const [legacyUsernameColor, setLegacyUsernameColor] = useSetting( settingsAtom, 'legacyUsernameColor' @@ -899,16 +907,16 @@ function Messages() { return ( - Messages + {t('Settings.messages')} - } /> + } /> - } /> + } /> } /> } /> } @@ -982,13 +990,14 @@ type GeneralProps = { requestClose: () => void; }; export function General({ requestClose }: GeneralProps) { + const { t } = useTranslation(); return ( - General + {t('Settings.general_title')} diff --git a/src/app/features/settings/notifications/AllMessages.tsx b/src/app/features/settings/notifications/AllMessages.tsx index 301cb9a7..ed28fb40 100644 --- a/src/app/features/settings/notifications/AllMessages.tsx +++ b/src/app/features/settings/notifications/AllMessages.tsx @@ -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() ?? { global: {} }, @@ -82,9 +84,9 @@ export function AllMessagesNotifications() { return ( - All Messages + {t('Settings.all_messages')} - Badge: + {t('Settings.badge')} 1 @@ -97,7 +99,7 @@ export function AllMessagesNotifications() { gap="400" > } /> @@ -108,7 +110,7 @@ export function AllMessagesNotifications() { gap="400" > } /> @@ -137,7 +139,7 @@ export function AllMessagesNotifications() { gap="400" > (''); @@ -97,7 +99,7 @@ function KeywordInput() { disabled={addingKeyword} > {addingKeyword && } - Save + {t('Settings.save')} ); @@ -146,6 +148,7 @@ function KeywordModeSwitcher({ pushRule }: PushRulesProps) { } export function KeywordMessagesNotifications() { + const { t } = useTranslation(); const pushRulesEvt = useAccountData(AccountDataEvent.PushRules); const pushRules = useMemo( () => pushRulesEvt?.getContent() ?? { global: {} }, @@ -162,9 +165,9 @@ export function KeywordMessagesNotifications() { return ( - Keyword Messages + {t('Settings.keyword_messages')} - Badge: + {t('Settings.badge')} 1 @@ -177,8 +180,8 @@ export function KeywordMessagesNotifications() { gap="400" > diff --git a/src/app/features/settings/notifications/NotificationModeSwitcher.tsx b/src/app/features/settings/notifications/NotificationModeSwitcher.tsx index fe008390..5987ff62 100644 --- a/src/app/features/settings/notifications/NotificationModeSwitcher.tsx +++ b/src/app/features/settings/notifications/NotificationModeSwitcher.tsx @@ -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 => - useMemo( +const useNotificationModeStr = (): Record => { + 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; diff --git a/src/app/features/settings/notifications/Notifications.tsx b/src/app/features/settings/notifications/Notifications.tsx index 095a9bba..2ef2dcbb 100644 --- a/src/app/features/settings/notifications/Notifications.tsx +++ b/src/app/features/settings/notifications/Notifications.tsx @@ -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 ( - Notifications + {t('Settings.notifications_title')} @@ -38,7 +40,7 @@ export function Notifications({ requestClose }: NotificationsProps) { - Block Messages + {t('Settings.block_messages')} Block Users" section.'} + description={t('Settings.block_messages_moved')} /> diff --git a/src/app/features/settings/notifications/SpecialMessages.tsx b/src/app/features/settings/notifications/SpecialMessages.tsx index cbebf648..7ab38017 100644 --- a/src/app/features/settings/notifications/SpecialMessages.tsx +++ b/src/app/features/settings/notifications/SpecialMessages.tsx @@ -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 ( - Special Messages + {t('Settings.special_messages')} - Badge: + {t('Settings.badge')} 1 @@ -139,7 +141,7 @@ export function SpecialMessagesNotifications() { gap="400" > {result && !result.email && ( - Your account does not have any email attached. + {t('Settings.email_no_email')} )} - {result && result.email && <>Send notification to your email. {`("${result.email}")`}} + {result && result.email && t('Settings.email_send_notif_to', { email: result.email })} {result === null && ( - Unexpected Error! + {t('Settings.unexpected_error')} )} - {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 ( - System + {t('Settings.system')} {'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')} ) : ( - Show desktop notifications when message arrive. + {t('Settings.notif_show_desktop')} ) } after={ notifPermission === 'prompt' ? ( ) : ( } /> diff --git a/src/app/pages/auth/register/PasswordRegisterForm.tsx b/src/app/pages/auth/register/PasswordRegisterForm.tsx index 6c7eeaba..d1af9bde 100644 --- a/src/app/pages/auth/register/PasswordRegisterForm.tsx +++ b/src/app/pages/auth/register/PasswordRegisterForm.tsx @@ -374,7 +374,10 @@ export function PasswordRegisterForm({ }} + components={{ + // eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label -- Trans fills content at runtime + termsLink: , + }} />