1b1a679827
REST-слой Фазы 1 на huma (OpenAPI 3.1 генерируется из кода):
- профиль: GET/PATCH /users/@me, смена пароля со step-up, публичный профиль,
завершение онбординга (новая миграция 00003 с onboarding_completed_at);
- серверы: создание/изменение/удаление, join/leave, список серверов
пользователя, журнал действий;
- комнаты: список с учётом прав, создание/изменение/удаление;
- участники: список с профилями и ролями, никнейм, тайм-аут, исключение;
- роли: CRUD, выдача/снятие с проверкой иерархии и запретом выдачи прав выше
собственных;
- админ инстанса: публичная информация, настройки, серверы, пользователи,
аудит, выдача прав администратора со step-up; обход лимитов фиксируется в
аудите отдельной записью limits.bypass;
- движок прав: участие в сервере стало обязательным условием (IsMember),
не участник не получает прав роли @user; калькулятор прав общий для API и
Gateway, инвалидация кэша после изменений;
- Gateway: браузерный клиент аутентифицируется cookie на рукопожатии, IDENTIFY
без токена использует её; события GUILD/CHANNEL/MEMBER/ROLE рассылаются из
ручек, USER_UPDATE — адресно;
- ошибки huma отдаются в едином конверте {"error":{"code","message"}}.
Тесты: 8 сценариев API (профиль, жизненный цикл сервера и права, лимиты и
обход админом, иерархия ролей, тайм-аут, скрытие комнаты оверрайдом,
членство в движке прав, cookie-идентификация Gateway).
335 lines
17 KiB
JSON
335 lines
17 KiB
JSON
{
|
||
"app": {
|
||
"name": "glchat",
|
||
"tagline": "Self-hosted общение: текст, голос, видео",
|
||
"skipToContent": "Перейти к содержимому"
|
||
},
|
||
"status": {
|
||
"title": "Фундамент развёрнут",
|
||
"description": "Сервер отвечает, база данных в режиме WAL, миграции применены. Функции общения появятся в следующих фазах.",
|
||
"connecting": "Проверяем состояние сервера…",
|
||
"instance": "Инстанс",
|
||
"version": "Версия",
|
||
"apiVersion": "Версия API",
|
||
"schemaReady": "База данных",
|
||
"schemaOk": "готова",
|
||
"maxUpload": "Максимальный размер загрузки",
|
||
"checks": "Проверки",
|
||
"retry": "Повторить",
|
||
"back": "Назад",
|
||
"users": "Пользователей",
|
||
"guilds": "Серверов",
|
||
"registration": "Регистрация"
|
||
},
|
||
"health": {
|
||
"online": "Сервер доступен",
|
||
"offline": "Сервер недоступен"
|
||
},
|
||
"errors": {
|
||
"network.unreachable": "Сервер не отвечает. Проверьте соединение и повторите попытку.",
|
||
"network.aborted": "Запрос отменён.",
|
||
"instance.not_ready": "Инстанс ещё запускается. Повторите через несколько секунд.",
|
||
"internal.error": "Внутренняя ошибка сервера.",
|
||
"not_found": "Запрашиваемый ресурс не найден.",
|
||
"unknown": "Неизвестная ошибка ({{code}}).",
|
||
"auth.2fa_required": "Введите код двухфакторной аутентификации из приложения.",
|
||
"auth.totp_invalid": "Неверный код двухфакторной аутентификации.",
|
||
"auth.invalid_credentials": "Неверная почта или пароль.",
|
||
"auth.step_up_required": "Подтвердите личность: введите пароль заново.",
|
||
"auth.guild_limit_reached": "Достигнут лимит серверов на этом инстансе.",
|
||
"auth.session_invalid": "Сессия истекла. Войдите снова.",
|
||
"auth.unauthorized": "Требуется вход в систему.",
|
||
"auth.registration_disabled": "Регистрация на этом инстансе выключена.",
|
||
"auth.username_taken": "Такое имя пользователя уже занято.",
|
||
"auth.email_taken": "Такая почта уже зарегистрирована.",
|
||
"auth.weak_password": "Пароль слишком простой. Используйте не менее 8 символов.",
|
||
"auth.too_many_attempts": "Слишком много попыток. Попробуйте позже.",
|
||
"permissions.denied": "Недостаточно прав для этого действия.",
|
||
"instance.admin_required": "Действие доступно только администратору инстанса.",
|
||
"guild.not_found": "Сервер не найден.",
|
||
"guild.member_limit_reached": "На сервере достигнут лимит участников.",
|
||
"channel.not_found": "Комната не найдена.",
|
||
"validation.failed": "Проверьте заполненные поля.",
|
||
"rate.limited": "Слишком много запросов. Подождите немного."
|
||
},
|
||
"theme": {
|
||
"switchToLight": "Светлая тема",
|
||
"switchToDark": "Тёмная тема",
|
||
"label": "Тема",
|
||
"dark": "Тёмная",
|
||
"light": "Светлая"
|
||
},
|
||
"language": {
|
||
"label": "Язык",
|
||
"ru": "Русский",
|
||
"en": "English"
|
||
},
|
||
"common": {
|
||
"loading": "Загрузка…",
|
||
"retry": "Повторить",
|
||
"cancel": "Отмена",
|
||
"close": "Закрыть",
|
||
"save": "Сохранить",
|
||
"saving": "Сохраняем…",
|
||
"create": "Создать",
|
||
"creating": "Создаём…",
|
||
"join": "Присоединиться",
|
||
"leave": "Покинуть",
|
||
"back": "Назад",
|
||
"copy": "Скопировать",
|
||
"copied": "Скопировано",
|
||
"yes": "Да",
|
||
"no": "Нет",
|
||
"optional": "необязательно",
|
||
"dismiss": "Скрыть"
|
||
},
|
||
"footer": {
|
||
"source": "Исходный код",
|
||
"license": "AGPL-3.0"
|
||
},
|
||
"auth": {
|
||
"login": {
|
||
"title": "Вход",
|
||
"subtitle": "Войдите, чтобы продолжить общение",
|
||
"email": "Почта",
|
||
"emailPlaceholder": "you@example.com",
|
||
"password": "Пароль",
|
||
"totp": "Код 2FA",
|
||
"totpHint": "Шесть цифр из приложения-аутентификатора.",
|
||
"submit": "Войти",
|
||
"submitting": "Входим…",
|
||
"noAccount": "Ещё нет аккаунта?",
|
||
"registerLink": "Зарегистрироваться",
|
||
"forgot": "Пароль забыт? Обратитесь к администратору инстанса.",
|
||
"instanceStatus": "Состояние инстанса",
|
||
"registrationDisabled": "Регистрация на этом инстансе выключена администратором."
|
||
},
|
||
"register": {
|
||
"title": "Регистрация",
|
||
"subtitle": "Создайте аккаунт на инстансе {{instance}}",
|
||
"username": "Имя пользователя",
|
||
"usernameHint": "3–32 символа: латиница, цифры, «_» и «-».",
|
||
"displayName": "Отображаемое имя",
|
||
"email": "Почта",
|
||
"password": "Пароль",
|
||
"passwordHint": "Не менее 8 символов.",
|
||
"requirements": "Требования к паролю: {{list}}",
|
||
"requirementLength": "не менее 8 символов",
|
||
"requirementLetter": "хотя бы одна буква",
|
||
"requirementDigit": "хотя бы одна цифра",
|
||
"submit": "Создать аккаунт",
|
||
"submitting": "Создаём аккаунт…",
|
||
"haveAccount": "Уже есть аккаунт?",
|
||
"loginLink": "Войти",
|
||
"disabledTitle": "Регистрация закрыта",
|
||
"disabledBody": "Администратор инстанса выключил самостоятельную регистрацию. Попросите приглашение или обратитесь к администратору."
|
||
},
|
||
"validation": {
|
||
"usernameFormat": "Имя пользователя: 3–32 символа, латиница, цифры, «_» или «-».",
|
||
"usernameRequired": "Укажите имя пользователя.",
|
||
"displayNameRequired": "Укажите отображаемое имя.",
|
||
"emailInvalid": "Укажите корректный адрес почты.",
|
||
"passwordShort": "Пароль должен быть не короче 8 символов.",
|
||
"passwordLetter": "Добавьте в пароль хотя бы одну букву.",
|
||
"passwordDigit": "Добавьте в пароль хотя бы одну цифру.",
|
||
"passwordMismatch": "Пароли не совпадают."
|
||
}
|
||
},
|
||
"onboarding": {
|
||
"title": "Добро пожаловать в glchat",
|
||
"greeting": "Привет, {{name}}!",
|
||
"subtitle": "Пара шагов — и можно общаться.",
|
||
"displayName": "Как вас показывать?",
|
||
"displayNameHint": "Это имя видят другие участники.",
|
||
"bio": "Расскажи о себе",
|
||
"bioPlaceholder": "Чем занимаетесь, что интересно…",
|
||
"language": "Язык интерфейса",
|
||
"theme": "Тема",
|
||
"start": "Начать",
|
||
"starting": "Готовим рабочее пространство…",
|
||
"skip": "Пропустить и начать",
|
||
"note": "Позже всё это можно изменить в настройках."
|
||
},
|
||
"guilds": {
|
||
"railLabel": "Серверы",
|
||
"listLabel": "Список серверов",
|
||
"add": "Создать сервер",
|
||
"addShort": "Создать сервер",
|
||
"create": {
|
||
"title": "Создать сервер",
|
||
"name": "Название сервера",
|
||
"namePlaceholder": "Например, «Друзья»",
|
||
"hint": "Название можно изменить позже в настройках сервера.",
|
||
"submit": "Создать",
|
||
"limit": "На инстансе можно создать не более {{count}} серверов."
|
||
},
|
||
"empty": {
|
||
"title": "Пока нет ни одного сервера",
|
||
"body": "Присоединитесь к главному серверу инстанса или создайте свой.",
|
||
"joinMain": "Присоединиться к главному серверу",
|
||
"joining": "Присоединяемся…",
|
||
"noMain": "Главный сервер на инстансе не настроен — создайте свой."
|
||
},
|
||
"leave": "Покинуть сервер",
|
||
"leaveConfirm": "Покинуть сервер «{{name}}»?",
|
||
"switchTo": "Открыть сервер {{name}}"
|
||
},
|
||
"channels": {
|
||
"title": "Комнаты",
|
||
"text": "Текстовые комнаты",
|
||
"voice": "Голосовые комнаты",
|
||
"category": "Категория",
|
||
"empty": "В этом сервере пока нет видимых комнат.",
|
||
"select": "Открыть комнату {{name}}",
|
||
"add": "Создать комнату",
|
||
"create": {
|
||
"title": "Новая комната",
|
||
"name": "Название комнаты",
|
||
"namePlaceholder": "например, общий-чат",
|
||
"type": "Тип комнаты",
|
||
"typeText": "Текстовая",
|
||
"typeVoice": "Голосовая",
|
||
"parent": "Категория",
|
||
"noParent": "Без категории",
|
||
"submit": "Создать"
|
||
},
|
||
"chatPlaceholder": {
|
||
"title": "Чат появится в Фазе 2",
|
||
"body": "Комната «{{name}}» уже доступна. Отправка сообщений и голосовые каналы появятся в следующей фазе."
|
||
},
|
||
"voicePlaceholder": {
|
||
"title": "Голосовая комната",
|
||
"body": "Подключение к голосу появится в следующих фазах."
|
||
},
|
||
"noSelection": {
|
||
"title": "Выберите комнату",
|
||
"body": "Слева — список серверов и комнат. Выберите комнату, чтобы открыть её."
|
||
},
|
||
"notInGuild": {
|
||
"title": "Сервер недоступен",
|
||
"body": "Похоже, вы не состоите в этом сервере."
|
||
}
|
||
},
|
||
"user": {
|
||
"panelLabel": "Панель пользователя",
|
||
"settings": "Настройки пользователя",
|
||
"logout": "Выйти",
|
||
"loggingOut": "Выходим…",
|
||
"statusLabel": "Статус: {{status}}",
|
||
"copyId": "Скопировать ID",
|
||
"status": {
|
||
"online": "В сети",
|
||
"idle": "Неактивен",
|
||
"dnd": "Не беспокоить",
|
||
"invisible": "Невидимый",
|
||
"offline": "Не в сети"
|
||
}
|
||
},
|
||
"gateway": {
|
||
"label": "Соединение",
|
||
"status": {
|
||
"idle": "Не подключено",
|
||
"connecting": "Подключаемся…",
|
||
"connected": "Подключено",
|
||
"reconnecting": "Переподключение…",
|
||
"disconnected": "Соединение потеряно"
|
||
},
|
||
"invalidated": "Сессия закрыта сервером. Войдите снова."
|
||
},
|
||
"settings": {
|
||
"title": "Настройки",
|
||
"back": "В приложение",
|
||
"tabs": {
|
||
"label": "Разделы настроек",
|
||
"profile": "Профиль",
|
||
"security": "Безопасность",
|
||
"appearance": "Внешний вид",
|
||
"instance": "Инстанс"
|
||
},
|
||
"profile": {
|
||
"title": "Профиль",
|
||
"description": "Как вас видят другие участники.",
|
||
"displayName": "Отображаемое имя",
|
||
"bio": "О себе",
|
||
"bioPlaceholder": "Пара слов о себе",
|
||
"customStatus": "Своё состояние",
|
||
"customStatusPlaceholder": "Например, «Пишу код»",
|
||
"customStatusEmoji": "Эмодзи состояния",
|
||
"customStatusEmojiPlaceholder": "🚀",
|
||
"status": "Статус присутствия",
|
||
"locale": "Язык интерфейса",
|
||
"saved": "Профиль сохранён.",
|
||
"username": "Имя пользователя",
|
||
"userId": "ID пользователя",
|
||
"badges": "Значки",
|
||
"noBadges": "нет"
|
||
},
|
||
"security": {
|
||
"title": "Безопасность",
|
||
"description": "Пароль, активные сессии и двухфакторная аутентификация.",
|
||
"passwordTitle": "Смена пароля",
|
||
"currentPassword": "Текущий пароль",
|
||
"newPassword": "Новый пароль",
|
||
"confirmPassword": "Повторите новый пароль",
|
||
"submitPassword": "Сменить пароль",
|
||
"passwordChanged": "Пароль изменён.",
|
||
"stepUpTitle": "Подтвердите личность",
|
||
"stepUpBody": "Для этого действия сервер требует повторного ввода пароля{{totp}}.",
|
||
"stepUpTotp": " и кода 2FA",
|
||
"stepUpSubmit": "Подтвердить",
|
||
"stepUpSuccess": "Личность подтверждена.",
|
||
"stepUpExpired": "Подтверждение устарело, введите пароль заново.",
|
||
"stepUpPassword": "Ваш пароль",
|
||
"sessionsTitle": "Активные сессии",
|
||
"sessionsEmpty": "Активных сессий нет.",
|
||
"sessionCurrent": "текущая",
|
||
"sessionCreated": "Создана",
|
||
"sessionSeen": "Активность",
|
||
"sessionIp": "IP",
|
||
"sessionAgent": "Устройство",
|
||
"unknownAgent": "Неизвестное устройство",
|
||
"logoutAll": "Выйти на всех устройствах",
|
||
"logoutAllConfirm": "Завершить все сессии, включая текущую?",
|
||
"logoutAllDone": "Все сессии завершены.",
|
||
"twoFactorTitle": "Двухфакторная аутентификация",
|
||
"twoFactorEnabled": "2FA включена",
|
||
"twoFactorDisabled": "2FA выключена",
|
||
"twoFactorEnable": "Включить 2FA",
|
||
"twoFactorSetupHint": "Добавьте секрет в приложение-аутентификатор, затем введите шестизначный код.",
|
||
"twoFactorSecret": "Секрет",
|
||
"twoFactorUri": "Ссылка otpauth",
|
||
"twoFactorQrAlt": "QR-код для настройки 2FA",
|
||
"twoFactorQrUnavailable": "QR-код недоступен — добавьте секрет вручную.",
|
||
"twoFactorCode": "Код из приложения",
|
||
"twoFactorConfirm": "Включить",
|
||
"twoFactorEnabledDone": "2FA включена. Сохраните коды восстановления.",
|
||
"recoveryTitle": "Коды восстановления",
|
||
"recoveryHint": "Каждый код одноразовый. Они показываются только один раз.",
|
||
"recoveryCopyAll": "Скопировать все коды"
|
||
},
|
||
"appearance": {
|
||
"title": "Внешний вид",
|
||
"description": "Тема и язык интерфейса. Хранятся локально в браузере.",
|
||
"theme": "Тема",
|
||
"themeHint": "Тёмная тема включена по умолчанию.",
|
||
"language": "Язык",
|
||
"languageHint": "Русский — основной язык интерфейса."
|
||
},
|
||
"instance": {
|
||
"title": "Инстанс",
|
||
"description": "Только для администратора инстанса. Данные только для чтения.",
|
||
"notAdmin": "Раздел доступен только администратору инстанса.",
|
||
"settings": "Настройки инстанса",
|
||
"settingsEmpty": "Настройки не заданы.",
|
||
"guilds": "Серверы ({{count}})",
|
||
"guildsEmpty": "Серверов нет.",
|
||
"users": "Пользователи ({{count}})",
|
||
"usersEmpty": "Пользователей нет.",
|
||
"audit": "Журнал аудита (последние {{count}})",
|
||
"auditEmpty": "Записей нет.",
|
||
"memberCount": "{{count}} участн.", "adminBadge": "админ",
|
||
"mainBadge": "главный"
|
||
}
|
||
}
|
||
}
|