import i18n from 'i18next' import { initReactI18next } from 'react-i18next' const resources = { ru: { translation: { appName: 'PnvPanel', tagline: 'Self-service портал для VPN-конфигураций', theme: 'Тема', language: 'Язык', light: 'Светлая', dark: 'Тёмная', system: 'Системная', unlimited: 'Без лимита', auth: { loginTitle: 'Вход', registerTitle: 'Регистрация', userName: 'Имя пользователя', password: 'Пароль', submitLogin: 'Войти', submitRegister: 'Зарегистрироваться', noAccount: 'Нет аккаунта?', haveAccount: 'Уже есть аккаунт?', goRegister: 'Зарегистрироваться', goLogin: 'Войти', loginViaTelegram: 'Войти через Telegram', invalidCredentials: 'Неверное имя пользователя или пароль.', duplicateUserName: 'Пользователь с таким именем уже существует.', genericError: 'Что-то пошло не так. Попробуйте ещё раз.', userNameHint: 'Латиница, цифры, «_», «.», «-», от 3 до 32 символов.', passwordHint: 'Не менее 8 символов, минимум одна заглавная буква и одна цифра.', or: 'или', telegramBotNotConfigured: 'Telegram-бот не настроен администратором.', telegramQrHint: 'Перейдите по ссылке или отсканируйте QR-код — откроется бот в Telegram. Если открываете его впервые, нажмите «Старт».', waitingForConfirmation: 'Ожидание подтверждения в Telegram…', telegramLoginRejected: 'Вход отклонён в Telegram.', telegramLoginExpired: 'Время ожидания истекло, попробуйте снова.', }, telegramLinkWarning: { message: 'Вы не привязали Telegram — без этого вы не будете получать уведомления и не сможете самостоятельно восстановить пароль.', action: 'Привязать Telegram', dismiss: 'Скрыть', }, nav: { dashboard: 'Мои конфиги', instructions: 'Инструкции', news: 'Новости', support: 'Поддержка', settings: 'Настройки', admin: 'Админка', logout: 'Выйти', toggleMenu: 'Меню', }, activation: { title: 'Аккаунт не активирован', description: 'Чтобы создавать конфиги, дождитесь активации администратором. Можно оставить комментарий к заявке.', commentLabel: 'Комментарий (необязательно)', submit: 'Запросить активацию', pending: 'Заявка на активацию отправлена, ожидайте решения администратора.', alreadyPending: 'У вас уже есть необработанная заявка на активацию.', retry: 'Повторить', }, configs: { title: 'Мои конфиги', quota: 'Использовано {{used}} из {{max}}', quotaUnlimited: 'Использовано {{used}}, без лимита', empty: 'У вас пока нет конфигов. Создайте первый.', create: 'Создать конфиг', selectLocation: 'Выберите локацию', location: 'Локация', label: 'Метка (необязательно)', noInboundsNotice: 'Пока нет доступных локаций для создания конфига. Обратитесь к администратору — необходимо, чтобы он добавил сервер.', created: 'Конфиг создан.', quotaExceeded: 'Достигнут лимит конфигов для вашей роли.', showLink: 'Ссылка / QR', rotate: 'Перевыпустить', rotated: 'Конфиг перевыпущен.', revoked: 'Конфиг отозван.', confirmRevoke: 'Отозвать этот конфиг? Действие необратимо.', copied: 'Скопировано.', copyLink: 'Скопировать ссылку', loadingLink: 'Загрузка ссылки…', subscriptionLink: 'Ссылка-подписка (для клиента):', panelEmail: 'Email в панели: {{email}}', aggregatedSubscription: 'Общая подписка', aggregatedSubscriptionHint: 'Одна ссылка/QR со всеми активными конфигами — удобно добавить один раз в клиент.', status: { Active: 'Активен', Disabled: 'Отключён', Expired: 'Истёк', LimitReached: 'Лимит исчерпан', Revoked: 'Отозван', }, }, instructions: { title: 'Инструкции по подключению', intro: 'Как подключиться за три шага — на любом устройстве.', step1: 'Установите приложение для вашей ОС из списка ниже.', step2: 'На странице «Мои конфиги» скопируйте ссылку или откройте QR-код нужного конфига.', step3: 'Импортируйте ссылку или отсканируйте QR в приложении — готово.', appsTitle: 'Приложения', noApps: 'Каталог приложений пока пуст.', os: { IOS: 'iOS', Android: 'Android', Windows: 'Windows', MacOS: 'macOS', Linux: 'Linux', }, }, news: { title: 'Новости', empty: 'Пока нет новостей.', }, support: { title: 'Поддержка', empty: 'У вас пока нет обращений.', reportBug: 'Сообщить об ошибке', requestRole: 'Запросить роль', submit: 'Отправить', messageLabel: 'Опишите проблему или предложение', attachmentsLabel: 'Скриншоты (необязательно, до 5)', filesSelected: '{{count}} файл(ов) выбрано', existingRole: 'Существующая роль', newRole: 'Новая роль', selectRole: 'Выберите роль', newRoleName: 'Название роли', newRoleMaxConfigs: 'Количество конфигов (-1 — без лимита)', newRoleMaxIpLimit: 'Количество IP (-1 — без лимита)', justification: 'Обоснование', ticketCreated: 'Обращение отправлено.', roleRequestPending: 'У вас уже есть необработанная заявка на роль.', reply: 'Ответить', replyPlaceholder: 'Написать комментарий…', reopen: 'Переоткрыть', reopened: 'Тикет переоткрыт.', lastActivity: 'Последняя активность: {{date}}', requestedExistingRole: 'Запрошена роль: {{role}}', requestedNewRole: 'Запрошена новая роль «{{name}}» (конфигов: {{configs}}, IP: {{ip}})', type: { BugReport: 'Ошибка/предложение', RoleRequest: 'Заявка на роль', }, status: { Open: 'Открыт', Resolved: 'Решён', Closed: 'Закрыт', }, }, settings: { changePassword: 'Сменить пароль', currentPassword: 'Текущий пароль', currentPasswordRequired: 'Введите текущий пароль.', currentPasswordInvalid: 'Неверный текущий пароль.', newPassword: 'Новый пароль', passwordChanged: 'Пароль изменён.', changeUserName: 'Сменить логин', changeUserNameHint: 'Текущий логин: {{current}}.', newUserName: 'Новый логин', userNameChanged: 'Логин изменён.', telegramHint: 'Привязка Telegram нужна для входа без пароля и восстановления доступа.', telegramLinkedStatus: 'Привязан', link: 'Привязать Telegram', unlink: 'Отвязать', confirmUnlink: 'Отвязать Telegram от аккаунта?', telegramLinked: 'Telegram привязан.', telegramUnlinked: 'Telegram отвязан.', waitingForLink: 'Ожидание подтверждения в Telegram…', deleteAccount: 'Удалить аккаунт', deleteAccountHint: 'Отзовёт все конфиги и безвозвратно удалит аккаунт.', confirmDelete: 'Вы уверены? Это действие необратимо.', confirmDeleteYes: 'Да, удалить', cancel: 'Отмена', }, admin: { prev: 'Назад', next: 'Вперёд', tabs: { overview: 'Обзор', activation: 'Запросы на активацию', users: 'Пользователи', configs: 'Все конфиги', roles: 'Роли', nodes: 'Ноды', apps: 'Приложения', news: 'Новости', support: 'Тикеты', audit: 'Аудит', maintenance: 'Обслуживание', }, users: { searchPlaceholder: 'Поиск по имени пользователя', userName: 'Имя пользователя', role: 'Роль', statusLabel: 'Статус', manage: 'Управление', empty: 'Пользователи не найдены.', total: 'Всего: {{count}}', status: { blocked: 'Заблокирован', active: 'Активен', pending: 'Не активирован', }, block: 'Заблокировать', unblock: 'Разблокировать', confirmBlock: 'Заблокировать пользователя? Все его конфиги будут отключены.', blocked: 'Пользователь заблокирован.', unblocked: 'Пользователь разблокирован.', roleChanged: 'Роль изменена.', resetPassword: 'Сбросить пароль', reset: 'Сбросить', passwordReset: 'Пароль сброшен.', configs: 'Конфиги', delete: 'Удалить пользователя', confirmDelete: 'Удалить пользователя? Все его конфиги будут отозваны в 3x-ui, действие необратимо.', deleted: 'Пользователь удалён.', }, configs: { searchPlaceholder: 'Поиск по email в панели или метке', owner: 'Владелец', node: 'Нода', location: 'Локация', protocol: 'Протокол', traffic: 'Трафик', statusLabel: 'Статус', allStatuses: 'Все статусы', created: 'Создан', empty: 'Конфиги не найдены.', total: 'Всего: {{count}}', }, activation: { empty: 'Нет ожидающих запросов на активацию.', approved: 'Пользователь активирован.', rejected: 'Запрос отклонён.', approve: 'Активировать', reject: 'Отклонить', }, roles: { create: 'Создать роль', name: 'Название', maxConfigs: 'Квота конфигов', maxConfigsHint: '−1 = без лимита.', maxIpLimit: 'Лимит IP на конфиг', maxIpLimitHint: '−1 = без лимита. Применяется только к новым конфигам.', system: 'системная', edit: 'Изменить', save: 'Сохранить', delete: 'Удалить', confirmDelete: 'Удалить роль? Это действие необратимо.', created: 'Роль создана.', updated: 'Квота обновлена.', deleted: 'Роль удалена.', }, nodes: { create: 'Добавить ноду', name: 'Название', baseAddress: 'Адрес панели', username: 'Логин', password: 'Пароль', location: 'Локация', empty: 'Ноды не добавлены.', created: 'Нода добавлена.', updated: 'Нода обновлена.', deleted: 'Нода удалена.', confirmDelete: 'Удалить ноду? Существующие конфиги на ней перестанут синхронизироваться.', edit: 'Изменить', delete: 'Удалить', probe: 'Проверить', sync: 'Синхронизировать', probeSuccess: 'Нода доступна.', probeFailure: 'Нода недоступна: {{message}}', syncSuccess: 'Синхронизировано инбаундов: {{count}}', status: { Unknown: 'Неизвестно', Online: 'Онлайн', Offline: 'Офлайн', }, enabled: 'Включена', disabled: 'Отключена', inbounds: 'Инбаунды', noInbounds: 'Инбаунды не найдены — нажмите «Синхронизировать».', publish: 'Публикация', published: 'Опубликован', unpublished: 'Не опубликован', publishSaved: 'Настройки публикации сохранены.', displayName: 'Отображаемое имя', maxClients: 'Лимит клиентов (необязательно)', allowedRoles: 'Доступно ролям', isPublishedLabel: 'Опубликовать инбаунд', optional: 'необязательно', }, apps: { create: 'Добавить приложение', name: 'Название', downloadUrl: 'Ссылка на скачивание', os: 'ОС', description: 'Описание', iconUrl: 'Ссылка на иконку', sortOrder: 'Порядок', enabled: 'Включено', disabled: 'отключено', empty: 'Каталог приложений пуст.', created: 'Приложение добавлено.', updated: 'Приложение обновлено.', deleted: 'Приложение удалено.', confirmDelete: 'Удалить приложение из каталога?', }, news: { create: 'Добавить новость', title: 'Заголовок', body: 'Текст (Markdown)', preview: 'Предпросмотр', empty: 'Новостей пока нет.', created: 'Новость опубликована.', updated: 'Новость обновлена.', deleted: 'Новость удалена.', confirmDelete: 'Удалить новость?', }, support: { empty: 'Обращений пока нет.', resolve: 'Решено', resolved: 'Тикет отмечен как решённый.', close: 'Закрыть', closed: 'Тикет закрыт.', approve: 'Одобрить', approved: 'Заявка одобрена, роль выдана.', reject: 'Отклонить', rejected: 'Заявка отклонена.', }, audit: { time: 'Время', action: 'Действие', target: 'Объект', source: 'Источник', empty: 'Журнал аудита пуст.', }, maintenance: { closedTickets: { title: 'Закрытые обращения', description: 'Удалить все закрытые обращения вместе с перепиской и вложениями (файлами). Действие необратимо.', action: 'Удалить закрытые обращения', confirm: 'Удалить все закрытые обращения и файлы в них? Действие необратимо.', deleted: 'Удалено обращений: {{count}}.', }, audit: { title: 'Журнал аудита', description: 'Удалить записи журнала аудита старше указанного числа дней. Действие необратимо.', olderThan: 'Старше', days: 'дней', action: 'Удалить старые записи', confirm: 'Удалить записи аудита старше {{days}} дней? Действие необратимо.', deleted: 'Удалено записей аудита: {{count}}.', }, apps: { title: 'Отключённые приложения', description: 'Удалить все отключённые приложения из каталога. Действие необратимо.', action: 'Удалить отключённые приложения', confirm: 'Удалить все отключённые приложения? Действие необратимо.', deleted: 'Удалено приложений: {{count}}.', }, factoryReset: { dangerZone: 'Опасная зона', title: 'Полный сброс панели', description: 'Вернуть панель в состояние только что развёрнутой — удалить всех пользователей, конфиги, ноды и все данные. Ваш текущий аккаунт сохранится.', action: 'Сбросить панель', dialogTitle: 'Полный сброс панели', warningIntro: 'Будет удалено безвозвратно:', warningItems: [ 'Все пользователи, кроме вашего текущего аккаунта', 'Все VPN-конфиги (сначала будут отозваны на нодах 3x-ui)', 'Все ноды и инбаунды — регистрация серверов 3x-ui и сохранённые креды', 'Все обращения в поддержку вместе с перепиской и вложениями', 'Все новости', 'Весь журнал аудита', 'Все роли, кроме системных admin/user', 'Каталог приложений (будет пересеян из стартового набора)', ], irreversible: 'Действие необратимо и не может быть отменено.', confirmPhrase: 'СБРОС', confirmInputLabel: 'Введите «{{phrase}}» для подтверждения', confirmButton: 'Сбросить панель безвозвратно', done: 'Панель сброшена до начального состояния.', }, }, stats: { totalUsers: 'Всего пользователей', activatedUsers: 'Активировано', pendingActivationRequests: 'Ожидают активации', totalNodes: 'Всего нод', onlineNodes: 'Нод онлайн', totalConfigs: 'Всего конфигов', activeConfigs: 'Активных конфигов', totalTraffic: 'Суммарный трафик', }, }, }, }, en: { translation: { appName: 'PnvPanel', tagline: 'Self-service portal for VPN configurations', theme: 'Theme', language: 'Language', light: 'Light', dark: 'Dark', system: 'System', unlimited: 'Unlimited', auth: { loginTitle: 'Log in', registerTitle: 'Register', userName: 'Username', password: 'Password', submitLogin: 'Log in', submitRegister: 'Register', noAccount: "Don't have an account?", haveAccount: 'Already have an account?', goRegister: 'Register', goLogin: 'Log in', loginViaTelegram: 'Log in via Telegram', invalidCredentials: 'Invalid username or password.', duplicateUserName: 'A user with this name already exists.', genericError: 'Something went wrong. Please try again.', userNameHint: 'Latin letters, digits, "_", ".", "-", 3 to 32 characters.', passwordHint: 'At least 8 characters, with one uppercase letter and one digit.', or: 'or', telegramBotNotConfigured: 'The Telegram bot has not been configured by the administrator.', telegramQrHint: 'Follow the link or scan the QR code — it opens the bot in Telegram. If this is your first time, tap "Start".', waitingForConfirmation: 'Waiting for confirmation in Telegram…', telegramLoginRejected: 'Login was rejected in Telegram.', telegramLoginExpired: 'The request expired, please try again.', }, telegramLinkWarning: { message: "You haven't linked Telegram — without it you won't receive notifications and won't be able to recover your password yourself.", action: 'Link Telegram', dismiss: 'Dismiss', }, nav: { dashboard: 'My configs', instructions: 'Instructions', news: 'News', support: 'Support', settings: 'Settings', admin: 'Admin', logout: 'Log out', toggleMenu: 'Menu', }, activation: { title: 'Account not activated', description: 'Wait for an administrator to activate your account before creating configs. You can leave a comment with your request.', commentLabel: 'Comment (optional)', submit: 'Request activation', pending: 'Activation request sent, waiting for administrator review.', alreadyPending: 'You already have a pending activation request.', retry: 'Retry', }, configs: { title: 'My configs', quota: 'Used {{used}} of {{max}}', quotaUnlimited: 'Used {{used}}, unlimited', empty: "You don't have any configs yet. Create your first one.", create: 'Create config', selectLocation: 'Select location', location: 'Location', label: 'Label (optional)', noInboundsNotice: 'No locations are available for creating a config yet. Please contact the administrator — a server needs to be added.', created: 'Config created.', quotaExceeded: 'Config quota reached for your role.', showLink: 'Link / QR', rotate: 'Rotate', rotated: 'Config rotated.', revoked: 'Config revoked.', confirmRevoke: 'Revoke this config? This cannot be undone.', copied: 'Copied.', copyLink: 'Copy link', loadingLink: 'Loading link…', subscriptionLink: 'Subscription link (for the client app):', panelEmail: 'Panel email: {{email}}', aggregatedSubscription: 'Aggregated subscription', aggregatedSubscriptionHint: 'One link/QR with all active configs — add it once to your client.', status: { Active: 'Active', Disabled: 'Disabled', Expired: 'Expired', LimitReached: 'Limit reached', Revoked: 'Revoked', }, }, instructions: { title: 'Connection instructions', intro: 'Get connected in three steps, on any device.', step1: 'Install the app for your OS from the list below.', step2: 'On the "My configs" page, copy the link or open the QR code for the config you want.', step3: 'Import the link or scan the QR code in the app — done.', appsTitle: 'Apps', noApps: 'The app catalog is empty right now.', os: { IOS: 'iOS', Android: 'Android', Windows: 'Windows', MacOS: 'macOS', Linux: 'Linux', }, }, news: { title: 'News', empty: 'No news yet.', }, support: { title: 'Support', empty: 'You have no tickets yet.', reportBug: 'Report a bug', requestRole: 'Request a role', submit: 'Submit', messageLabel: 'Describe the issue or suggestion', attachmentsLabel: 'Screenshots (optional, up to 5)', filesSelected: '{{count}} file(s) selected', existingRole: 'Existing role', newRole: 'New role', selectRole: 'Select a role', newRoleName: 'Role name', newRoleMaxConfigs: 'Max configs (-1 = unlimited)', newRoleMaxIpLimit: 'Max IPs (-1 = unlimited)', justification: 'Justification', ticketCreated: 'Ticket submitted.', roleRequestPending: 'You already have a pending role request.', reply: 'Reply', replyPlaceholder: 'Write a comment…', reopen: 'Reopen', reopened: 'Ticket reopened.', lastActivity: 'Last activity: {{date}}', requestedExistingRole: 'Requested role: {{role}}', requestedNewRole: 'Requested new role "{{name}}" (configs: {{configs}}, IPs: {{ip}})', type: { BugReport: 'Bug/suggestion', RoleRequest: 'Role request', }, status: { Open: 'Open', Resolved: 'Resolved', Closed: 'Closed', }, }, settings: { changePassword: 'Change password', currentPassword: 'Current password', currentPasswordRequired: 'Enter your current password.', currentPasswordInvalid: 'Current password is incorrect.', newPassword: 'New password', passwordChanged: 'Password changed.', changeUserName: 'Change username', changeUserNameHint: 'Current username: {{current}}.', newUserName: 'New username', userNameChanged: 'Username changed.', telegramHint: 'Linking Telegram enables passwordless login and account recovery.', telegramLinkedStatus: 'Linked', link: 'Link Telegram', unlink: 'Unlink', confirmUnlink: 'Unlink Telegram from your account?', telegramLinked: 'Telegram linked.', telegramUnlinked: 'Telegram unlinked.', waitingForLink: 'Waiting for confirmation in Telegram…', deleteAccount: 'Delete account', deleteAccountHint: 'Revokes all configs and permanently deletes your account.', confirmDelete: 'Are you sure? This cannot be undone.', confirmDeleteYes: 'Yes, delete', cancel: 'Cancel', }, admin: { prev: 'Previous', next: 'Next', tabs: { overview: 'Overview', activation: 'Activation requests', users: 'Users', configs: 'All configs', roles: 'Roles', nodes: 'Nodes', apps: 'Apps', news: 'News', support: 'Tickets', audit: 'Audit', maintenance: 'Maintenance', }, users: { searchPlaceholder: 'Search by username', userName: 'Username', role: 'Role', statusLabel: 'Status', manage: 'Manage', empty: 'No users found.', total: 'Total: {{count}}', status: { blocked: 'Blocked', active: 'Active', pending: 'Not activated', }, block: 'Block', unblock: 'Unblock', confirmBlock: 'Block this user? All their configs will be disabled.', blocked: 'User blocked.', unblocked: 'User unblocked.', roleChanged: 'Role changed.', resetPassword: 'Reset password', reset: 'Reset', passwordReset: 'Password reset.', configs: 'Configs', delete: 'Delete user', confirmDelete: 'Delete this user? All their configs will be revoked in 3x-ui — this cannot be undone.', deleted: 'User deleted.', }, configs: { searchPlaceholder: 'Search by panel email or label', owner: 'Owner', node: 'Node', location: 'Location', protocol: 'Protocol', traffic: 'Traffic', statusLabel: 'Status', allStatuses: 'All statuses', created: 'Created', empty: 'No configs found.', total: 'Total: {{count}}', }, activation: { empty: 'No pending activation requests.', approved: 'User activated.', rejected: 'Request rejected.', approve: 'Approve', reject: 'Reject', }, roles: { create: 'Create role', name: 'Name', maxConfigs: 'Config quota', maxConfigsHint: '−1 = unlimited.', maxIpLimit: 'IP limit per config', maxIpLimitHint: '−1 = unlimited. Applies to new configs only.', system: 'system', edit: 'Edit', save: 'Save', delete: 'Delete', confirmDelete: 'Delete this role? This cannot be undone.', created: 'Role created.', updated: 'Quota updated.', deleted: 'Role deleted.', }, nodes: { create: 'Add node', name: 'Name', baseAddress: 'Panel address', username: 'Username', password: 'Password', location: 'Location', empty: 'No nodes added yet.', created: 'Node added.', updated: 'Node updated.', deleted: 'Node deleted.', confirmDelete: 'Delete this node? Existing configs on it will stop syncing.', edit: 'Edit', delete: 'Delete', probe: 'Probe', sync: 'Sync', probeSuccess: 'Node is reachable.', probeFailure: 'Node unreachable: {{message}}', syncSuccess: 'Synced inbounds: {{count}}', status: { Unknown: 'Unknown', Online: 'Online', Offline: 'Offline', }, enabled: 'Enabled', disabled: 'Disabled', inbounds: 'Inbounds', noInbounds: 'No inbounds found — click "Sync".', publish: 'Publishing', published: 'Published', unpublished: 'Not published', publishSaved: 'Publishing settings saved.', displayName: 'Display name', maxClients: 'Client limit (optional)', allowedRoles: 'Allowed for roles', isPublishedLabel: 'Publish inbound', optional: 'optional', }, apps: { create: 'Add app', name: 'Name', downloadUrl: 'Download link', os: 'OS', description: 'Description', iconUrl: 'Icon URL', sortOrder: 'Sort order', enabled: 'Enabled', disabled: 'disabled', empty: 'The app catalog is empty.', created: 'App added.', updated: 'App updated.', deleted: 'App deleted.', confirmDelete: 'Remove this app from the catalog?', }, news: { create: 'Add post', title: 'Title', body: 'Body (Markdown)', preview: 'Preview', empty: 'No news yet.', created: 'Post published.', updated: 'Post updated.', deleted: 'Post deleted.', confirmDelete: 'Delete this post?', }, support: { empty: 'No tickets yet.', resolve: 'Resolve', resolved: 'Ticket marked as resolved.', close: 'Close', closed: 'Ticket closed.', approve: 'Approve', approved: 'Request approved, role granted.', reject: 'Reject', rejected: 'Request rejected.', }, audit: { time: 'Time', action: 'Action', target: 'Target', source: 'Source', empty: 'The audit log is empty.', }, maintenance: { closedTickets: { title: 'Closed tickets', description: 'Delete all closed tickets along with their comments and attachments (files). This cannot be undone.', action: 'Delete closed tickets', confirm: 'Delete all closed tickets and their files? This cannot be undone.', deleted: 'Deleted tickets: {{count}}.', }, audit: { title: 'Audit log', description: 'Delete audit log entries older than the given number of days. This cannot be undone.', olderThan: 'Older than', days: 'days', action: 'Delete old entries', confirm: 'Delete audit entries older than {{days}} days? This cannot be undone.', deleted: 'Deleted audit entries: {{count}}.', }, apps: { title: 'Disabled apps', description: 'Delete all disabled apps from the catalog. This cannot be undone.', action: 'Delete disabled apps', confirm: 'Delete all disabled apps? This cannot be undone.', deleted: 'Deleted apps: {{count}}.', }, factoryReset: { dangerZone: 'Danger zone', title: 'Full panel reset', description: 'Return the panel to a freshly-deployed state — deletes all users, configs, nodes and data. Your current account is kept.', action: 'Reset panel', dialogTitle: 'Full panel reset', warningIntro: 'This will permanently delete:', warningItems: [ 'All users except your current account', 'All VPN configs (revoked on the 3x-ui nodes first)', 'All nodes and inbounds — 3x-ui server registrations and stored credentials', 'All support tickets with their comments and attachments', 'All news posts', 'The entire audit log', 'All roles except the system admin/user roles', 'The app catalog (will be re-seeded from the default set)', ], irreversible: 'This cannot be undone.', confirmPhrase: 'RESET', confirmInputLabel: 'Type "{{phrase}}" to confirm', confirmButton: 'Reset the panel permanently', done: 'The panel has been reset to its initial state.', }, }, stats: { totalUsers: 'Total users', activatedUsers: 'Activated', pendingActivationRequests: 'Pending activation', totalNodes: 'Total nodes', onlineNodes: 'Nodes online', totalConfigs: 'Total configs', activeConfigs: 'Active configs', totalTraffic: 'Total traffic', }, }, }, }, } const STORAGE_KEY = 'pnv-lang' const stored = typeof localStorage !== 'undefined' ? localStorage.getItem(STORAGE_KEY) : null void i18n.use(initReactI18next).init({ resources, lng: stored ?? 'ru', fallbackLng: 'ru', interpolation: { escapeValue: false }, }) export function setLanguage(lng: string) { localStorage.setItem(STORAGE_KEY, lng) void i18n.changeLanguage(lng) } export default i18n