Files
glchat/web/src/i18n/locales/en.json
T
grendervill 9c001fedfe feat(web): базовый клиент Фазы 1 — вход, серверы, комнаты, настройки
Клиент React 19 + TanStack Query + Zustand:

- вход (поле TOTP появляется на `auth.2fa_required`), регистрация с проверками
  и учётом `registration_enabled`, онбординг первого входа с возможностью
  пропустить, редирект `/` и страница `/status`;
- защита маршрутов: неавторизованных — на `/login`, без онбординга — на
  `/onboarding`;
- оболочка `/app`: рейка серверов, сайдбар категорий и комнат, шапка сервера,
  панель пользователя, модалки создания сервера и комнаты, экран «нет серверов»
  со вступлением в главный сервер, заглушка комнаты до Фазы 2;
- настройки: профиль, безопасность (step-up, сессии, logout-all, 2FA с
  локальным QR и кодами восстановления), внешний вид, админ-раздел инстанса;
- Gateway-клиент: HELLO/IDENTIFY/RESUME, heartbeat с ожиданием ACK,
  экспоненциальное переподключение, разбор `INVALID_SESSION {reason,resumable}`;
- диспетчер событий: READY/RESUMED/USER_UPDATE/GUILD_UPDATE/GUILD_DELETE/
  CHANNEL_* применяются к стору, GUILD_CREATE догружается по REST,
  MEMBER_*/ROLE_* инвалидируют запросы участников, ролей и карточки сервера;
- i18n ru/en, 66 тестов Vitest, `check`/`lint`/`test`/`build` зелёные.

Серверные правки под клиент:

- `totp_enabled` в профиле (`GET /users/@me`, вход, регистрация) — клиенту
  нужно знать, требовать ли код при step-up;
- `POST /auth/2fa/setup` отдаёт `otpauth_url` и `qr_png` (data-URI): QR-код
  рисуется локально, внешние сервисы генерации QR не используются;
- права в ответах — имена (`VIEW_CHANNEL|SEND_MESSAGES`), клиент сверяет имена.
2026-09-19 22:03:46 +03:00

337 lines
12 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"app": {
"name": "glchat",
"tagline": "Self-hosted communication: text, voice, video",
"skipToContent": "Skip to content"
},
"status": {
"title": "Foundation deployed",
"description": "The server responds, the database runs in WAL mode, migrations are applied. Chat features arrive in the next phases.",
"connecting": "Checking server status…",
"instance": "Instance",
"version": "Version",
"apiVersion": "API version",
"schemaReady": "Database",
"schemaOk": "ready",
"maxUpload": "Maximum upload size",
"checks": "Checks",
"retry": "Retry",
"back": "Back",
"users": "Users",
"guilds": "Servers",
"registration": "Registration"
},
"health": {
"online": "Server is reachable",
"offline": "Server is unreachable"
},
"errors": {
"network.unreachable": "The server is not responding. Check your connection and try again.",
"network.aborted": "The request was aborted.",
"instance.not_ready": "The instance is still starting. Try again in a few seconds.",
"internal.error": "Internal server error.",
"not_found": "The requested resource was not found.",
"unknown": "Unknown error ({{code}}).",
"auth.2fa_required": "Enter the two-factor code from your authenticator app.",
"auth.totp_invalid": "The two-factor code is incorrect.",
"auth.invalid_credentials": "Wrong email or password.",
"auth.step_up_required": "Confirm your identity: enter your password again.",
"auth.guild_limit_reached": "You have reached the server limit on this instance.",
"auth.session_invalid": "Your session has expired. Sign in again.",
"auth.unauthorized": "Sign-in is required.",
"auth.registration_disabled": "Registration is disabled on this instance.",
"auth.username_taken": "This username is already taken.",
"auth.email_taken": "This email is already registered.",
"auth.weak_password": "The password is too weak. Use at least 8 characters.",
"auth.too_many_attempts": "Too many attempts. Try again later.",
"permissions.denied": "You do not have permission for this action.",
"instance.admin_required": "This action is available to the instance admin only.",
"guild.not_found": "Server not found.",
"guild.member_limit_reached": "This server has reached its member limit.",
"channel.not_found": "Channel not found.",
"validation.failed": "Check the fields you filled in.",
"rate.limited": "Too many requests. Please wait a moment."
},
"theme": {
"switchToLight": "Light theme",
"switchToDark": "Dark theme",
"label": "Theme",
"dark": "Dark",
"light": "Light"
},
"language": {
"label": "Language",
"ru": "Русский",
"en": "English"
},
"common": {
"loading": "Loading…",
"retry": "Retry",
"cancel": "Cancel",
"close": "Close",
"save": "Save",
"saving": "Saving…",
"create": "Create",
"creating": "Creating…",
"join": "Join",
"leave": "Leave",
"back": "Back",
"copy": "Copy",
"copied": "Copied",
"yes": "Yes",
"no": "No",
"optional": "optional",
"dismiss": "Dismiss"
},
"footer": {
"source": "Source code",
"license": "AGPL-3.0"
},
"auth": {
"login": {
"title": "Sign in",
"subtitle": "Sign in to keep talking",
"email": "Email",
"emailPlaceholder": "you@example.com",
"password": "Password",
"totp": "2FA code",
"totpHint": "Six digits from your authenticator app.",
"submit": "Sign in",
"submitting": "Signing in…",
"noAccount": "No account yet?",
"registerLink": "Create one",
"forgot": "Forgot your password? Ask the instance admin.",
"instanceStatus": "Instance status",
"registrationDisabled": "Registration on this instance is disabled by the admin."
},
"register": {
"title": "Create account",
"subtitle": "Create an account on {{instance}}",
"username": "Username",
"usernameHint": "3–32 characters: latin letters, digits, “_” and “-”.",
"displayName": "Display name",
"email": "Email",
"password": "Password",
"passwordHint": "At least 8 characters.",
"requirements": "Password requirements: {{list}}",
"requirementLength": "at least 8 characters",
"requirementLetter": "at least one letter",
"requirementDigit": "at least one digit",
"submit": "Create account",
"submitting": "Creating account…",
"haveAccount": "Already have an account?",
"loginLink": "Sign in",
"disabledTitle": "Registration is closed",
"disabledBody": "The instance admin disabled self-service registration. Ask for an invite or contact the admin."
},
"validation": {
"usernameFormat": "Username: 3–32 characters, latin letters, digits, “_” or “-”.",
"usernameRequired": "Enter a username.",
"displayNameRequired": "Enter a display name.",
"emailInvalid": "Enter a valid email address.",
"passwordShort": "The password must be at least 8 characters long.",
"passwordLetter": "Add at least one letter to the password.",
"passwordDigit": "Add at least one digit to the password.",
"passwordMismatch": "The passwords do not match."
}
},
"onboarding": {
"title": "Welcome to glchat",
"greeting": "Hi, {{name}}!",
"subtitle": "A couple of steps and you are ready.",
"displayName": "How should we show you?",
"displayNameHint": "Other members see this name.",
"bio": "Tell us about yourself",
"bioPlaceholder": "What you do, what you like…",
"language": "Interface language",
"theme": "Theme",
"start": "Get started",
"starting": "Preparing your workspace…",
"skip": "Skip and start",
"note": "You can change all of this later in settings."
},
"guilds": {
"railLabel": "Servers",
"listLabel": "Server list",
"add": "Create a server",
"addShort": "Create a server",
"create": {
"title": "Create a server",
"name": "Server name",
"namePlaceholder": "For example, “Friends”",
"hint": "You can rename it later in the server settings.",
"submit": "Create",
"limit": "You can create at most {{count}} servers on this instance.",
"limitReached": "You have reached the server limit — remove one of your servers first."
},
"empty": {
"title": "No servers yet",
"body": "Join the instance main server or create your own.",
"joinMain": "Join the main server",
"joining": "Joining…",
"noMain": "This instance has no main server configured — create your own."
},
"leave": "Leave server",
"leaveConfirm": "Leave the server “{{name}}”?",
"switchTo": "Open server {{name}}"
},
"channels": {
"title": "Channels",
"text": "Text channels",
"voice": "Voice channels",
"category": "Category",
"empty": "This server has no visible channels yet.",
"select": "Open channel {{name}}",
"add": "Create a channel",
"create": {
"title": "New channel",
"name": "Channel name",
"namePlaceholder": "for example, general",
"type": "Channel type",
"typeText": "Text",
"typeVoice": "Voice",
"parent": "Category",
"noParent": "No category",
"submit": "Create"
},
"chatPlaceholder": {
"title": "Chat arrives in Phase 2",
"body": "The channel “{{name}}” is ready. Messages and voice arrive in the next phase."
},
"voicePlaceholder": {
"title": "Voice channel",
"body": "Connecting to voice arrives in the next phases."
},
"noSelection": {
"title": "Pick a channel",
"body": "The server and channel lists are on the left. Pick a channel to open it."
},
"notInGuild": {
"title": "Server unavailable",
"body": "Looks like you are not a member of this server."
}
},
"user": {
"panelLabel": "User panel",
"settings": "User settings",
"logout": "Sign out",
"loggingOut": "Signing out…",
"statusLabel": "Status: {{status}}",
"copyId": "Copy ID",
"status": {
"online": "Online",
"idle": "Idle",
"dnd": "Do not disturb",
"invisible": "Invisible",
"offline": "Offline"
}
},
"gateway": {
"label": "Connection",
"status": {
"idle": "Not connected",
"connecting": "Connecting…",
"connected": "Connected",
"reconnecting": "Reconnecting…",
"disconnected": "Connection lost"
},
"invalidated": "The server closed the session. Sign in again."
},
"settings": {
"title": "Settings",
"back": "Back to app",
"tabs": {
"label": "Settings sections",
"profile": "Profile",
"security": "Security",
"appearance": "Appearance",
"instance": "Instance"
},
"profile": {
"title": "Profile",
"description": "How other members see you.",
"displayName": "Display name",
"bio": "About me",
"bioPlaceholder": "A few words about you",
"customStatus": "Custom status",
"customStatusPlaceholder": "For example, “Writing code”",
"customStatusEmoji": "Status emoji",
"customStatusEmojiPlaceholder": "🚀",
"status": "Presence status",
"locale": "Interface language",
"saved": "Profile saved.",
"username": "Username",
"userId": "User ID",
"badges": "Badges",
"noBadges": "none"
},
"security": {
"title": "Security",
"description": "Password, active sessions and two-factor authentication.",
"passwordTitle": "Change password",
"currentPassword": "Current password",
"newPassword": "New password",
"confirmPassword": "Repeat the new password",
"submitPassword": "Change password",
"passwordChanged": "Password changed.",
"stepUpTitle": "Confirm your identity",
"stepUpBody": "The server requires your password again for this action{{totp}}.",
"stepUpTotp": " and a 2FA code",
"stepUpSubmit": "Confirm",
"stepUpSuccess": "Identity confirmed.",
"stepUpExpired": "The confirmation expired, enter your password again.",
"stepUpPassword": "Your password",
"sessionsTitle": "Active sessions",
"sessionsEmpty": "No active sessions.",
"sessionCurrent": "current",
"sessionCreated": "Created",
"sessionSeen": "Last seen",
"sessionIp": "IP",
"sessionAgent": "Device",
"unknownAgent": "Unknown device",
"logoutAll": "Sign out everywhere",
"logoutAllConfirm": "End all sessions, including this one?",
"logoutAllDone": "All sessions have been ended.",
"twoFactorTitle": "Two-factor authentication",
"twoFactorEnabled": "2FA is on",
"twoFactorDisabled": "2FA is off",
"twoFactorEnable": "Enable 2FA",
"twoFactorSetupHint": "Add the secret to your authenticator app, then enter the six-digit code.",
"twoFactorSecret": "Secret",
"twoFactorUri": "otpauth link",
"twoFactorQrAlt": "QR code for 2FA setup",
"twoFactorQrUnavailable": "The QR code is unavailable — add the secret manually.",
"twoFactorCode": "Code from the app",
"twoFactorConfirm": "Enable",
"twoFactorEnabledDone": "2FA is enabled. Save your recovery codes.",
"recoveryTitle": "Recovery codes",
"recoveryHint": "Every code works once. They are shown only once.",
"recoveryCopyAll": "Copy all codes"
},
"appearance": {
"title": "Appearance",
"description": "Interface theme and language. Stored locally in your browser.",
"theme": "Theme",
"themeHint": "The dark theme is on by default.",
"language": "Language",
"languageHint": "Russian is the primary interface language."
},
"instance": {
"title": "Instance",
"description": "Instance admin only. Read-only data.",
"notAdmin": "This section is available to the instance admin only.",
"settings": "Instance settings",
"settingsEmpty": "No settings configured.",
"guilds": "Servers ({{count}})",
"guildsEmpty": "No servers.",
"users": "Users ({{count}})",
"usersEmpty": "No users.",
"audit": "Audit log (last {{count}})",
"auditEmpty": "No entries.",
"memberCount": "{{count}} members",
"adminBadge": "admin",
"mainBadge": "main"
}
}
}