Translate backups
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
379be2955b
commit
b50fa94bf2
4 files changed
+159
-53
No files matched your search
@@ -15,6 +15,7 @@ import {
|
||||
Trash2,
|
||||
Upload,
|
||||
} from "lucide-react";
|
||||
import { useLanguage } from "@/components/language-provider";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -31,6 +32,7 @@ import { Select } from "@/components/ui/select";
|
||||
import { SkeletonRows } from "@/components/ui/skeleton";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { BACKUP_TABLE_GROUPS } from "@/lib/backups/table-groups";
|
||||
import type { TranslationKey } from "@/lib/i18n/types";
|
||||
import type { BackupItem, BackupSettings } from "./types";
|
||||
import {
|
||||
WEEKDAYS,
|
||||
@@ -46,6 +48,7 @@ import {
|
||||
} from "./utils";
|
||||
|
||||
export default function BackupsPage() {
|
||||
const { t } = useLanguage();
|
||||
const queryClient = useQueryClient();
|
||||
const restoreInput = useRef<HTMLInputElement | null>(null);
|
||||
const savedSettings = useRef<BackupSettings | null>(null);
|
||||
@@ -116,10 +119,10 @@ export default function BackupsPage() {
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl md:text-3xl font-medium text-neutral-900">
|
||||
Database Backups
|
||||
{t("backups.title")}
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-neutral-500">
|
||||
Export database records and store them in the configured R2 bucket.
|
||||
{t("backups.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -131,17 +134,17 @@ export default function BackupsPage() {
|
||||
onChange={(event) => {
|
||||
const file = event.target.files?.[0];
|
||||
event.target.value = "";
|
||||
if (!file || !window.confirm("Restore this backup? This replaces all current database records and may sign you out.")) return;
|
||||
if (!file || !window.confirm(t("backups.restoreConfirm"))) return;
|
||||
restore.mutate(file);
|
||||
}}
|
||||
/>
|
||||
<Button onClick={() => runBackup.mutate()} disabled={runBackup.isPending || !backupConfigured}>
|
||||
<Play size={18} />
|
||||
{runBackup.isPending ? "Starting..." : "Back up"}
|
||||
{runBackup.isPending ? t("backups.starting") : t("backups.backUp")}
|
||||
</Button>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<Button type="button" variant="outline" className="h-10 w-10 px-0" aria-label="Backup actions">
|
||||
<Button type="button" variant="outline" className="h-10 w-10 px-0" aria-label={t("backups.actions")}>
|
||||
<MoreVertical className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenu.Trigger>
|
||||
@@ -149,11 +152,11 @@ export default function BackupsPage() {
|
||||
<DropdownMenu.Content align="end" sideOffset={6} className="z-50 min-w-48 rounded-lg border border-neutral-200 bg-white p-1 text-sm shadow-lg">
|
||||
<DropdownMenu.Item disabled={restore.isPending} onSelect={() => restoreInput.current?.click()} className="flex cursor-pointer items-center gap-2 rounded-md px-3 py-2 text-neutral-900 outline-none hover:bg-neutral-100 focus:bg-neutral-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50">
|
||||
<Upload className="h-4 w-4" />
|
||||
{restore.isPending ? "Restoring..." : "Restore from backup"}
|
||||
{restore.isPending ? t("backups.restoring") : t("backups.restore")}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item disabled={!settings || autoSaveSettings.isPending} onSelect={() => setSettingsOpen(true)} className="flex cursor-pointer items-center gap-2 rounded-md px-3 py-2 text-neutral-900 outline-none hover:bg-neutral-100 focus:bg-neutral-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50">
|
||||
<Settings className="h-4 w-4" />
|
||||
Settings
|
||||
{t("backups.settings")}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
@@ -164,8 +167,8 @@ export default function BackupsPage() {
|
||||
<Dialog open={settingsOpen} onOpenChange={setSettingsOpen}>
|
||||
<DialogContent className="max-h-[calc(100vh-4rem)] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Tables to back up</DialogTitle>
|
||||
<DialogDescription>Choose related groups of tables for manual and automatic backups. All groups are enabled by default. Restore requires a backup containing every group.</DialogDescription>
|
||||
<DialogTitle>{t("backups.tablesTitle")}</DialogTitle>
|
||||
<DialogDescription>{t("backups.tablesDescription")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
{settings && <div className="space-y-5">
|
||||
<div className="divide-y divide-neutral-100">
|
||||
@@ -173,20 +176,20 @@ export default function BackupsPage() {
|
||||
const enabled = !settings.excludedTableGroups.includes(group.id);
|
||||
const lastEnabled = enabled && settings.excludedTableGroups.length === BACKUP_TABLE_GROUPS.length - 1;
|
||||
return <div key={group.id} className="flex items-start justify-between gap-4 py-4 first:pt-0 last:pb-0">
|
||||
<div className="min-w-0"><p className="text-sm font-medium text-neutral-900">{group.label}</p><p className="mt-1 break-words text-xs text-neutral-500">Tables: {group.tables.join(", ")}</p></div>
|
||||
<Switch checked={enabled} disabled={lastEnabled} onCheckedChange={(checked) => setSettings({ ...settings, excludedTableGroups: checked ? settings.excludedTableGroups.filter((id) => id !== group.id) : [...settings.excludedTableGroups, group.id] })} aria-label={`Back up ${group.label}`} />
|
||||
<div className="min-w-0"><p className="text-sm font-medium text-neutral-900">{group.label}</p><p className="mt-1 break-words text-xs text-neutral-500">{t("backups.tables", { list: group.tables.join(", ") })}</p></div>
|
||||
<Switch checked={enabled} disabled={lastEnabled} onCheckedChange={(checked) => setSettings({ ...settings, excludedTableGroups: checked ? settings.excludedTableGroups.filter((id) => id !== group.id) : [...settings.excludedTableGroups, group.id] })} aria-label={t("backups.backUpGroup", { group: group.label })} />
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
{saveSettings.error && <p className="text-sm text-red-700">{saveSettings.error instanceof Error ? saveSettings.error.message : "Failed to save backup settings"}</p>}
|
||||
<Button onClick={() => saveSettings.mutate(settings)} disabled={saveSettings.isPending}><Save className="h-4 w-4" />{saveSettings.isPending ? "Saving..." : "Save settings"}</Button>
|
||||
{saveSettings.error && <p className="text-sm text-red-700">{saveSettings.error instanceof Error ? saveSettings.error.message : t("backups.saveSettingsFailed")}</p>}
|
||||
<Button onClick={() => saveSettings.mutate(settings)} disabled={saveSettings.isPending}><Save className="h-4 w-4" />{saveSettings.isPending ? t("common.saving") : t("backups.saveSettings")}</Button>
|
||||
</div>}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{error && (
|
||||
<p className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
{error instanceof Error ? error.message : "Backup operation failed"}
|
||||
{error instanceof Error ? error.message : t("backups.operationFailed")}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -197,12 +200,10 @@ export default function BackupsPage() {
|
||||
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-amber-700" />
|
||||
<div>
|
||||
<CardTitle className="text-amber-950">
|
||||
Complete backup setup
|
||||
{t("backups.setupTitle")}
|
||||
</CardTitle>
|
||||
<CardDescription className="mt-1 text-amber-800">
|
||||
Add the missing values under the deployed Worker's
|
||||
Variables and Secrets settings. This check disappears after
|
||||
backup configuration is complete.
|
||||
{t("backups.setupDescription")}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
@@ -229,7 +230,7 @@ export default function BackupsPage() {
|
||||
<RefreshCw
|
||||
className={`h-4 w-4 ${backups.isFetching ? "animate-spin" : ""}`}
|
||||
/>
|
||||
Check again
|
||||
{t("backups.checkAgain")}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -238,17 +239,16 @@ export default function BackupsPage() {
|
||||
|
||||
<Card className="rounded-3xl border-0 bg-white p-6">
|
||||
<CardHeader className="py-0">
|
||||
<CardTitle>Automatic backup</CardTitle>
|
||||
<CardTitle>{t("backups.automatic")}</CardTitle>
|
||||
<CardDescription>
|
||||
The schedule runs at 02:00 UTC. Monthly schedules are limited to
|
||||
days 1-28.
|
||||
{t("backups.scheduleNote")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-5 pt-5">
|
||||
{settings && (
|
||||
<>
|
||||
<div className="flex items-center justify-between gap-4 border-t border-neutral-100 pt-5">
|
||||
<Label htmlFor="automatic-backup-switch">Enable Automatic backup</Label>
|
||||
<Label htmlFor="automatic-backup-switch">{t("backups.enableAutomatic")}</Label>
|
||||
<Switch
|
||||
id="automatic-backup-switch"
|
||||
checked={settings.enabled}
|
||||
@@ -260,7 +260,7 @@ export default function BackupsPage() {
|
||||
<>
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<Label htmlFor="schedule-type" className="flex-1">Frequency</Label>
|
||||
<Label htmlFor="schedule-type" className="flex-1">{t("backups.frequency")}</Label>
|
||||
<Select
|
||||
id="schedule-type"
|
||||
value={settings.scheduleType}
|
||||
@@ -282,15 +282,15 @@ export default function BackupsPage() {
|
||||
}}
|
||||
className="px-3 py-2"
|
||||
>
|
||||
<option value="daily">Daily</option>
|
||||
<option value="weekly">Selected day of week</option>
|
||||
<option value="monthly">Selected day of month</option>
|
||||
<option value="daily">{t("backups.daily")}</option>
|
||||
<option value="weekly">{t("backups.weekly")}</option>
|
||||
<option value="monthly">{t("backups.monthly")}</option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{settings.scheduleType === "weekly" && (
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<Label htmlFor="weekday">Day of week</Label>
|
||||
<Label htmlFor="weekday">{t("backups.dayOfWeek")}</Label>
|
||||
<Select
|
||||
id="weekday"
|
||||
value={settings.scheduleValue ?? 1}
|
||||
@@ -307,7 +307,7 @@ export default function BackupsPage() {
|
||||
>
|
||||
{WEEKDAYS.map((day) => (
|
||||
<option key={day.value} value={day.value}>
|
||||
{day.label}
|
||||
{t(day.labelKey)}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
@@ -316,7 +316,7 @@ export default function BackupsPage() {
|
||||
|
||||
{settings.scheduleType === "monthly" && (
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<Label htmlFor="month-day">Day of month</Label>
|
||||
<Label htmlFor="month-day">{t("backups.dayOfMonth")}</Label>
|
||||
<Input
|
||||
id="month-day"
|
||||
className="w-1/2 max-w-52 shrink-0"
|
||||
@@ -342,7 +342,7 @@ export default function BackupsPage() {
|
||||
|
||||
<div className="grid gap-4 border-t border-neutral-100 pt-5">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<Label htmlFor="retention-switch">Delete old backups automatically</Label>
|
||||
<Label htmlFor="retention-switch">{t("backups.deleteOld")}</Label>
|
||||
<Switch
|
||||
id="retention-switch"
|
||||
checked={settings.retentionEnabled}
|
||||
@@ -358,7 +358,7 @@ export default function BackupsPage() {
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<Label htmlFor="retention-days">
|
||||
Delete backups older than
|
||||
{t("backups.deleteOlderThan")}
|
||||
</Label>
|
||||
<div className="flex w-1/2 max-w-52 shrink-0 items-center gap-2 relative">
|
||||
<Input
|
||||
@@ -380,7 +380,7 @@ export default function BackupsPage() {
|
||||
else setSettings({ ...settings, retentionDays: savedSettings.current?.retentionDays ?? 30 });
|
||||
}}
|
||||
/>
|
||||
<span className="text-sm text-neutral-500 absolute z-10 right-6">days</span>
|
||||
<span className="text-sm text-neutral-500 absolute z-10 right-6">{t("backups.days")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -394,18 +394,18 @@ export default function BackupsPage() {
|
||||
<section className="overflow-hidden rounded-3xl bg-white">
|
||||
<div className="flex items-center gap-3 border-b border-neutral-100 px-4 py-4">
|
||||
<DatabaseBackup className="h-5 w-5 text-neutral-500" />
|
||||
<h2 className="font-semibold text-neutral-900">Backup history</h2>
|
||||
<h2 className="font-semibold text-neutral-900">{t("backups.history")}</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-[1fr_110px_110px_170px_120px] gap-4 border-b border-neutral-100 bg-neutral-50 px-4 py-2 text-xs font-semibold uppercase tracking-wide text-neutral-500">
|
||||
<span>File</span>
|
||||
<span>Status</span>
|
||||
<span>Size</span>
|
||||
<span>Created</span>
|
||||
<span>Actions</span>
|
||||
<span>{t("backups.col.file")}</span>
|
||||
<span>{t("backups.col.status")}</span>
|
||||
<span>{t("backups.col.size")}</span>
|
||||
<span>{t("backups.col.created")}</span>
|
||||
<span>{t("backups.col.actions")}</span>
|
||||
</div>
|
||||
{backups.isLoading && <SkeletonRows count={5} />}
|
||||
{!backups.isLoading && (backups.data?.backups ?? []).length === 0 && (
|
||||
<p className="px-4 py-6 text-sm text-neutral-500">No backups yet.</p>
|
||||
<p className="px-4 py-6 text-sm text-neutral-500">{t("backups.none")}</p>
|
||||
)}
|
||||
{(backups.data?.backups ?? []).map((backup: BackupItem) => (
|
||||
<div
|
||||
@@ -417,12 +417,12 @@ export default function BackupsPage() {
|
||||
{backup.filename ?? backup.id}
|
||||
</p>
|
||||
<p className="truncate text-xs text-neutral-500">
|
||||
{backup.trigger === "manual" ? "Manual" : "Scheduled"}
|
||||
{backup.trigger === "manual" ? t("backups.manual") : t("backups.scheduled")}
|
||||
{backup.error ? `: ${backup.error}` : ""}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline" className={getStatusClass(backup.status)}>
|
||||
{backup.status}
|
||||
{t(`backups.status.${backup.status}` as TranslationKey)}
|
||||
</Badge>
|
||||
<span className="text-sm text-neutral-600">
|
||||
{formatBackupSize(backup.size)}
|
||||
@@ -434,7 +434,7 @@ export default function BackupsPage() {
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
title="Download backup"
|
||||
title={t("backups.download")}
|
||||
disabled={backup.status !== "completed" || download.isPending}
|
||||
onClick={() => download.mutate(backup)}
|
||||
>
|
||||
@@ -443,7 +443,7 @@ export default function BackupsPage() {
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
title="Delete backup"
|
||||
title={t("backups.delete")}
|
||||
disabled={
|
||||
deleteBackup.isPending ||
|
||||
backup.status === "queued" ||
|
||||
|
||||
@@ -3,13 +3,13 @@ import { formatUserDate } from "@/lib/time/utils";
|
||||
import type { BackupItem, BackupsResponse, BackupSettings } from "./types";
|
||||
|
||||
export const WEEKDAYS = [
|
||||
{ value: 0, label: "Sunday" },
|
||||
{ value: 1, label: "Monday" },
|
||||
{ value: 2, label: "Tuesday" },
|
||||
{ value: 3, label: "Wednesday" },
|
||||
{ value: 4, label: "Thursday" },
|
||||
{ value: 5, label: "Friday" },
|
||||
{ value: 6, label: "Saturday" },
|
||||
{ value: 0, labelKey: "weekday.0" as const },
|
||||
{ value: 1, labelKey: "weekday.1" as const },
|
||||
{ value: 2, labelKey: "weekday.2" as const },
|
||||
{ value: 3, labelKey: "weekday.3" as const },
|
||||
{ value: 4, labelKey: "weekday.4" as const },
|
||||
{ value: 5, labelKey: "weekday.5" as const },
|
||||
{ value: 6, labelKey: "weekday.6" as const },
|
||||
];
|
||||
|
||||
export async function fetchBackups(): Promise<BackupsResponse> {
|
||||
|
||||
+54
-1
@@ -1286,5 +1286,58 @@
|
||||
"webhooks.col.nextRetry": "Next retry",
|
||||
"webhooks.loadingDeliveries": "Loading deliveries…",
|
||||
"webhooks.noDeliveries": "No deliveries recorded yet.",
|
||||
"webhooks.retry": "Retry"
|
||||
"webhooks.retry": "Retry",
|
||||
"weekday.0": "Sunday",
|
||||
"weekday.1": "Monday",
|
||||
"weekday.2": "Tuesday",
|
||||
"weekday.3": "Wednesday",
|
||||
"weekday.4": "Thursday",
|
||||
"weekday.5": "Friday",
|
||||
"weekday.6": "Saturday",
|
||||
"backups.status.queued": "queued",
|
||||
"backups.status.running": "running",
|
||||
"backups.status.completed": "completed",
|
||||
"backups.status.failed": "failed",
|
||||
"backups.restoreConfirm": "Restore this backup? This replaces all current database records and may sign you out.",
|
||||
"backups.title": "Database Backups",
|
||||
"backups.description": "Export database records and store them in the configured R2 bucket.",
|
||||
"backups.starting": "Starting...",
|
||||
"backups.backUp": "Back up",
|
||||
"backups.actions": "Backup actions",
|
||||
"backups.restoring": "Restoring...",
|
||||
"backups.restore": "Restore from backup",
|
||||
"backups.settings": "Settings",
|
||||
"backups.tablesTitle": "Tables to back up",
|
||||
"backups.tablesDescription": "Choose related groups of tables for manual and automatic backups. All groups are enabled by default. Restore requires a backup containing every group.",
|
||||
"backups.tables": "Tables: {list}",
|
||||
"backups.backUpGroup": "Back up {group}",
|
||||
"backups.saveSettingsFailed": "Failed to save backup settings",
|
||||
"backups.saveSettings": "Save settings",
|
||||
"backups.operationFailed": "Backup operation failed",
|
||||
"backups.setupTitle": "Complete backup setup",
|
||||
"backups.setupDescription": "Add the missing values under the deployed Worker's Variables and Secrets settings. This check disappears after backup configuration is complete.",
|
||||
"backups.checkAgain": "Check again",
|
||||
"backups.automatic": "Automatic backup",
|
||||
"backups.scheduleNote": "The schedule runs at 02:00 UTC. Monthly schedules are limited to days 1-28.",
|
||||
"backups.enableAutomatic": "Enable Automatic backup",
|
||||
"backups.frequency": "Frequency",
|
||||
"backups.daily": "Daily",
|
||||
"backups.weekly": "Selected day of week",
|
||||
"backups.monthly": "Selected day of month",
|
||||
"backups.dayOfWeek": "Day of week",
|
||||
"backups.dayOfMonth": "Day of month",
|
||||
"backups.deleteOld": "Delete old backups automatically",
|
||||
"backups.deleteOlderThan": "Delete backups older than",
|
||||
"backups.days": "days",
|
||||
"backups.history": "Backup history",
|
||||
"backups.col.file": "File",
|
||||
"backups.col.status": "Status",
|
||||
"backups.col.size": "Size",
|
||||
"backups.col.created": "Created",
|
||||
"backups.col.actions": "Actions",
|
||||
"backups.none": "No backups yet.",
|
||||
"backups.manual": "Manual",
|
||||
"backups.scheduled": "Scheduled",
|
||||
"backups.download": "Download backup",
|
||||
"backups.delete": "Delete backup"
|
||||
}
|
||||
+54
-1
@@ -1286,5 +1286,58 @@
|
||||
"webhooks.col.nextRetry": "Próxima tentativa",
|
||||
"webhooks.loadingDeliveries": "Carregando entregas…",
|
||||
"webhooks.noDeliveries": "Nenhuma entrega registrada ainda.",
|
||||
"webhooks.retry": "Tentar novamente"
|
||||
"webhooks.retry": "Tentar novamente",
|
||||
"weekday.0": "Domingo",
|
||||
"weekday.1": "Segunda-feira",
|
||||
"weekday.2": "Terça-feira",
|
||||
"weekday.3": "Quarta-feira",
|
||||
"weekday.4": "Quinta-feira",
|
||||
"weekday.5": "Sexta-feira",
|
||||
"weekday.6": "Sábado",
|
||||
"backups.status.queued": "na fila",
|
||||
"backups.status.running": "em execução",
|
||||
"backups.status.completed": "concluído",
|
||||
"backups.status.failed": "falhou",
|
||||
"backups.restoreConfirm": "Restaurar este backup? Isto substitui todos os registros atuais do banco de dados e pode desconectar você.",
|
||||
"backups.title": "Backups do banco de dados",
|
||||
"backups.description": "Exporte os registros do banco de dados e armazene-os no bucket R2 configurado.",
|
||||
"backups.starting": "Iniciando...",
|
||||
"backups.backUp": "Fazer backup",
|
||||
"backups.actions": "Ações de backup",
|
||||
"backups.restoring": "Restaurando...",
|
||||
"backups.restore": "Restaurar de um backup",
|
||||
"backups.settings": "Configurações",
|
||||
"backups.tablesTitle": "Tabelas para backup",
|
||||
"backups.tablesDescription": "Escolha grupos de tabelas relacionadas para backups manuais e automáticos. Todos os grupos vêm ativados por padrão. A restauração exige um backup que contenha todos os grupos.",
|
||||
"backups.tables": "Tabelas: {list}",
|
||||
"backups.backUpGroup": "Fazer backup de {group}",
|
||||
"backups.saveSettingsFailed": "Falha ao salvar as configurações de backup",
|
||||
"backups.saveSettings": "Salvar configurações",
|
||||
"backups.operationFailed": "Falha na operação de backup",
|
||||
"backups.setupTitle": "Conclua a configuração do backup",
|
||||
"backups.setupDescription": "Adicione os valores ausentes nas configurações de Variáveis e Segredos do Worker implantado. Esta verificação desaparece depois que a configuração do backup for concluída.",
|
||||
"backups.checkAgain": "Verificar novamente",
|
||||
"backups.automatic": "Backup automático",
|
||||
"backups.scheduleNote": "O agendamento é executado às 02:00 UTC. Agendamentos mensais ficam limitados aos dias 1 a 28.",
|
||||
"backups.enableAutomatic": "Ativar backup automático",
|
||||
"backups.frequency": "Frequência",
|
||||
"backups.daily": "Diariamente",
|
||||
"backups.weekly": "Dia da semana selecionado",
|
||||
"backups.monthly": "Dia do mês selecionado",
|
||||
"backups.dayOfWeek": "Dia da semana",
|
||||
"backups.dayOfMonth": "Dia do mês",
|
||||
"backups.deleteOld": "Excluir backups antigos automaticamente",
|
||||
"backups.deleteOlderThan": "Excluir backups com mais de",
|
||||
"backups.days": "dias",
|
||||
"backups.history": "Histórico de backups",
|
||||
"backups.col.file": "Arquivo",
|
||||
"backups.col.status": "Status",
|
||||
"backups.col.size": "Tamanho",
|
||||
"backups.col.created": "Criado em",
|
||||
"backups.col.actions": "Ações",
|
||||
"backups.none": "Nenhum backup ainda.",
|
||||
"backups.manual": "Manual",
|
||||
"backups.scheduled": "Agendado",
|
||||
"backups.download": "Baixar backup",
|
||||
"backups.delete": "Excluir backup"
|
||||
}
|
||||
Reference in new issue
Block a user