9c001fedfe
Клиент 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`), клиент сверяет имена.
337 lines
12 KiB
JSON
337 lines
12 KiB
JSON
{
|
||
"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"
|
||
}
|
||
}
|
||
}
|