i18n: fix critical untranslated strings across the UI
Some checks failed
Deploy to Production / Trigger production update (push) Failing after 10s

- DestroyTab: wrap all hardcoded English strings with t()
- instances: add status_reinstalling, status_changing_password keys
- common: add shutdown key (ru + en)
- fw: translate groups, ipsets, aliases, security_group and related actions
- nodeadm: translate upgrade button label
- en.json: fix Russian placeholder_notes in English locale
This commit is contained in:
2026-08-13 16:17:07 +05:00
parent d88647de77
commit 80d7a98256
3 changed files with 44 additions and 21 deletions

View File

@@ -1,5 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { useTranslation } from 'react-i18next';
import { AlertTriangle, Loader2 } from 'lucide-react'; import { AlertTriangle, Loader2 } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@@ -19,6 +20,7 @@ interface Props {
export default function DestroyTab({ serverId, vmid, type, node, name }: Props) { export default function DestroyTab({ serverId, vmid, type, node, name }: Props) {
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation();
const deleteVM = useDeleteVM(serverId, vmid, type, node); const deleteVM = useDeleteVM(serverId, vmid, type, node);
const [confirmation, setConfirmation] = useState(''); const [confirmation, setConfirmation] = useState('');
@@ -29,7 +31,7 @@ export default function DestroyTab({ serverId, vmid, type, node, name }: Props)
if (!canDelete) return; if (!canDelete) return;
deleteVM.mutate(undefined, { deleteVM.mutate(undefined, {
onSuccess: () => { onSuccess: () => {
toast.success(`${displayName} has been destroyed`); toast.success(t('instances.destroyed', { name: displayName }));
navigate('/instances'); navigate('/instances');
}, },
onError: (err) => toast.error(err.message), onError: (err) => toast.error(err.message),
@@ -42,21 +44,20 @@ export default function DestroyTab({ serverId, vmid, type, node, name }: Props)
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-destructive"> <CardTitle className="flex items-center gap-2 text-destructive">
<AlertTriangle className="h-5 w-5" /> <AlertTriangle className="h-5 w-5" />
Destroy {vmTypeLabel(type)} {t('instances.destroy_title', { type: vmTypeLabel(type) })}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive"> <div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
<p className="font-medium">This action is irreversible!</p> <p className="font-medium">{t('instances.destroy_irreversible')}</p>
<p className="mt-1 text-destructive/80"> <p className="mt-1 text-destructive/80">
This will permanently delete <strong>{displayName}</strong> (#{vmid}) and all its data, {t('instances.destroy_warning_before')} <strong>{displayName}</strong> (#{vmid}){t('instances.destroy_warning_after')}
including disks and snapshots. IP allocations will be released.
</p> </p>
</div> </div>
<div> <div>
<Label htmlFor="confirm-name"> <Label htmlFor="confirm-name">
Type <strong className="text-foreground">{displayName}</strong> to confirm {t('instances.destroy_confirm_label_before')} <strong className="text-foreground">{displayName}</strong> {t('instances.destroy_confirm_label_after')}
</Label> </Label>
<Input <Input
id="confirm-name" id="confirm-name"
@@ -74,7 +75,7 @@ export default function DestroyTab({ serverId, vmid, type, node, name }: Props)
className="w-full" className="w-full"
> >
{deleteVM.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />} {deleteVM.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Permanently Destroy {vmTypeLabel(type)} {t('instances.destroy_button', { type: vmTypeLabel(type) })}
</Button> </Button>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -303,6 +303,7 @@
"console": "Console", "console": "Console",
"start": "Start", "start": "Start",
"stop": "Stop", "stop": "Stop",
"shutdown": "Shutdown",
"restart": "Restart", "restart": "Restart",
"snapshots": "Snapshots", "snapshots": "Snapshots",
"suspend": "Suspend", "suspend": "Suspend",
@@ -357,7 +358,7 @@
"placeholder_snapshot_name": "e.g. before-update", "placeholder_snapshot_name": "e.g. before-update",
"placeholder_root_pam": "root@pam", "placeholder_root_pam": "root@pam",
"placeholder_disk": "scsi0", "placeholder_disk": "scsi0",
"placeholder_notes": "Описание / заметки об инстансе...", "placeholder_notes": "Description / notes about the instance...",
"ipam": "IPAM" "ipam": "IPAM"
}, },
"snap_archive": { "snap_archive": {
@@ -530,7 +531,17 @@
"backup_now": "Create backup", "backup_now": "Create backup",
"backup_started": "Backup started in background", "backup_started": "Backup started in background",
"backup_create": "Create backup", "backup_create": "Create backup",
"backup_no_storages": "No backup-capable storages on this node" "backup_no_storages": "No backup-capable storages on this node",
"destroy_title": "Destroy {{type}}",
"destroy_irreversible": "This action is irreversible!",
"destroy_warning_before": "This will permanently delete",
"destroy_warning_after": " and all its data, including disks and snapshots. IP allocations will be released.",
"destroy_confirm_label_before": "Type",
"destroy_confirm_label_after": "to confirm",
"destroy_button": "Permanently Destroy {{type}}",
"destroyed": "{{name}} has been destroyed",
"status_reinstalling": "Reinstalling",
"status_changing_password": "Changing password"
}, },
"wizard": { "wizard": {
"title": "Create Instance", "title": "Create Instance",

View File

@@ -303,6 +303,7 @@
"console": "Консоль", "console": "Консоль",
"start": "Запустить", "start": "Запустить",
"stop": "Остановить", "stop": "Остановить",
"shutdown": "Выключить",
"restart": "Перезагрузить", "restart": "Перезагрузить",
"snapshots": "Снимки", "snapshots": "Снимки",
"suspend": "Приостановить", "suspend": "Приостановить",
@@ -530,7 +531,17 @@
"backup_now": "Создать резервную копию", "backup_now": "Создать резервную копию",
"backup_started": "Бэкап запущен в фоне", "backup_started": "Бэкап запущен в фоне",
"backup_create": "Создать бэкап", "backup_create": "Создать бэкап",
"backup_no_storages": "Нет хранилищ с поддержкой бэкапов на этой ноде" "backup_no_storages": "Нет хранилищ с поддержкой бэкапов на этой ноде",
"destroy_title": "Уничтожить {{type}}",
"destroy_irreversible": "Это действие необратимо!",
"destroy_warning_before": "Будут безвозвратно удалены",
"destroy_warning_after": " и все данные, включая диски и снимки. IP-адреса будут освобождены.",
"destroy_confirm_label_before": "Введите",
"destroy_confirm_label_after": "для подтверждения",
"destroy_button": "Безвозвратно уничтожить {{type}}",
"destroyed": "{{name}} уничтожен",
"status_reinstalling": "Переустановка",
"status_changing_password": "Смена пароля"
}, },
"wizard": { "wizard": {
"title": "Создание инстанса", "title": "Создание инстанса",
@@ -808,16 +819,16 @@
"enable": "Включить", "enable": "Включить",
"disable": "Выключить", "disable": "Выключить",
"rules": "Правила", "rules": "Правила",
"groups": "Security Groups", "groups": "Группы безопасности",
"ipsets": "IP Sets", "ipsets": "IP-множества",
"aliases": "Aliases", "aliases": "Псевдонимы",
"options": "Опции", "options": "Опции",
"add_rule": "Добавить правило", "add_rule": "Добавить правило",
"rule_hint": "Правила применяются сверху вниз.", "rule_hint": "Правила применяются сверху вниз.",
"direction": "Направление", "direction": "Направление",
"action": "Действие", "action": "Действие",
"match": "Источник → Назначение", "match": "Источник → Назначение",
"security_group": "Security group", "security_group": "Группа безопасности",
"select_group": "Выберите группу", "select_group": "Выберите группу",
"proto": "Протокол", "proto": "Протокол",
"macro": "Макрос", "macro": "Макрос",
@@ -827,18 +838,18 @@
"sport": "Порт источн.", "sport": "Порт источн.",
"enabled": "Включено", "enabled": "Включено",
"log": "Логировать", "log": "Логировать",
"err_group_required": "Выберите security group", "err_group_required": "Выберите группу безопасности",
"rule_added": "Правило добавлено", "rule_added": "Правило добавлено",
"rule_deleted": "Правило удалено", "rule_deleted": "Правило удалено",
"delete_rule_confirm": "Удалить правило?", "delete_rule_confirm": "Удалить правило?",
"add_group": "Добавить security group", "add_group": "Добавить группу безопасности",
"delete_group": "Удалить security group", "delete_group": "Удалить группу безопасности",
"group_name": "Имя группы", "group_name": "Имя группы",
"group_added": "Группа создана", "group_added": "Группа создана",
"group_deleted": "Группа удалена", "group_deleted": "Группа удалена",
"add_ipset": "Добавить IP set", "add_ipset": "Добавить IP-множество",
"delete_ipset": "Удалить IP set", "delete_ipset": "Удалить IP-множество",
"ipset_name": "Имя IP set", "ipset_name": "Имя IP-множества",
"ipset_added": "IP set создан", "ipset_added": "IP set создан",
"ipset_deleted": "IP set удалён", "ipset_deleted": "IP set удалён",
"add_alias": "Добавить алиас", "add_alias": "Добавить алиас",
@@ -966,7 +977,7 @@
"updates_count": "Доступно обновлений: {{n}}", "updates_count": "Доступно обновлений: {{n}}",
"upgrade_hint": "«Установить обновления» откроет консоль ноды с apt dist-upgrade (потребуется подтверждение). Не закрывайте вкладку до завершения.", "upgrade_hint": "«Установить обновления» откроет консоль ноды с apt dist-upgrade (потребуется подтверждение). Не закрывайте вкладку до завершения.",
"do_upgrade": "Установить обновления", "do_upgrade": "Установить обновления",
"upgrade": "Upgrade", "upgrade": "Обновить",
"repo_updated": "Репозиторий обновлён", "repo_updated": "Репозиторий обновлён",
"enable": "Включить", "enable": "Включить",
"disable": "Выключить" "disable": "Выключить"