diff --git a/package.json b/package.json index 0ce0152..059fed2 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "ra-language-german": "^3.13.4", "ra-language-italian": "^3.13.1", "ra-language-farsi": "^4.2.0", + "ra-language-russian": "^4.14.2", "react": "^18.0.0", "react-admin": "^4.16.15", "react-dom": "^18.0.0", diff --git a/src/App.jsx b/src/App.jsx index 1eff2a7..352fb0a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -23,6 +23,7 @@ import englishMessages from "./i18n/en"; import frenchMessages from "./i18n/fr"; import chineseMessages from "./i18n/zh"; import italianMessages from "./i18n/it"; +import russianMessages from "./i18n/ru"; // TODO: Can we use lazy loading together with browser locale? const messages = { @@ -31,6 +32,7 @@ const messages = { fr: frenchMessages, it: italianMessages, zh: chineseMessages, + ru: russianMessages }; const i18nProvider = polyglotI18nProvider( locale => (messages[locale] ? messages[locale] : messages.en), diff --git a/src/components/LoginPage.jsx b/src/components/LoginPage.jsx index b540515..82a8772 100644 --- a/src/components/LoginPage.jsx +++ b/src/components/LoginPage.jsx @@ -293,6 +293,7 @@ const LoginPage = () => { Italiano 简体中文 Persian(فارسی) + Русский {formDataProps => } diff --git a/src/i18n/ru.js b/src/i18n/ru.js new file mode 100644 index 0000000..c14439d --- /dev/null +++ b/src/i18n/ru.js @@ -0,0 +1,390 @@ +import russianMessages from "ra-language-russian"; + +const ru = { + ...russianMessages, + synapseadmin: { + auth: { + base_url: "Домашняя страница", + welcome: "Добро пожаловать в Synapse-admin", + server_version: "Версия Synapse", + supports_specs: "поддерживает спецификации Matrix", + username_error: "Введите полный идентификатор пользователя: '@user:domain'", + protocol_error: "Адрес должен начинаться с 'http://' или 'https://'", + url_error: "Некорректный сервер Matrix", + sso_sign_in: "Присоединиться с помощью технологии единого входа", + }, + users: { + invalid_user_id: "Локальная часть идентификатора пользователя Matrix без домашнего сервера.", + tabs: { sso: "Технология единого входа" }, + }, + rooms: { + tabs: { + basic: "Основное", + members: "Участники", + detail: "Подробности", + permission: "Права", + }, + }, + reports: { tabs: { basic: "Основное", detail: "Подробности" } }, + }, + import_users: { + error: { + at_entry: "При входе %{entry}: %{message}", + error: "Ошибка", + required_field: "Обязательное поле '%{field}' не представленно", + invalid_value: + "Недопустимое значение в строке %{row}. '%{field}' поле может быть только 'true' или 'false'", + unreasonably_big: + "Отказался загружать неоправданно большой файл %{size} Мбайт", + already_in_progress: "Импорт уже запущен", + id_exits: "Идентификатор %{id} уже существует", + }, + title: "Импорт пользователей из CSV", + goToPdf: "В PDF", + cards: { + importstats: { + header: "Импорт пользователей", + users_total: + "%{smart_count} пользователь в CSV файл |||| %{smart_count} пользователей в CSV файл", + guest_count: "%{smart_count} гость |||| %{smart_count} гостей", + admin_count: "%{smart_count} администратор |||| %{smart_count} администраторов", + }, + conflicts: { + header: "Решение конфликтов", + mode: { + stop: "Остановиться, если конфликт произошел", + skip: "Вывести ошибку и пропустить конфликт", + }, + }, + ids: { + header: "Идентификаторы", + all_ids_present: "Идентификаторы представлены для каждой записи", + count_ids_present: + "%{smart_count} запись с идентификатором |||| %{smart_count} записей с идентификатором", + mode: { + ignore: "Игнорировать идентификаторы в CSV и создавать новые", + update: "Обновлять существующие записи", + }, + }, + passwords: { + header: "Пароли", + all_passwords_present: "Пароли представлены для каждой записи", + count_passwords_present: + "%{smart_count} запись с паролем |||| %{smart_count} записей с паролем", + use_passwords: "Использовать пароли из CSV", + }, + upload: { + header: "Загрузка CSV файла", + explanation: + "Здесь вы можете загрузить файл со значениями, разделенными запятыми, который будет обработан для создания или обновления пользователей. Файл должен содержать поля 'id' и 'displayname'. Вы можете загрузить пример здесь:", + }, + startImport: { + simulate_only: "Не выполнять реальных действий", + run_import: "Импорт", + }, + results: { + header: "Импорт результатов", + total: + "Всего %{smart_count} запись |||| Всего %{smart_count} записей", + successful: "%{smart_count} записей успешно импортировано", + skipped: "%{smart_count} записей пропущено", + download_skipped: "Загрузить пропущенные записи", + with_error: + "%{smart_count} запись с ошибками ||| %{smart_count} записей с ошибками", + simulated_only: "Результат не будет сохранен", + }, + }, + }, + resources: { + users: { + name: "Пользователь |||| Пользователи", + email: "Почта", + msisdn: "Телефон", + threepid: "Почта / Телефон", + fields: { + avatar: "Аватар", + id: "Идентификатор пользователя", + name: "Имя", + is_guest: "Гость", + admin: "Администратор", + deactivated: "Деактивирован", + guests: "Показать гостей", + show_deactivated: "Показать деактивированных пользователей", + user_id: "Найти пользователя", + displayname: "Отображаемое имя", + password: "Пароль", + avatar_url: "Ссылка на аватар", + avatar_src: "Аватар", + medium: "Тип", + threepids: "Иной идентификатор", + address: "Адрес", + creation_ts_ms: "Время создания", + consent_version: "Версия соглашения", + auth_provider: "Поставщик", + user_type: "Тип пользователя", + }, + helper: { + password: "Изменение пароля приведет к выходу пользователя из всех сеансов.", + deactivate: "Вы должны ввести пароль для повторной активации учетной записи.", + erase: "Пометить пользователя как удаленного в связи с защитой персональных данных", + }, + action: { + erase: "Удаление пользовательских данных", + }, + }, + rooms: { + name: "Комната |||| Комнаты", + fields: { + room_id: "Идентификатор комнаты", + name: "Название", + canonical_alias: "Псевдоним", + joined_members: "Участники", + joined_local_members: "Внутренние участники", + joined_local_devices: "Используемые устройства", + state_events: "События изменения состояния", + version: "Версия", + is_encrypted: "Зашифрованно", + encryption: "Шифрование", + federatable: "Федерация", + public: "Видимость в списке комнат", + creator: "Создатель", + join_rules: "Правила присоединения", + guest_access: "Гостевой доступ", + history_visibility: "Видимость истории", + topic: "Тема", + avatar: "Аватар", + }, + helper: { + forward_extremities: + "Перенаправленные заключения, это такие события, которые не имеют потомков в рамках графа, отвечающего за их репрезентацию. Чем больше пользователей находится в комнате, тем больше операций требуется выполнить Synapse для разрешения коллиций, которые возникают при их проверке наступления событий (это дорогостоящая операция). Хотя в Synapse есть код, предотвращающий одновременное присутствие слишком большого количества таких объектов в комнате, ошибки иногда могут привести к их повторному появлению. Если в комнате содержится >10 перенаправленных заключений, имеет смысл выяснить, какая комната стала причиной их появления и, возможно, удалить их, используя SQL-запросы, упомянутые issue #1760.", + }, + enums: { + join_rules: { + public: "Публичный", + knock: "По запросу", + invite: "По приглашению", + private: "Приватный", + }, + guest_access: { + can_join: "Гости могут присоединиться", + forbidden: "Гости не могут присоединиться", + }, + history_visibility: { + invited: "С момента приглашения", + joined: "С момента присоединения", + shared: "С момента разрешения", + world_readable: "Всегда", + }, + unencrypted: "Не зашифрованно", + }, + action: { + erase: { + title: "Удалить комнату", + content: + "Вы уверены, что хотите удалить комнату? Это невозможно отменить. Все сообщения и медиафайлы, находящиеся в общем доступе в комнате, будут удалены с сервера!", + }, + }, + }, + reports: { + name: "Жалоба |||| Жалобы", + fields: { + id: "идентификатор", + received_ts: "время жалобы", + user_id: "коментатор", + name: "название комнаты", + score: "оценка", + reason: "причина", + event_id: "идентификатор события", + event_json: { + origin: "сервер", + origin_server_ts: "время отправки", + type: "тип события", + content: { + msgtype: "тип содержания", + body: "содержание", + format: "формат", + formatted_body: "форматированное содержание", + algorithm: "алгоритм", + }, + }, + }, + action: { + erase: { + title: "Удалить жалобу", + content: + "Вы уверены, что хотите удалить жалобу? Это невозможно отменить.", + }, + }, + }, + connections: { + name: "Подключения", + fields: { + last_seen: "Дата", + ip: "IP адрес", + user_agent: "User agent", + }, + }, + devices: { + name: "Устройство |||| Устройства", + fields: { + device_id: "Идентификатор устройства", + display_name: "Название устройства", + last_seen_ts: "Метка времени", + last_seen_ip: "IP адрес", + }, + action: { + erase: { + title: "Удаление %{id}", + content: 'Вы уверены, что хотите удалить устройство "%{name}"?', + success: "Устройство успешно удалено.", + failure: "Произошла ошибка.", + }, + }, + }, + users_media: { + name: "Медиа файлы", + fields: { + media_id: "Идентификатор медиа файла", + media_length: "Размер файла (в байтах)", + media_type: "Тип", + upload_name: "Имя файла", + quarantined_by: "Отправлен в карантин", + safe_from_quarantine: "Защищен от карантина", + created_ts: "Создан", + last_access_ts: "Последнее обращение", + }, + }, + delete_media: { + name: "Медиа файлы", + fields: { + before_ts: "последнее обращение", + size_gt: "Больше чем (в байтах)", + keep_profiles: "Сохранить аватары", + }, + action: { + send: "Удалить медиа файлы", + send_success: "Запрос отправлен.", + send_failure: "Произошла ошибка.", + }, + helper: { + send: "Этот метод удаляет медиа файлы с диска сервера. Это включает в себя любые миниатюры и копии загруженных файлов. Этот метод не повлияет на файлы, загруженные во внешние хранилища.", + }, + }, + protect_media: { + action: { + create: "Не защищено, установить защиту", + delete: "Защищено, удалить защиту", + none: "В карантине", + send_success: "Успешно изменен статус защиты.", + send_failure: "Произошла ошибка.", + }, + }, + quarantine_media: { + action: { + name: "Карантин", + create: "Добавить в карантин", + delete: "В карантине, вывести из карантина", + none: "Защищено от карантина", + send_success: "Успешно изменен статус карантина.", + send_failure: "Произошла ошибка.", + }, + }, + pushers: { + name: "Уведомление |||| Уведомления", + fields: { + app: "Приложение", + app_display_name: "Отображаемое имя приложения", + app_id: "Идентификатор приложения", + device_display_name: "Отображаемое имя устройства", + kind: "Тип", + lang: "Язык", + profile_tag: "Тег профиля", + pushkey: "Токен доступа", + data: { url: "URL" }, + }, + }, + servernotices: { + name: "Уведомления", + send: "Отправить уведомление", + fields: { + body: "Сообщение", + }, + action: { + send: "Отправить", + send_success: "Уведомление успешно отправлено.", + send_failure: "Произошла ошибка.", + }, + helper: { + send: 'Отправляет уведомление выбранным пользователям. Функция "Server Notices" должна быть активирована на сервере.', + }, + }, + user_media_statistics: { + name: "Пользовательские медиа файлы", + fields: { + media_count: "Количество медиа файлов", + media_length: "Длина медиа файлов", + }, + }, + forward_extremities: { + name: "Перенаправленные заключения", + fields: { + id: "Идентификатор события", + received_ts: "Время", + depth: "Глубина", + state_group: "Группа состояния", + }, + }, + room_state: { + name: "События изменения состояния", + fields: { + type: "Тип", + content: "Содержание", + origin_server_ts: "время отправления", + sender: "Отправитель", + }, + }, + room_directory: { + name: "Публичные комнаты", + fields: { + world_readable: "Видимо для гостей", + guest_can_join: "Гости могут присоединиться", + }, + action: { + title: + "Удалить комнату |||| Удалить %{smart_count} комнат", + content: + "Вы уверены, что хотите удалить комнату? |||| Вы уверены, что хотите удалить %{smart_count} комнат?", + erase: "Удалить комнату", + create: "Опубликовать комнату", + send_success: "Комната успешно опубликована.", + send_failure: "Произошла ошибка.", + }, + }, + destinations: { + name: "Федерация", + fields: { + destination: "Назначение", + failure_ts: "Время сбоя", + retry_last_ts: "Время последней попытки", + retry_interval: "Интервал повторения", + last_successful_stream_ordering: "Последнее успешное соединение", + stream_ordering: "Соединение", + }, + action: { reconnect: "Переподключиться" }, + }, + }, + registration_tokens: { + name: "Токены регистрации", + fields: { + token: "Токен", + valid: "Допустимый токен", + uses_allowed: "Разрешено", + pending: "Ожидается", + completed: "Использован", + expiry_time: "Время истечения срока действия", + length: "Длина", + }, + helper: { length: "Длина токена, если токен не указан." }, + }, +}; +export default ru;