diff --git a/client/src/api/storageReclaim.ts b/client/src/api/storageReclaim.ts index 42786cd5d..bb5974c8a 100644 --- a/client/src/api/storageReclaim.ts +++ b/client/src/api/storageReclaim.ts @@ -1,12 +1,14 @@ -import type { StorageReclaimReport, StorageReclaimState } from '@trek/shared'; +import type { StorageReclaimConfirmRequest, StorageReclaimState } from '@trek/shared'; import { apiClient } from './client'; -/** Admin > Storage, unreferenced files: the nightly deletion switch and a check on demand. */ +/** Admin > Storage, unreferenced files: the nightly deletion switch, a check on demand, and the large-share confirmation. */ export const storageReclaimApi = { setDelete: (deleteEnabled: boolean): Promise => apiClient.put('/admin/storage/reclaim', { deleteEnabled }).then((r) => r.data), - // A check lists every reclaimable category and asks each domain about it; a - // large bucket outlasts the 8s instance timeout. It only reports, whatever the switch says. - check: (): Promise => - apiClient.post('/admin/storage/reclaim/run', { reportOnly: true }, { timeout: 300_000 }).then((r) => r.data), + /** Starts a report-only check in the background (202); its report arrives through the admin state. */ + check: (): Promise<{ started: true }> => apiClient.post('/admin/storage/reclaim/run').then((r) => r.data), + confirm: (category: StorageReclaimConfirmRequest['category']): Promise => + apiClient + .post('/admin/storage/reclaim/confirm', { category } satisfies StorageReclaimConfirmRequest) + .then((r) => r.data), }; diff --git a/client/src/components/Admin/storage/AdminStoragePanel.test.tsx b/client/src/components/Admin/storage/AdminStoragePanel.test.tsx index 0b6cea26a..9be430ea9 100644 --- a/client/src/components/Admin/storage/AdminStoragePanel.test.tsx +++ b/client/src/components/Admin/storage/AdminStoragePanel.test.tsx @@ -14,9 +14,10 @@ const S3_MASKED = { /** A last check with three covers a week old and two younger ones; every other category never reclaimed. */ function reclaimReport(): StorageReclaimReport { const categories = Object.fromEntries( - STORAGE_RECLAIM_CATEGORIES.map((c) => [c, { objects: 0, unreferenced: { objects: 0, bytes: 0 }, eligible: { objects: 0, bytes: 0 }, deleted: { objects: 0, bytes: 0 }, skipped: 'not_reclaimed' as const }]), + STORAGE_RECLAIM_CATEGORIES.map((c) => [c, { objects: 0, unreferenced: { objects: 0, bytes: 0 }, eligible: { objects: 0, bytes: 0 }, deleted: { objects: 0, bytes: 0 }, confirmationRequired: false, skipped: 'not_reclaimed' as const }]), ) as StorageReclaimReport['categories']; - categories.covers = { objects: 40, unreferenced: { objects: 5, bytes: 5000 }, eligible: { objects: 3, bytes: 3000 }, deleted: { objects: 0, bytes: 0 }, skipped: null }; + categories.covers = { objects: 40, unreferenced: { objects: 5, bytes: 5000 }, eligible: { objects: 3, bytes: 3000 }, deleted: { objects: 0, bytes: 0 }, confirmationRequired: false, skipped: null }; + categories.places = { objects: 10, unreferenced: { objects: 3, bytes: 300 }, eligible: { objects: 0, bytes: 0 }, deleted: { objects: 0, bytes: 0 }, confirmationRequired: true, skipped: null }; return { startedAt: Date.now() - 60_000, finishedAt: Date.now() - 30_000, mode: 'report', stopped: null, categories }; } @@ -1228,27 +1229,54 @@ describe('AdminStoragePanel', () => { expect(within(row).getByRole('button', { name: 'Sync now' })).toBeInTheDocument(); }); - it('FE-ADMIN-STOR-051: unreferenced files: the last check shows, the switch and the check go to the server', async () => { + it('FE-ADMIN-STOR-051: unreferenced files: the last check shows, the switch, the check and a confirmation go to the server', async () => { const lastRun = reclaimReport(); let put: unknown = null; - let run: unknown = null; + let runs = 0; + let confirm: unknown = null; server.use( http.put('/api/admin/storage/reclaim', async ({ request }) => { put = await request.json(); - return HttpResponse.json({ deleteEnabled: true, lastRun }); + return HttpResponse.json({ deleteEnabled: true, running: false, lastRun, lastDeleteRun: null }); }), - http.post('/api/admin/storage/reclaim/run', async ({ request }) => { - run = await request.json(); - return HttpResponse.json(lastRun); + http.post('/api/admin/storage/reclaim/run', () => { + runs += 1; + return HttpResponse.json({ started: true }, { status: 202 }); + }), + http.post('/api/admin/storage/reclaim/confirm', async ({ request }) => { + confirm = await request.json(); + return HttpResponse.json({ deleteEnabled: true, running: false, lastRun, lastDeleteRun: null }); }), ); - await renderPanel(baseState({ reclaim: { deleteEnabled: false, lastRun } })); + await renderPanel(baseState({ reclaim: { deleteEnabled: false, running: false, lastRun, lastDeleteRun: null } })); + // Once a check started, the state the panel polls says it runs. + server.use( + http.get('/api/admin/storage', () => + HttpResponse.json(baseState({ reclaim: { deleteEnabled: true, running: runs > 0, lastRun, lastDeleteRun: null } })), + ), + ); expect(screen.getByText('Unreferenced files')).toBeInTheDocument(); expect(within(screen.getByTestId('storage-reclaim-covers')).getByText(/Can be freed/)).toBeInTheDocument(); + expect(within(screen.getByTestId('storage-reclaim-places')).getByText(/More than a fifth/)).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: 'Delete unreferenced files' })); await waitFor(() => expect(put).toEqual({ deleteEnabled: true })); await waitFor(() => expect(screen.getByRole('button', { name: 'Delete unreferenced files' })).toHaveAttribute('aria-pressed', 'true')); + fireEvent.click(within(screen.getByTestId('storage-reclaim-places')).getByRole('button', { name: 'Confirm' })); + await waitFor(() => expect(confirm).toEqual({ category: 'places' })); fireEvent.click(screen.getByRole('button', { name: 'Check now' })); - await waitFor(() => expect(run).toEqual({ reportOnly: true })); + await waitFor(() => expect(runs).toBe(1)); + // The check runs in the background: the panel shows it running until the state says otherwise. + await waitFor(() => expect(screen.getByText('Checking…')).toBeInTheDocument()); + }); + + it('FE-ADMIN-STOR-051b: a refused check names the reason in the operator\'s language', async () => { + server.use( + http.post('/api/admin/storage/reclaim/run', () => + HttpResponse.json({ error: 'busy', code: 'reclaim_busy' }, { status: 409 }), + ), + ); + await renderPanel(baseState({ reclaim: { deleteEnabled: false, running: false, lastRun: null, lastDeleteRun: null } })); + fireEvent.click(screen.getByRole('button', { name: 'Check now' })); + await waitFor(() => expect(screen.getByText('Another storage task is running. Try again in a moment.')).toBeInTheDocument()); }); }); diff --git a/client/src/components/Admin/storage/AdminStoragePanel.tsx b/client/src/components/Admin/storage/AdminStoragePanel.tsx index b1d7265e8..553629768 100644 --- a/client/src/components/Admin/storage/AdminStoragePanel.tsx +++ b/client/src/components/Admin/storage/AdminStoragePanel.tsx @@ -97,7 +97,7 @@ export default function AdminStoragePanel(): React.ReactElement { const { admin, editing, setEditing, confirmRemove, setConfirmRemove, syncPrompt, setSyncPrompt, migratePrompt, closeMigratePrompt, migrationQueue, handleCancelMigration, handleRefreshStats, handleStartBackfill, handleCancelBackfill, loaded, - reclaim, reclaimChecking, handleReclaimCheck, handleReclaimDelete, + reclaim, reclaimChecking, reclaimSaving, handleReclaimCheck, handleReclaimDelete, handleReclaimConfirm, } = useStoragePanel() // Open/closed only: the render below and moveAndSave both recompute // computeMigrationCandidates(draft, state) fresh rather than reading the @@ -633,6 +633,8 @@ export default function AdminStoragePanel(): React.ReactElement { void handleReclaimConfirm(category)} onCheck={() => void handleReclaimCheck()} onDelete={(enabled) => void handleReclaimDelete(enabled)} /> diff --git a/client/src/components/Admin/storage/StorageReclaimSection.tsx b/client/src/components/Admin/storage/StorageReclaimSection.tsx index 8851d4662..fd4084a63 100644 --- a/client/src/components/Admin/storage/StorageReclaimSection.tsx +++ b/client/src/components/Admin/storage/StorageReclaimSection.tsx @@ -11,17 +11,29 @@ import type { ReclaimCount, ReclaimView } from './reclaimModel'; interface Props { reclaim: ReclaimView; + /** A check was asked for or is running. */ checking: boolean; + /** The switch is on its way to the server. */ + saving: boolean; onCheck: () => void; onDelete: (enabled: boolean) => void; + onConfirm: (category: ReclaimView['lines'][number]['category']) => void; } /** * Unreferenced files on the desktop storage panel: what the last check found, - * the switch that lets the nightly run delete them, and a check on demand. The - * phone shows the same through MStorageReclaimCard, off the same view model. + * the switch that lets the nightly run delete them, a check on demand and the + * confirmation a category with an unusually large share of unused files waits + * for. The phone shows the same through MStorageReclaimCard, off the same view model. */ -export default function StorageReclaimSection({ reclaim, checking, onCheck, onDelete }: Props): React.ReactElement { +export default function StorageReclaimSection({ + reclaim, + checking, + saving, + onCheck, + onDelete, + onConfirm, +}: Props): React.ReactElement { const { t, locale } = useTranslation(); const count = (c: ReclaimCount) => t('storage.usage.line', { count: c.objects, size: formatBytes(c.bytes) }); const checked = reclaim.checkedAt !== null; @@ -44,7 +56,7 @@ export default function StorageReclaimSection({ reclaim, checking, onCheck, onDe ) : ( )} - {t('storage.reclaim.check')} + {checking ? t('storage.reclaim.running') : t('storage.reclaim.check')} } > @@ -52,11 +64,13 @@ export default function StorageReclaimSection({ reclaim, checking, onCheck, onDe onDelete(!reclaim.deleteEnabled)} label={t('storage.reclaim.delete')} + disabled={saving} /> } /> @@ -68,45 +82,59 @@ export default function StorageReclaimSection({ reclaim, checking, onCheck, onDe

{reclaim.stopped && (

- {t('storage.reclaim.stopped')} + {t(reclaim.stopped === 'failed' ? 'storage.reclaim.stoppedFailed' : 'storage.reclaim.stopped')}

)} {checked && (
-
+
)} - {checked && reclaim.lines.length === 0 && ( + {checked && !reclaim.stopped && reclaim.lines.length === 0 && ( {t('storage.reclaim.nothing')} )} {reclaim.lines.length > 0 && ( -
    +
      {reclaim.lines.map((line) => (
    • - - {t(`storage.category.${line.category}`)} - - {line.skipped ? ( - {t(`storage.reclaim.skip.${line.skipped}`)} - ) : ( - <> - {line.freeable.objects > 0 && ( - - {t('storage.reclaim.freeable')}: {count(line.freeable)} - - )} - {line.waiting.objects > 0 && ( - - {t('storage.reclaim.waiting')}: {count(line.waiting)} - - )} - +
      + + {t(`storage.category.${line.category}`)} + + {line.freeable.objects > 0 && ( + + {t('storage.reclaim.freeable')}: {count(line.freeable)} + + )} + {line.waiting.objects > 0 && ( + + {t('storage.reclaim.waiting')}: {count(line.waiting)} + + )} + {line.skipped && ( + {t(`storage.reclaim.skip.${line.skipped}`)} + )} +
      + {line.confirmationRequired && ( +
      +

      + {t('storage.reclaim.confirmNeeded')} +

      + +
      )}
    • ))} diff --git a/client/src/components/Admin/storage/reclaimModel.test.ts b/client/src/components/Admin/storage/reclaimModel.test.ts index 5e22f3e0f..e8c6c7874 100644 --- a/client/src/components/Admin/storage/reclaimModel.test.ts +++ b/client/src/components/Admin/storage/reclaimModel.test.ts @@ -1,4 +1,4 @@ -// FE-ADMIN-STOR-RECLAIM-001 to -005: the reclaim report as both storage panels show it. +// FE-ADMIN-STOR-RECLAIM-001 to -007: the reclaim report as both storage panels show it. import { STORAGE_RECLAIM_CATEGORIES, type StorageReclaimCategoryReport, type StorageReclaimReport } from '@trek/shared'; import { reclaimView } from './reclaimModel'; @@ -6,33 +6,38 @@ import { reclaimView } from './reclaimModel'; const none = { objects: 0, bytes: 0 }; function category(patch: Partial = {}): StorageReclaimCategoryReport { - return { objects: 0, unreferenced: none, eligible: none, deleted: none, skipped: null, ...patch }; + return { + objects: 0, + unreferenced: none, + eligible: none, + deleted: none, + confirmationRequired: false, + skipped: null, + ...patch, + }; } function report( - patch: Partial> + patch: Partial>, + mode: StorageReclaimReport['mode'] = 'report' ): StorageReclaimReport { const categories = Object.fromEntries( STORAGE_RECLAIM_CATEGORIES.map((c) => [c, category({ skipped: 'not_reclaimed' })]) ) as StorageReclaimReport['categories']; - return { - startedAt: 1_000, - finishedAt: 2_000, - mode: 'report', - stopped: null, - categories: { ...categories, ...patch }, - }; + return { startedAt: 1_000, finishedAt: 2_000, mode, stopped: null, categories: { ...categories, ...patch } }; } describe('reclaimView', () => { - it('FE-ADMIN-STOR-RECLAIM-001: no state reads as deletion off and never checked', () => { + it('FE-ADMIN-STOR-RECLAIM-001: no state reads as deletion off, not running and never checked', () => { expect(reclaimView(null)).toEqual({ deleteEnabled: false, + running: false, checkedAt: null, - stopped: false, + stopped: null, freeable: none, waiting: none, - deleted: none, + freed: none, + freedAt: null, lines: [], }); }); @@ -40,6 +45,8 @@ describe('reclaimView', () => { it('FE-ADMIN-STOR-RECLAIM-002: freeable is what is a week old, waiting what is younger, summed over categories', () => { const view = reclaimView({ deleteEnabled: true, + running: false, + lastDeleteRun: null, lastRun: report({ covers: category({ objects: 40, @@ -56,39 +63,71 @@ describe('reclaimView', () => { expect(view.lines.map((line) => line.category)).toEqual(['covers', 'places']); }); - it('FE-ADMIN-STOR-RECLAIM-003: what the run deleted is no longer freeable, and counts as freed', () => { - const view = reclaimView({ - deleteEnabled: true, - lastRun: report({ + it('FE-ADMIN-STOR-RECLAIM-003: freed comes from the last run with deletion on, which a check on demand does not replace', () => { + const deleting = report( + { files: category({ unreferenced: { objects: 2, bytes: 20 }, eligible: { objects: 2, bytes: 20 }, deleted: { objects: 2, bytes: 20 }, }), - }), + }, + 'delete' + ); + const view = reclaimView({ + deleteEnabled: true, + running: false, + lastDeleteRun: { ...deleting, finishedAt: 900 }, + lastRun: report({}), }); + expect(view.freed).toEqual({ objects: 2, bytes: 20 }); + expect(view.freedAt).toBe(900); expect(view.freeable).toEqual(none); - expect(view.deleted).toEqual({ objects: 2, bytes: 20 }); expect(view.lines).toEqual([]); }); it('FE-ADMIN-STOR-RECLAIM-004: a skipped category shows its reason and counts toward nothing, one never reclaimed shows nothing', () => { const view = reclaimView({ deleteEnabled: false, - lastRun: report({ - avatars: category({ skipped: 'too_many_unreferenced', unreferenced: { objects: 9, bytes: 9 } }), - }), + running: false, + lastDeleteRun: null, + lastRun: report({ avatars: category({ skipped: 'foreign_install', unreferenced: { objects: 9, bytes: 9 } }) }), }); expect(view.lines).toEqual([ - { category: 'avatars', freeable: none, waiting: none, skipped: 'too_many_unreferenced' }, + { category: 'avatars', freeable: none, waiting: none, confirmationRequired: false, skipped: 'foreign_install' }, ]); expect(view.waiting).toEqual(none); expect(view.lines.some((line) => line.category === 'backups')).toBe(false); }); - it('FE-ADMIN-STOR-RECLAIM-005: a check stopped by a restore says so', () => { - expect( - reclaimView({ deleteEnabled: false, lastRun: { ...report({}), stopped: 'database_reopened' } }).stopped - ).toBe(true); + it('FE-ADMIN-STOR-RECLAIM-005: a pass that stopped says why, and a category it stopped in keeps its partial figures', () => { + const view = reclaimView({ + deleteEnabled: false, + running: false, + lastDeleteRun: null, + lastRun: { + ...report({ covers: category({ skipped: 'interrupted', unreferenced: { objects: 4, bytes: 40 } }) }), + stopped: 'failed', + }, + }); + expect(view.stopped).toBe('failed'); + expect(view.waiting).toEqual({ objects: 4, bytes: 40 }); + expect(view.lines[0]).toMatchObject({ category: 'covers', skipped: 'interrupted' }); + }); + + it('FE-ADMIN-STOR-RECLAIM-006: a category waiting for a confirmation shows even with nothing old enough yet', () => { + const view = reclaimView({ + deleteEnabled: true, + running: false, + lastDeleteRun: null, + lastRun: report({ places: category({ objects: 10, confirmationRequired: true }) }), + }); + expect(view.lines).toEqual([ + { category: 'places', freeable: none, waiting: none, confirmationRequired: true, skipped: null }, + ]); + }); + + it('FE-ADMIN-STOR-RECLAIM-007: a running check reads as running', () => { + expect(reclaimView({ deleteEnabled: false, running: true, lastRun: null, lastDeleteRun: null }).running).toBe(true); }); }); diff --git a/client/src/components/Admin/storage/reclaimModel.ts b/client/src/components/Admin/storage/reclaimModel.ts index f4b564f79..02314bcff 100644 --- a/client/src/components/Admin/storage/reclaimModel.ts +++ b/client/src/components/Admin/storage/reclaimModel.ts @@ -1,4 +1,9 @@ -import { STORAGE_RECLAIM_CATEGORIES, type StorageReclaimCategoryReport, type StorageReclaimState } from '@trek/shared'; +import { + STORAGE_RECLAIM_CATEGORIES, + type StorageReclaimCategoryReport, + type StorageReclaimReport, + type StorageReclaimState, +} from '@trek/shared'; /** A number of files and their size, as the report counts them. */ export interface ReclaimCount { @@ -13,22 +18,32 @@ interface ReclaimCategoryLine { freeable: ReclaimCount; /** Unused for less than a week: these get their week first. */ waiting: ReclaimCount; - /** Why the category was left alone; null when it was checked. */ + /** + * An unusually large share of the category looks unused: nothing in it is + * deleted until an admin confirms that this is expected (a cleaned-up + * backlog, say, rather than a second installation in the same bucket). + */ + confirmationRequired: boolean; + /** Why the category was left alone, or stopped part way; null when it was checked through. */ skipped: Exclude | null; } /** The admin's view of the reclaim pass: the switch and what the last check found. */ export interface ReclaimView { deleteEnabled: boolean; + /** A check, nightly or on demand, is running now. */ + running: boolean; /** When the last check finished, ms; null before the first one. */ checkedAt: number | null; - /** The last check stopped early, because a restore swapped the database under it. */ - stopped: boolean; + /** Why the last check stopped before the end; null when it ran through. */ + stopped: StorageReclaimReport['stopped']; freeable: ReclaimCount; waiting: ReclaimCount; - /** What the last check deleted itself (only a nightly run with deletion on deletes). */ - deleted: ReclaimCount; - /** Only the categories with something to free, something waiting, or a reason they were skipped. */ + /** What the last run with deletion on removed: a check on demand only reports, so it never resets this. */ + freed: ReclaimCount; + /** When that run finished, ms; null when nothing ever ran with deletion on. */ + freedAt: number | null; + /** Only the categories with something to free, something waiting, something to confirm, or a reason they were skipped. */ lines: ReclaimCategoryLine[]; } @@ -42,40 +57,57 @@ function minus(a: ReclaimCount, b: ReclaimCount): ReclaimCount { return { objects: Math.max(0, a.objects - b.objects), bytes: Math.max(0, a.bytes - b.bytes) }; } +function freedBy(run: StorageReclaimReport | null): ReclaimCount { + if (!run) return NONE; + return STORAGE_RECLAIM_CATEGORIES.reduce( + (sum, category) => add(sum, run.categories[category]?.deleted ?? NONE), + NONE + ); +} + /** * The reclaim state as the storage panels show it, desktop and phone alike. * `null` (a server from before the reclaim pass, or a state not loaded) reads - * as deletion off and never checked. + * as deletion off, not running and never checked. */ export function reclaimView(state: StorageReclaimState | null): ReclaimView { const run = state?.lastRun ?? null; const lines: ReclaimCategoryLine[] = []; let freeable = NONE; let waiting = NONE; - let deleted = NONE; for (const category of STORAGE_RECLAIM_CATEGORIES) { const report = run?.categories[category]; if (!report || report.skipped === 'not_reclaimed') continue; - // A skipped category frees nothing, whatever it counted: only its reason is shown. - if (report.skipped !== null) { - lines.push({ category, freeable: NONE, waiting: NONE, skipped: report.skipped }); - continue; - } // What was deleted on this run no longer waits to be freed. const due = minus(report.eligible, report.deleted); const young = minus(report.unreferenced, report.eligible); - freeable = add(freeable, due); - waiting = add(waiting, young); - deleted = add(deleted, report.deleted); - if (due.objects > 0 || young.objects > 0) lines.push({ category, freeable: due, waiting: young, skipped: null }); + // A category the pass stopped in, or that waits for a confirmation, still + // counts what it found; one it never judged counts nothing. + const judged = report.skipped === null || report.skipped === 'interrupted' || report.skipped === 'delete_failed'; + if (judged) { + freeable = add(freeable, due); + waiting = add(waiting, young); + } + const counts = judged ? { freeable: due, waiting: young } : { freeable: NONE, waiting: NONE }; + if ( + counts.freeable.objects > 0 || + counts.waiting.objects > 0 || + report.confirmationRequired || + report.skipped !== null + ) { + lines.push({ category, ...counts, confirmationRequired: report.confirmationRequired, skipped: report.skipped }); + } } + const deleteRun = state?.lastDeleteRun ?? null; return { deleteEnabled: state?.deleteEnabled ?? false, + running: state?.running ?? false, checkedAt: run?.finishedAt ?? null, - stopped: run?.stopped != null, + stopped: run?.stopped ?? null, freeable, waiting, - deleted, + freed: freedBy(deleteRun), + freedAt: deleteRun?.finishedAt ?? null, lines, }; } diff --git a/client/src/components/Admin/storage/useStorageAdmin.ts b/client/src/components/Admin/storage/useStorageAdmin.ts index 3f8999b2c..26033fe36 100644 --- a/client/src/components/Admin/storage/useStorageAdmin.ts +++ b/client/src/components/Admin/storage/useStorageAdmin.ts @@ -1,5 +1,12 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import type { StorageAdminState, StorageBackend, StorageConfig, StorageConfigPut, StorageTestResponse } from '@trek/shared' +import type { + StorageAdminState, + StorageBackend, + StorageConfig, + StorageConfigPut, + StorageReclaimConfirmRequest, + StorageTestResponse, +} from '@trek/shared' import { adminApi } from '../../../api/client' import { storageReclaimApi } from '../../../api/storageReclaim' import { getApiErrorMessage, type ApiError } from '../../../types' @@ -7,6 +14,16 @@ import { settingsDocumentOf } from './storageModel' export type StorageTestResults = Record +/** A refused reclaim call: the server's code when it sent one the panel can name, its message otherwise. */ +export type ReclaimFailure = { code: string } | { message: string } + +function reclaimFailureOf(err: unknown, fallback: string): ReclaimFailure { + const code = (err as ApiError | null)?.response?.data as { code?: unknown } | undefined + return typeof code?.code === 'string' && code.code.startsWith('reclaim_') + ? { code: code.code } + : { message: getApiErrorMessage(err, fallback) } +} + /** 50ms under vitest so poll tests run on real timers (MSW + fake timers don't mix). */ export const BACKFILL_POLL_MS = import.meta.env.MODE === 'test' ? 50 : 5000 @@ -60,9 +77,11 @@ export interface StorageAdmin { cancelMigration: (category: string) => Promise refreshStats: () => Promise /** Switches the nightly deletion of unreferenced files on or off; null on success. */ - setReclaimDelete: (enabled: boolean) => Promise - /** Runs a check now that only reports; null on success. */ - checkReclaim: () => Promise + setReclaimDelete: (enabled: boolean) => Promise + /** Starts a check that only reports, in the background; the state polls until it is done. Null on success. */ + checkReclaim: () => Promise + /** Lets the nightly run delete in a category where an unusually large share looks unused. Null on success. */ + confirmReclaim: (category: StorageReclaimConfirmRequest['category']) => Promise /** Re-GETs admin state (poll-safe — see the implementation comment below). Exposed for the panel's failed-queue recovery path. */ refreshState: () => Promise /** @@ -276,7 +295,8 @@ export function useStorageAdmin(genericError: string, conflictError: string): St useEffect(() => { if ( !state?.backfills.some((b) => b.status === 'running') && - !state?.migrations.some((m) => m.status === 'running') + !state?.migrations.some((m) => m.status === 'running') && + !state?.reclaim?.running ) return const iv = setInterval(() => { @@ -361,33 +381,60 @@ export function useStorageAdmin(genericError: string, conflictError: string): St } }, [genericError]) - /** Writes the reclaim part of the state the way refreshStats writes usage. */ + /** + * Writes the reclaim part of the state. It counts as a deliberate state + * application, so a poll GET that was in flight before it is dropped instead + * of putting the switch back where it was. + */ const setReclaim = useCallback((reclaim: NonNullable) => { const prev = stateRef.current if (!prev) return + stateSeq.current += 1 const next = { ...prev, reclaim } stateRef.current = next setState(next) }, []) - const setReclaimDelete = useCallback(async (enabled: boolean): Promise => { + const setReclaimDelete = useCallback( + async (enabled: boolean): Promise => { + try { + setReclaim(await storageReclaimApi.setDelete(enabled)) + return null + } catch (err: unknown) { + return reclaimFailureOf(err, genericError) + } + }, + [genericError, setReclaim], + ) + + const checkReclaim = useCallback(async (): Promise => { try { - setReclaim(await storageReclaimApi.setDelete(enabled)) + await storageReclaimApi.check() + // Running from here on: the poll above picks the report up once the pass is done. + const current = stateRef.current?.reclaim + setReclaim({ + deleteEnabled: current?.deleteEnabled ?? false, + running: true, + lastRun: current?.lastRun ?? null, + lastDeleteRun: current?.lastDeleteRun ?? null, + }) return null } catch (err: unknown) { - return getApiErrorMessage(err, genericError) + return reclaimFailureOf(err, genericError) } }, [genericError, setReclaim]) - const checkReclaim = useCallback(async (): Promise => { - try { - const lastRun = await storageReclaimApi.check() - setReclaim({ deleteEnabled: stateRef.current?.reclaim?.deleteEnabled ?? false, lastRun }) - return null - } catch (err: unknown) { - return getApiErrorMessage(err, genericError) - } - }, [genericError, setReclaim]) + const confirmReclaim = useCallback( + async (category: StorageReclaimConfirmRequest['category']): Promise => { + try { + setReclaim(await storageReclaimApi.confirm(category)) + return null + } catch (err: unknown) { + return reclaimFailureOf(err, genericError) + } + }, + [genericError, setReclaim], + ) return { state, @@ -411,6 +458,7 @@ export function useStorageAdmin(genericError: string, conflictError: string): St refreshStats, setReclaimDelete, checkReclaim, + confirmReclaim, refreshState, discardDraft, } diff --git a/client/src/components/Admin/storage/useStoragePanel.test.ts b/client/src/components/Admin/storage/useStoragePanel.test.ts index 012a03d69..6594befbb 100644 --- a/client/src/components/Admin/storage/useStoragePanel.test.ts +++ b/client/src/components/Admin/storage/useStoragePanel.test.ts @@ -1,4 +1,4 @@ -// FE-ADMIN-STOR-PANEL-001 to -019: the storage panel logic behind both admin shells. +// FE-ADMIN-STOR-PANEL-001 to -020: the storage panel logic behind both admin shells. import { act, renderHook, waitFor } from '@testing-library/react'; import { STORAGE_CATEGORIES, type StorageAdminState, type StorageBackend, type StorageConfig } from '@trek/shared'; @@ -60,6 +60,7 @@ function makeAdmin(state: StorageAdminState | null, draft?: StorageConfig): Stor refreshStats: vi.fn(async () => null), setReclaimDelete: vi.fn(async () => null), checkReclaim: vi.fn(async () => null), + confirmReclaim: vi.fn(async () => null), refreshState: vi.fn(async () => {}), discardDraft: vi.fn(async () => {}), }; @@ -289,20 +290,38 @@ describe('migrationPromptLine', () => { describe('useStoragePanel: unreferenced files', () => { it('FE-ADMIN-STOR-PANEL-018: the reclaim switch and the check go through the admin hook, a failure as a toast', async () => { fake.admin = makeAdmin(baseState()); - vi.mocked(fake.admin.checkReclaim).mockResolvedValueOnce('busy'); + vi.mocked(fake.admin.checkReclaim).mockResolvedValueOnce({ code: 'reclaim_busy' }); + vi.mocked(fake.admin.setReclaimDelete).mockResolvedValueOnce({ message: 'disk full' }); const { result } = renderHook(() => useStoragePanel()); await act(async () => { await result.current.handleReclaimDelete(true); }); expect(fake.admin.setReclaimDelete).toHaveBeenCalledWith(true); + // A failure without a code the panel knows shows the server's message. + expect(toast.error).toHaveBeenCalledWith('disk full'); + expect(result.current.reclaimSaving).toBe(false); await act(async () => { await result.current.handleReclaimCheck(); }); expect(fake.admin.checkReclaim).toHaveBeenCalledTimes(1); - expect(toast.error).toHaveBeenCalledWith('busy'); + // A code the panel knows is shown in the operator's language. + expect(toast.error).toHaveBeenCalledWith('storage.reclaim.error.reclaim_busy'); expect(result.current.reclaimChecking).toBe(false); }); + it('FE-ADMIN-STOR-PANEL-020: confirming a category goes through the admin hook and says so; a running check keeps the button busy', async () => { + const state = baseState(); + state.reclaim = { deleteEnabled: true, running: true, lastRun: null, lastDeleteRun: null }; + fake.admin = makeAdmin(state); + const { result } = renderHook(() => useStoragePanel()); + expect(result.current.reclaimChecking).toBe(true); + await act(async () => { + await result.current.handleReclaimConfirm('places'); + }); + expect(fake.admin.confirmReclaim).toHaveBeenCalledWith('places'); + expect(toast.success).toHaveBeenCalledWith('storage.reclaim.confirmed'); + }); + it('FE-ADMIN-STOR-PANEL-019: a server without the reclaim state reads as deletion off and never checked', () => { fake.admin = makeAdmin(baseState()); const { result } = renderHook(() => useStoragePanel()); diff --git a/client/src/components/Admin/storage/useStoragePanel.ts b/client/src/components/Admin/storage/useStoragePanel.ts index 327938e9a..e949a4858 100644 --- a/client/src/components/Admin/storage/useStoragePanel.ts +++ b/client/src/components/Admin/storage/useStoragePanel.ts @@ -7,6 +7,7 @@ import type { StorageConfig, StorageConfigPut, StorageMigrationStatus, + StorageReclaimConfirmRequest, } from '@trek/shared'; import { useTranslation } from '../../../i18n'; @@ -32,7 +33,7 @@ import { type FoldedBackendRow, type MigrationCandidate, } from './storageModel'; -import { useStorageAdmin } from './useStorageAdmin'; +import { useStorageAdmin, type ReclaimFailure } from './useStorageAdmin'; /** Display-name mapper for joined category lists: the raw id renders only in the badge. */ export const categoryNames = (t: (key: string) => string, ids: readonly string[]): string => @@ -166,18 +167,30 @@ export function useStoragePanel() { if (error) toast.error(error); }; - const [reclaimChecking, setReclaimChecking] = useState(false); + // The click itself, until the server answered; the pass that follows shows through `reclaim.running`. + const [reclaimStarting, setReclaimStarting] = useState(false); + const [reclaimSaving, setReclaimSaving] = useState(false); + const reclaimError = (failure: ReclaimFailure) => + 'code' in failure ? t(`storage.reclaim.error.${failure.code}`) : failure.message; const handleReclaimCheck = async () => { - setReclaimChecking(true); - const error = await admin.checkReclaim(); - setReclaimChecking(false); - if (error) toast.error(error); + setReclaimStarting(true); + const failure = await admin.checkReclaim(); + setReclaimStarting(false); + if (failure) toast.error(reclaimError(failure)); }; const handleReclaimDelete = async (enabled: boolean) => { - const error = await admin.setReclaimDelete(enabled); - if (error) toast.error(error); + setReclaimSaving(true); + const failure = await admin.setReclaimDelete(enabled); + setReclaimSaving(false); + if (failure) toast.error(reclaimError(failure)); + }; + const handleReclaimConfirm = async (category: StorageReclaimConfirmRequest['category']) => { + const failure = await admin.confirmReclaim(category); + if (failure) toast.error(reclaimError(failure)); + else toast.success(t('storage.reclaim.confirmed')); }; const reclaim = reclaimView(admin.state?.reclaim ?? null); + const reclaimChecking = reclaimStarting || reclaim.running; const handleStartBackfill = async (row: FoldedBackendRow) => { if (!row.mirrorName) return; @@ -387,8 +400,10 @@ export function useStoragePanel() { handleRefreshStats, reclaim, reclaimChecking, + reclaimSaving, handleReclaimCheck, handleReclaimDelete, + handleReclaimConfirm, handleStartBackfill, handleCancelBackfill, loaded, diff --git a/client/src/mobile/screens/admin/MAdminStoragePanel.tsx b/client/src/mobile/screens/admin/MAdminStoragePanel.tsx index 01974cea5..38580b799 100644 --- a/client/src/mobile/screens/admin/MAdminStoragePanel.tsx +++ b/client/src/mobile/screens/admin/MAdminStoragePanel.tsx @@ -173,7 +173,7 @@ export default function MAdminStoragePanel(): React.ReactElement { const { admin, editing, setEditing, confirmRemove, setConfirmRemove, syncPrompt, setSyncPrompt, migratePrompt, handleCancelMigration, handleRefreshStats, handleStartBackfill, handleCancelBackfill, loaded, - reclaim, reclaimChecking, handleReclaimCheck, handleReclaimDelete, + reclaim, reclaimChecking, reclaimSaving, handleReclaimCheck, handleReclaimDelete, handleReclaimConfirm, } = useStoragePanel() const [categoryPicker, setCategoryPicker] = useState(null) @@ -570,6 +570,8 @@ export default function MAdminStoragePanel(): React.ReactElement { void handleReclaimConfirm(category)} onCheck={() => void handleReclaimCheck()} onDelete={(enabled) => void handleReclaimDelete(enabled)} /> diff --git a/client/src/mobile/screens/admin/MStorageReclaimCard.tsx b/client/src/mobile/screens/admin/MStorageReclaimCard.tsx index a6a596439..ab9eca9c0 100644 --- a/client/src/mobile/screens/admin/MStorageReclaimCard.tsx +++ b/client/src/mobile/screens/admin/MStorageReclaimCard.tsx @@ -9,13 +9,24 @@ import { MAdminButton, MAdminCard, MAdminCardHead, MAdminRow } from './MAdminUi' interface Props { reclaim: ReclaimView; + /** A check was asked for or is running. */ checking: boolean; + /** The switch is on its way to the server. */ + saving: boolean; onCheck: () => void; onDelete: (enabled: boolean) => void; + onConfirm: (category: ReclaimView['lines'][number]['category']) => void; } /** Unreferenced files on the phone's storage panel, off the view model StorageReclaimSection shows on desktop. */ -export default function MStorageReclaimCard({ reclaim, checking, onCheck, onDelete }: Props): React.ReactElement { +export default function MStorageReclaimCard({ + reclaim, + checking, + saving, + onCheck, + onDelete, + onConfirm, +}: Props): React.ReactElement { const { t, locale } = useTranslation(); const count = (c: ReclaimCount) => t('storage.usage.line', { count: c.objects, size: formatBytes(c.bytes) }); const checked = reclaim.checkedAt !== null; @@ -27,7 +38,7 @@ export default function MStorageReclaimCard({ reclaim, checking, onCheck, onDele hint={t('storage.reclaim.hint')} trailing={ - {t('storage.reclaim.check')} + {checking ? t('storage.reclaim.running') : t('storage.reclaim.check')} } /> @@ -36,7 +47,12 @@ export default function MStorageReclaimCard({ reclaim, checking, onCheck, onDele title={t('storage.reclaim.delete')} hint={t('storage.reclaim.deleteHint')} trailing={ - + } />
      @@ -47,17 +63,17 @@ export default function MStorageReclaimCard({ reclaim, checking, onCheck, onDele

      {reclaim.stopped && (

      - {t('storage.reclaim.stopped')} + {t(reclaim.stopped === 'failed' ? 'storage.reclaim.stoppedFailed' : 'storage.reclaim.stopped')}

      )} {checked && (
      -
      +
      )} - {checked && reclaim.lines.length === 0 && ( + {checked && !reclaim.stopped && reclaim.lines.length === 0 && (

      {t('storage.reclaim.nothing')}

      @@ -69,23 +85,30 @@ export default function MStorageReclaimCard({ reclaim, checking, onCheck, onDele

      {t(`storage.category.${line.category}`)}

      - {line.skipped ? ( + {line.skipped && (

      {t(`storage.reclaim.skip.${line.skipped}`)}

      - ) : ( - <> - {line.freeable.objects > 0 && ( -

      - {t('storage.reclaim.freeable')}: {count(line.freeable)} -

      - )} - {line.waiting.objects > 0 && ( -

      - {t('storage.reclaim.waiting')}: {count(line.waiting)} -

      - )} - + )} + {line.freeable.objects > 0 && ( +

      + {t('storage.reclaim.freeable')}: {count(line.freeable)} +

      + )} + {line.waiting.objects > 0 && ( +

      + {t('storage.reclaim.waiting')}: {count(line.waiting)} +

      + )} + {line.confirmationRequired && ( +
      +

      + {t('storage.reclaim.confirmNeeded')} +

      + onConfirm(line.category)}> + {t('storage.reclaim.confirm')} + +
      )}
      ))} diff --git a/client/tests/unit/mobile/admin/MAdminStoragePanel.test.tsx b/client/tests/unit/mobile/admin/MAdminStoragePanel.test.tsx index 7025566e5..a4587eae0 100644 --- a/client/tests/unit/mobile/admin/MAdminStoragePanel.test.tsx +++ b/client/tests/unit/mobile/admin/MAdminStoragePanel.test.tsx @@ -9,9 +9,10 @@ import MAdminStoragePanel from '../../../../src/mobile/screens/admin/MAdminStora /** A last check with three covers a week old and two younger ones; every other category never reclaimed. */ function reclaimReport(): StorageReclaimReport { const categories = Object.fromEntries( - STORAGE_RECLAIM_CATEGORIES.map((c) => [c, { objects: 0, unreferenced: { objects: 0, bytes: 0 }, eligible: { objects: 0, bytes: 0 }, deleted: { objects: 0, bytes: 0 }, skipped: 'not_reclaimed' as const }]), + STORAGE_RECLAIM_CATEGORIES.map((c) => [c, { objects: 0, unreferenced: { objects: 0, bytes: 0 }, eligible: { objects: 0, bytes: 0 }, deleted: { objects: 0, bytes: 0 }, confirmationRequired: false, skipped: 'not_reclaimed' as const }]), ) as StorageReclaimReport['categories']; - categories.covers = { objects: 40, unreferenced: { objects: 5, bytes: 5000 }, eligible: { objects: 3, bytes: 3000 }, deleted: { objects: 0, bytes: 0 }, skipped: null }; + categories.covers = { objects: 40, unreferenced: { objects: 5, bytes: 5000 }, eligible: { objects: 3, bytes: 3000 }, deleted: { objects: 0, bytes: 0 }, confirmationRequired: false, skipped: null }; + categories.places = { objects: 10, unreferenced: { objects: 3, bytes: 300 }, eligible: { objects: 0, bytes: 0 }, deleted: { objects: 0, bytes: 0 }, confirmationRequired: true, skipped: null }; return { startedAt: Date.now() - 60_000, finishedAt: Date.now() - 30_000, mode: 'report', stopped: null, categories }; } @@ -474,27 +475,54 @@ describe('MAdminStoragePanel', () => { expect(posted.find((b) => b.name === 'off-box')?.options.endpoint).toBe('http://edited.example:9000'); }); - it('FE-MOB-MSTOR-019: unreferenced files: the last check shows, the switch and the check go to the server', async () => { + it('FE-MOB-MSTOR-019: unreferenced files: the last check shows, the switch, the check and a confirmation go to the server', async () => { const lastRun = reclaimReport(); let put: unknown = null; - let run: unknown = null; + let runs = 0; + let confirm: unknown = null; server.use( http.put('/api/admin/storage/reclaim', async ({ request }) => { put = await request.json(); - return HttpResponse.json({ deleteEnabled: true, lastRun }); + return HttpResponse.json({ deleteEnabled: true, running: false, lastRun, lastDeleteRun: null }); }), - http.post('/api/admin/storage/reclaim/run', async ({ request }) => { - run = await request.json(); - return HttpResponse.json(lastRun); + http.post('/api/admin/storage/reclaim/run', () => { + runs += 1; + return HttpResponse.json({ started: true }, { status: 202 }); + }), + http.post('/api/admin/storage/reclaim/confirm', async ({ request }) => { + confirm = await request.json(); + return HttpResponse.json({ deleteEnabled: true, running: false, lastRun, lastDeleteRun: null }); }), ); - await renderPanel(baseState({ reclaim: { deleteEnabled: false, lastRun } })); + await renderPanel(baseState({ reclaim: { deleteEnabled: false, running: false, lastRun, lastDeleteRun: null } })); + // Once a check started, the state the panel polls says it runs. + server.use( + http.get('/api/admin/storage', () => + HttpResponse.json(baseState({ reclaim: { deleteEnabled: true, running: runs > 0, lastRun, lastDeleteRun: null } })), + ), + ); expect(screen.getByText('Unreferenced files')).toBeInTheDocument(); expect(within(screen.getByTestId('m-storage-reclaim-covers')).getByText(/Can be freed/)).toBeInTheDocument(); + expect(within(screen.getByTestId('m-storage-reclaim-places')).getByText(/More than a fifth/)).toBeInTheDocument(); fireEvent.click(screen.getByRole('switch', { name: 'Delete unreferenced files' })); await waitFor(() => expect(put).toEqual({ deleteEnabled: true })); await waitFor(() => expect(screen.getByRole('switch', { name: 'Delete unreferenced files' })).toHaveAttribute('aria-checked', 'true')); + fireEvent.click(within(screen.getByTestId('m-storage-reclaim-places')).getByRole('button', { name: 'Confirm' })); + await waitFor(() => expect(confirm).toEqual({ category: 'places' })); fireEvent.click(screen.getByRole('button', { name: 'Check now' })); - await waitFor(() => expect(run).toEqual({ reportOnly: true })); + await waitFor(() => expect(runs).toBe(1)); + // The check runs in the background: the panel shows it running until the state says otherwise. + await waitFor(() => expect(screen.getByText('Checking…')).toBeInTheDocument()); + }); + + it('FE-MOB-MSTOR-019b: a refused check names the reason in the operator\'s language', async () => { + server.use( + http.post('/api/admin/storage/reclaim/run', () => + HttpResponse.json({ error: 'busy', code: 'reclaim_busy' }, { status: 409 }), + ), + ); + await renderPanel(baseState({ reclaim: { deleteEnabled: false, running: false, lastRun: null, lastDeleteRun: null } })); + fireEvent.click(screen.getByRole('button', { name: 'Check now' })); + await waitFor(() => expect(screen.getByText('Another storage task is running. Try again in a moment.')).toBeInTheDocument()); }); }); diff --git a/shared/src/i18n/ar/storage.ts b/shared/src/i18n/ar/storage.ts index 8c89fad23..e0e4f6449 100644 --- a/shared/src/i18n/ar/storage.ts +++ b/shared/src/i18n/ar/storage.ts @@ -152,21 +152,32 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'يبحث TREK كل ليلة عن الملفات المرفوعة التي لم تعد أي رحلة أو مكان أو يومية أو مجموعة أو مستخدم يستخدمها، مثلاً بعد حذف رحلة.', 'storage.reclaim.check': 'افحص الآن', - 'storage.reclaim.checked': 'تم الفحص {age}', + 'storage.reclaim.checked': 'آخر فحص: {age}', 'storage.reclaim.never': 'لم يتم الفحص بعد', 'storage.reclaim.delete': 'حذف الملفات غير المرتبطة', 'storage.reclaim.deleteHint': - 'عند الإيقاف، يكتفي الفحص الليلي بالإبلاغ. عند التفعيل، يُحذف أي ملف لم يُستخدم منذ أسبوع. أبقِه متوقفاً إذا كانت عدة تثبيتات تشترك في حاوية (Bucket) تخزين واحدة.', + 'عند الإيقاف، يكتفي الفحص الليلي بالإبلاغ. عند التفعيل، يُحذف أي ملف لم يُستخدم منذ أسبوع. الملفات التي تحذفها في TREK تُزال فورًا في الحالتين.', 'storage.reclaim.freeable': 'يمكن تحرير مساحته', 'storage.reclaim.waiting': 'غير مستخدمة منذ أقل من أسبوع', - 'storage.reclaim.deleted': 'تم تحريرها في آخر فحص', + 'storage.reclaim.deleted': 'تم تحريرها في آخر تنظيف ليلي', 'storage.reclaim.nothing': 'لم يتم العثور على ملفات غير مرتبطة.', 'storage.reclaim.stopped': 'توقف الفحص الأخير مبكراً لأن قاعدة البيانات استُعيدت أثناء تشغيله.', 'storage.reclaim.skip.sources_missing': 'تم التخطي: تعذّر الاستعلام من كل أجزاء TREK', 'storage.reclaim.skip.shared_location': 'تم التخطي: يشترك في موقعه مع فئة أخرى', - 'storage.reclaim.skip.too_many_unreferenced': - 'تم التخطي: يبدو أن عدداً كبيراً من الملفات غير مستخدم، ربما بسبب حاوية (Bucket) مشتركة', 'storage.reclaim.skip.list_failed': 'تم التخطي: تعذّر سرد الملفات', 'storage.reclaim.skip.source_failed': 'تم التخطي: فشل أحد الاستعلامات', + 'storage.reclaim.running': 'جارٍ الفحص…', + 'storage.reclaim.stoppedFailed': 'توقف الفحص الأخير بسبب خطأ. سيعمل مجددًا الليلة.', + 'storage.reclaim.skip.foreign_install': 'تم التخطي: تثبيت TREK آخر يستخدم هذا التخزين', + 'storage.reclaim.skip.owner_unknown': 'تم التخطي: تعذّر على TREK وسم هذا التخزين على أنه خاص به', + 'storage.reclaim.skip.delete_failed': 'توقف: رفض التخزين عملية حذف', + 'storage.reclaim.skip.interrupted': 'توقف قبل الاكتمال، الأرقام جزئية', + 'storage.reclaim.confirmNeeded': + 'يبدو أن أكثر من خُمس هذه الملفات غير مستخدم. لن يُحذف شيء هنا حتى تؤكد أن هذا متوقع.', + 'storage.reclaim.confirm': 'تأكيد', + 'storage.reclaim.confirmed': 'تم التأكيد. قد يحذف التنظيف الليلي هذه الملفات الآن.', + 'storage.reclaim.error.reclaim_busy': 'مهمة تخزين أخرى قيد التشغيل. حاول مجددًا بعد قليل.', + 'storage.reclaim.error.reclaim_demo': 'غير متاح في نسخة العرض التجريبي.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'لا شيء لتأكيده بعد الآن. شغّل فحصًا أولًا.', }; export default storage; diff --git a/shared/src/i18n/az/storage.ts b/shared/src/i18n/az/storage.ts index ade4d1aa2..602a89a77 100644 --- a/shared/src/i18n/az/storage.ts +++ b/shared/src/i18n/az/storage.ts @@ -153,22 +153,33 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'TREK hər gecə heç bir səfər, yer, jurnal, kolleksiya və ya istifadəçinin artıq istifadə etmədiyi yüklənmiş faylları axtarır, məsələn, səfər silindikdən sonra.', 'storage.reclaim.check': 'İndi yoxla', - 'storage.reclaim.checked': 'Yoxlanıldı: {age}', + 'storage.reclaim.checked': 'Son yoxlama: {age}', 'storage.reclaim.never': 'Hələ yoxlanılmayıb', 'storage.reclaim.delete': 'İstinad olunmayan faylları sil', 'storage.reclaim.deleteHint': - 'Söndürülü olduqda gecə yoxlaması yalnız hesabat verir. Aktiv olduqda bir həftədir heç nəyin istifadə etmədiyi fayl silinir. Bir neçə quraşdırma eyni bucket-i paylaşırsa, söndürülü saxlayın.', + 'Söndürülü olduqda gecə yoxlaması yalnız hesabat verir. Aktiv olduqda bir həftədir heç nəyin istifadə etmədiyi fayl silinir. TREK-də sildiyiniz fayllar hər iki halda dərhal silinir.', 'storage.reclaim.freeable': 'Boşaldıla bilər', 'storage.reclaim.waiting': 'Bir həftədən az istifadə olunmayıb', - 'storage.reclaim.deleted': 'Son yoxlamada boşaldıldı', + 'storage.reclaim.deleted': 'Son gecə təmizliyi ilə boşaldıldı', 'storage.reclaim.nothing': 'İstinad olunmayan fayl tapılmadı.', 'storage.reclaim.stopped': 'Son yoxlama vaxtından əvvəl dayandı, çünki o işləyərkən verilənlər bazası bərpa olundu.', 'storage.reclaim.skip.sources_missing': 'Keçildi: TREK-in bütün hissələrinə sorğu göndərmək mümkün olmadı', 'storage.reclaim.skip.shared_location': 'Keçildi: yeri başqa kateqoriya ilə paylaşılır', - 'storage.reclaim.skip.too_many_unreferenced': - 'Keçildi: çox fayl istifadə olunmayan görünür, bəlkə paylaşılan bucket-dir', 'storage.reclaim.skip.list_failed': 'Keçildi: faylların siyahısını almaq mümkün olmadı', 'storage.reclaim.skip.source_failed': 'Keçildi: bir sorğu uğursuz oldu', + 'storage.reclaim.running': 'Yoxlanılır…', + 'storage.reclaim.stoppedFailed': 'Son yoxlama xəta ilə dayandı. Bu gecə yenidən işləyəcək.', + 'storage.reclaim.skip.foreign_install': 'Keçildi: başqa TREK quraşdırması bu saxlama yerindən istifadə edir', + 'storage.reclaim.skip.owner_unknown': 'Keçildi: TREK bu saxlama yerini özününkü kimi işarələyə bilmədi', + 'storage.reclaim.skip.delete_failed': 'Dayandırıldı: saxlama yeri silməni rədd etdi', + 'storage.reclaim.skip.interrupted': 'Bitməmiş dayandırıldı, rəqəmlər qismən məlumatdır', + 'storage.reclaim.confirmNeeded': + 'Bu faylların beşdə birindən çoxu istifadə olunmayan görünür. Bunun gözlənilən olduğunu təsdiqləyənə qədər burada heç nə silinməyəcək.', + 'storage.reclaim.confirm': 'Təsdiqlə', + 'storage.reclaim.confirmed': 'Təsdiqləndi. Gecə təmizliyi indi bu faylları silə bilər.', + 'storage.reclaim.error.reclaim_busy': 'Başqa saxlama tapşırığı işləyir. Bir azdan yenidən cəhd edin.', + 'storage.reclaim.error.reclaim_demo': 'Demo quraşdırmada əlçatan deyil.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Artıq təsdiqlənəcək bir şey yoxdur. Əvvəlcə yoxlama aparın.', }; export default storage; diff --git a/shared/src/i18n/br/storage.ts b/shared/src/i18n/br/storage.ts index 4a397843c..5879f2d02 100644 --- a/shared/src/i18n/br/storage.ts +++ b/shared/src/i18n/br/storage.ts @@ -142,22 +142,33 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'Toda noite o TREK procura arquivos enviados que nenhuma viagem, lugar, diário, coleção ou usuário usa mais, por exemplo depois que uma viagem foi excluída.', 'storage.reclaim.check': 'Verificar agora', - 'storage.reclaim.checked': 'Verificado {age}', + 'storage.reclaim.checked': 'Última verificação: {age}', 'storage.reclaim.never': 'Ainda não verificado', 'storage.reclaim.delete': 'Excluir arquivos sem referência', 'storage.reclaim.deleteHint': - 'Desativado, a verificação noturna apenas informa. Ativado, um arquivo que ninguém usa há uma semana é excluído. Deixe desativado se várias instalações compartilham o mesmo bucket de armazenamento.', + 'Desativado, a verificação noturna apenas informa. Ativado, um arquivo que ninguém usa há uma semana é excluído. Arquivos que você exclui no TREK são removidos na hora, de qualquer forma.', 'storage.reclaim.freeable': 'Pode ser liberado', 'storage.reclaim.waiting': 'Sem uso há menos de uma semana', - 'storage.reclaim.deleted': 'Liberado na última verificação', + 'storage.reclaim.deleted': 'Liberado pela última limpeza noturna', 'storage.reclaim.nothing': 'Nenhum arquivo sem referência encontrado.', 'storage.reclaim.stopped': 'A última verificação parou antes do fim porque o banco de dados foi restaurado durante a execução.', 'storage.reclaim.skip.sources_missing': 'Ignorado: nem todas as partes do TREK puderam ser consultadas', 'storage.reclaim.skip.shared_location': 'Ignorado: compartilha o local com outra categoria', - 'storage.reclaim.skip.too_many_unreferenced': - 'Ignorado: muitos arquivos parecem sem uso, possivelmente um bucket compartilhado', 'storage.reclaim.skip.list_failed': 'Ignorado: não foi possível listar os arquivos', 'storage.reclaim.skip.source_failed': 'Ignorado: uma consulta falhou', + 'storage.reclaim.running': 'Verificando…', + 'storage.reclaim.stoppedFailed': 'A última verificação parou após um erro. Ela roda de novo esta noite.', + 'storage.reclaim.skip.foreign_install': 'Ignorado: outra instalação do TREK usa este armazenamento', + 'storage.reclaim.skip.owner_unknown': 'Ignorado: o TREK não conseguiu marcar este armazenamento como seu', + 'storage.reclaim.skip.delete_failed': 'Interrompido: o armazenamento recusou uma exclusão', + 'storage.reclaim.skip.interrupted': 'Interrompido antes de terminar, os números são parciais', + 'storage.reclaim.confirmNeeded': + 'Mais de um quinto destes arquivos parece sem uso. Nada aqui será excluído até você confirmar que isso é esperado.', + 'storage.reclaim.confirm': 'Confirmar', + 'storage.reclaim.confirmed': 'Confirmado. A limpeza noturna agora pode excluir estes arquivos.', + 'storage.reclaim.error.reclaim_busy': 'Outra tarefa de armazenamento está em execução. Tente novamente em instantes.', + 'storage.reclaim.error.reclaim_demo': 'Indisponível em uma instância de demonstração.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Não há mais nada a confirmar. Execute uma verificação primeiro.', }; export default storage; diff --git a/shared/src/i18n/ca/storage.ts b/shared/src/i18n/ca/storage.ts index fbe6d0c4f..c89bca065 100644 --- a/shared/src/i18n/ca/storage.ts +++ b/shared/src/i18n/ca/storage.ts @@ -146,21 +146,34 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': "Cada nit TREK cerca fitxers pujats que cap viatge, lloc, diari, col·lecció o usuari fa servir ja, per exemple després d'eliminar un viatge.", 'storage.reclaim.check': 'Comprova ara', - 'storage.reclaim.checked': 'Comprovat {age}', + 'storage.reclaim.checked': 'Última comprovació: {age}', 'storage.reclaim.never': 'Encara no comprovat', 'storage.reclaim.delete': 'Elimina els fitxers sense referència', - 'storage.reclaim.deleteHint': - "Desactivat, la comprovació nocturna només informa. Activat, s'elimina un fitxer que ningú no ha fet servir durant una setmana. Deixa-ho desactivat si diverses instal·lacions comparteixen un mateix bucket d'emmagatzematge.", 'storage.reclaim.freeable': 'Es pot alliberar', 'storage.reclaim.waiting': "Sense ús fa menys d'una setmana", - 'storage.reclaim.deleted': "Alliberat a l'última comprovació", 'storage.reclaim.nothing': "No s'ha trobat cap fitxer sense referència.", 'storage.reclaim.stopped': "L'última comprovació s'ha aturat abans d'hora perquè la base de dades es va restaurar mentre s'executava.", 'storage.reclaim.skip.sources_missing': "Omès: no s'han pogut consultar totes les parts de TREK", 'storage.reclaim.skip.shared_location': 'Omès: comparteix la ubicació amb una altra categoria', - 'storage.reclaim.skip.too_many_unreferenced': 'Omès: massa fitxers semblen sense ús, potser un bucket compartit', 'storage.reclaim.skip.list_failed': "Omès: no s'han pogut llistar els fitxers", 'storage.reclaim.skip.source_failed': 'Omès: ha fallat una consulta', + 'storage.reclaim.deleted': "Alliberat per l'última neteja nocturna", + 'storage.reclaim.deleteHint': + "Desactivat, la comprovació nocturna només informa. Activat, s'elimina un fitxer que ningú no ha fet servir durant una setmana. Els fitxers que elimines a TREK s'esborren a l'instant en tots dos casos.", + 'storage.reclaim.running': 'Comprovant…', + 'storage.reclaim.stoppedFailed': "L'última comprovació s'ha aturat per un error. Torna a executar-se aquesta nit.", + 'storage.reclaim.skip.foreign_install': 'Omès: una altra instal·lació de TREK fa servir aquest emmagatzematge', + 'storage.reclaim.skip.owner_unknown': 'Omès: TREK no ha pogut marcar aquest emmagatzematge com a propi', + 'storage.reclaim.skip.delete_failed': "Aturat: l'emmagatzematge ha rebutjat una eliminació", + 'storage.reclaim.skip.interrupted': "Aturat abans d'acabar, les xifres són parcials", + 'storage.reclaim.confirmNeeded': + "Més d'una cinquena part d'aquests fitxers semblen sense ús. No s'elimina res d'aquí fins que confirmis que és l'esperat.", + 'storage.reclaim.confirm': 'Confirma', + 'storage.reclaim.confirmed': 'Confirmat. La neteja nocturna ja pot eliminar aquests fitxers.', + 'storage.reclaim.error.reclaim_busy': + "Hi ha una altra tasca d'emmagatzematge en curs. Torna-ho a provar d'aquí a un moment.", + 'storage.reclaim.error.reclaim_demo': 'No disponible en una instància de demostració.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Ja no hi ha res per confirmar. Executa primer una comprovació.', }; export default storage; diff --git a/shared/src/i18n/cs/storage.ts b/shared/src/i18n/cs/storage.ts index 787c127b0..62735ee9c 100644 --- a/shared/src/i18n/cs/storage.ts +++ b/shared/src/i18n/cs/storage.ts @@ -143,21 +143,32 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'TREK každou noc hledá nahrané soubory, které už nepoužívá žádný výlet, místo, deník, sbírka ani uživatel, například po smazání výletu.', 'storage.reclaim.check': 'Zkontrolovat nyní', - 'storage.reclaim.checked': 'Zkontrolováno {age}', + 'storage.reclaim.checked': 'Poslední kontrola: {age}', 'storage.reclaim.never': 'Zatím nezkontrolováno', 'storage.reclaim.delete': 'Smazat nepoužívané soubory', 'storage.reclaim.deleteHint': - 'Když je vypnuto, noční kontrola jen hlásí. Když je zapnuto, soubor, který se týden nepoužil, se smaže. Pokud několik instalací sdílí jeden bucket úložiště, nechte vypnuto.', + 'Když je vypnuto, noční kontrola jen hlásí. Když je zapnuto, soubor, který se týden nepoužil, se smaže. Soubory, které smažete v TREK, se odstraní okamžitě v obou případech.', 'storage.reclaim.freeable': 'Lze uvolnit', 'storage.reclaim.waiting': 'Nepoužito méně než týden', - 'storage.reclaim.deleted': 'Uvolněno při poslední kontrole', + 'storage.reclaim.deleted': 'Uvolněno poslední noční úklidem', 'storage.reclaim.nothing': 'Nebyly nalezeny žádné nepoužívané soubory.', 'storage.reclaim.stopped': 'Poslední kontrola skončila předčasně, protože byla během jejího běhu obnovena databáze.', 'storage.reclaim.skip.sources_missing': 'Přeskočeno: nešlo se zeptat všech částí TREK', 'storage.reclaim.skip.shared_location': 'Přeskočeno: sdílí umístění s jinou kategorií', - 'storage.reclaim.skip.too_many_unreferenced': - 'Přeskočeno: příliš mnoho souborů vypadá nepoužitě, možná jde o sdílený bucket', 'storage.reclaim.skip.list_failed': 'Přeskočeno: soubory se nepodařilo vypsat', 'storage.reclaim.skip.source_failed': 'Přeskočeno: dotaz selhal', + 'storage.reclaim.running': 'Kontroluje se…', + 'storage.reclaim.stoppedFailed': 'Poslední kontrola skončila kvůli chybě. Dnes v noci proběhne znovu.', + 'storage.reclaim.skip.foreign_install': 'Přeskočeno: toto úložiště používá jiná instalace TREK', + 'storage.reclaim.skip.owner_unknown': 'Přeskočeno: TREK nemohl označit toto úložiště jako své', + 'storage.reclaim.skip.delete_failed': 'Zastaveno: úložiště odmítlo smazání', + 'storage.reclaim.skip.interrupted': 'Zastaveno před dokončením, údaje jsou neúplné', + 'storage.reclaim.confirmNeeded': + 'Více než pětina těchto souborů vypadá nepoužitě. Nic z nich se nesmaže, dokud nepotvrdíte, že je to v pořádku.', + 'storage.reclaim.confirm': 'Potvrdit', + 'storage.reclaim.confirmed': 'Potvrzeno. Noční úklid nyní může tyto soubory smazat.', + 'storage.reclaim.error.reclaim_busy': 'Běží jiná úloha úložiště. Zkuste to za chvíli znovu.', + 'storage.reclaim.error.reclaim_demo': 'Není dostupné v demo instanci.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Není už co potvrzovat. Nejdřív spusťte kontrolu.', }; export default storage; diff --git a/shared/src/i18n/de/storage.ts b/shared/src/i18n/de/storage.ts index df7173c68..2c7631685 100644 --- a/shared/src/i18n/de/storage.ts +++ b/shared/src/i18n/de/storage.ts @@ -146,22 +146,34 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'Jede Nacht sucht TREK nach hochgeladenen Dateien, die keine Reise, kein Ort, kein Reisetagebuch, keine Sammlung und kein Nutzer mehr verwendet, etwa nach dem Löschen einer Reise.', 'storage.reclaim.check': 'Jetzt prüfen', - 'storage.reclaim.checked': 'Geprüft {age}', + 'storage.reclaim.checked': 'Letzte Prüfung: {age}', 'storage.reclaim.never': 'Noch nicht geprüft', 'storage.reclaim.delete': 'Nicht mehr genutzte Dateien löschen', 'storage.reclaim.deleteHint': - 'Aus: Die nächtliche Prüfung berichtet nur. An: Eine Datei, die eine Woche lang nichts mehr verwendet, wird gelöscht. Lass es aus, wenn sich mehrere Installationen einen Speicher-Bucket teilen.', + 'Aus: Die nächtliche Prüfung berichtet nur. An: Eine Datei, die eine Woche lang nichts mehr verwendet, wird gelöscht. Was du in TREK löschst, wird so oder so sofort entfernt.', 'storage.reclaim.freeable': 'Kann freigegeben werden', 'storage.reclaim.waiting': 'Seit weniger als einer Woche ungenutzt', - 'storage.reclaim.deleted': 'Bei der letzten Prüfung freigegeben', + 'storage.reclaim.deleted': 'Bei der letzten nächtlichen Bereinigung freigegeben', 'storage.reclaim.nothing': 'Keine ungenutzten Dateien gefunden.', 'storage.reclaim.stopped': 'Die letzte Prüfung wurde vorzeitig beendet, weil währenddessen die Datenbank wiederhergestellt wurde.', 'storage.reclaim.skip.sources_missing': 'Übersprungen: Nicht jeder Teil von TREK konnte gefragt werden', 'storage.reclaim.skip.shared_location': 'Übersprungen: teilt den Speicherort mit einer anderen Kategorie', - 'storage.reclaim.skip.too_many_unreferenced': - 'Übersprungen: Zu viele Dateien wirken ungenutzt, womöglich ein geteilter Bucket', 'storage.reclaim.skip.list_failed': 'Übersprungen: Die Dateien konnten nicht aufgelistet werden', 'storage.reclaim.skip.source_failed': 'Übersprungen: Eine Abfrage ist fehlgeschlagen', + 'storage.reclaim.running': 'Wird geprüft…', + 'storage.reclaim.stoppedFailed': + 'Die letzte Prüfung wurde nach einem Fehler abgebrochen. Sie läuft heute Nacht erneut.', + 'storage.reclaim.skip.foreign_install': 'Übersprungen: Eine andere TREK-Installation nutzt diesen Speicher', + 'storage.reclaim.skip.owner_unknown': 'Übersprungen: TREK konnte diesen Speicher nicht als eigenen markieren', + 'storage.reclaim.skip.delete_failed': 'Abgebrochen: Der Speicher hat ein Löschen verweigert', + 'storage.reclaim.skip.interrupted': 'Vorzeitig beendet, die Zahlen sind unvollständig', + 'storage.reclaim.confirmNeeded': + 'Mehr als ein Fünftel dieser Dateien wirkt ungenutzt. Hier wird nichts gelöscht, bis du bestätigst, dass das so stimmt.', + 'storage.reclaim.confirm': 'Bestätigen', + 'storage.reclaim.confirmed': 'Bestätigt. Die nächtliche Bereinigung darf diese Dateien jetzt löschen.', + 'storage.reclaim.error.reclaim_busy': 'Gerade läuft eine andere Speicheraufgabe. Versuch es gleich noch einmal.', + 'storage.reclaim.error.reclaim_demo': 'Auf einer Demo-Instanz nicht verfügbar.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Es gibt nichts mehr zu bestätigen. Starte zuerst eine Prüfung.', }; export default storage; diff --git a/shared/src/i18n/en/storage.ts b/shared/src/i18n/en/storage.ts index 228ca034d..3ab2dddb3 100644 --- a/shared/src/i18n/en/storage.ts +++ b/shared/src/i18n/en/storage.ts @@ -156,20 +156,32 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'Every night TREK looks for uploaded files that no trip, place, journal, collection or user uses any more, for example after a trip was deleted.', 'storage.reclaim.check': 'Check now', - 'storage.reclaim.checked': 'Checked {age}', + 'storage.reclaim.checked': 'Last check: {age}', 'storage.reclaim.never': 'Not checked yet', 'storage.reclaim.delete': 'Delete unreferenced files', 'storage.reclaim.deleteHint': - 'Off, the nightly check only reports. On, a file nothing has used for a week is deleted. Leave it off if several installations share one storage bucket.', + 'Off, the nightly check only reports. On, a file nothing has used for a week is deleted. Files you delete in TREK are removed right away either way.', 'storage.reclaim.freeable': 'Can be freed', 'storage.reclaim.waiting': 'Unused for less than a week', - 'storage.reclaim.deleted': 'Freed on the last check', + 'storage.reclaim.deleted': 'Freed by the last nightly cleanup', 'storage.reclaim.nothing': 'No unreferenced files found.', 'storage.reclaim.stopped': 'The last check stopped early because the database was restored while it ran.', 'storage.reclaim.skip.sources_missing': 'Skipped: not every part of TREK could be asked', 'storage.reclaim.skip.shared_location': 'Skipped: shares its location with another category', - 'storage.reclaim.skip.too_many_unreferenced': 'Skipped: too many files look unused, possibly a shared bucket', 'storage.reclaim.skip.list_failed': 'Skipped: the files could not be listed', 'storage.reclaim.skip.source_failed': 'Skipped: a lookup failed', + 'storage.reclaim.running': 'Checking…', + 'storage.reclaim.stoppedFailed': 'The last check stopped after an error. It runs again tonight.', + 'storage.reclaim.skip.foreign_install': 'Skipped: another TREK installation uses this storage', + 'storage.reclaim.skip.owner_unknown': 'Skipped: TREK could not mark this storage as its own', + 'storage.reclaim.skip.delete_failed': 'Stopped: the storage refused a delete', + 'storage.reclaim.skip.interrupted': 'Stopped before it finished, the figures are partial', + 'storage.reclaim.confirmNeeded': + 'More than a fifth of these files look unused. Nothing here is deleted until you confirm that this is expected.', + 'storage.reclaim.confirm': 'Confirm', + 'storage.reclaim.confirmed': 'Confirmed. The nightly cleanup may delete these files now.', + 'storage.reclaim.error.reclaim_busy': 'Another storage task is running. Try again in a moment.', + 'storage.reclaim.error.reclaim_demo': 'Not available on a demo instance.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'There is nothing to confirm any more. Run a check first.', }; export default storage; diff --git a/shared/src/i18n/es/storage.ts b/shared/src/i18n/es/storage.ts index ca717a9f3..f4b1abdaf 100644 --- a/shared/src/i18n/es/storage.ts +++ b/shared/src/i18n/es/storage.ts @@ -146,22 +146,33 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'Cada noche TREK busca archivos subidos que ningún viaje, lugar, diario, colección o usuario usa ya, por ejemplo tras eliminar un viaje.', 'storage.reclaim.check': 'Comprobar ahora', - 'storage.reclaim.checked': 'Comprobado {age}', + 'storage.reclaim.checked': 'Última comprobación: {age}', 'storage.reclaim.never': 'Aún no comprobado', 'storage.reclaim.delete': 'Eliminar archivos sin referencia', 'storage.reclaim.deleteHint': - 'Desactivado, la comprobación nocturna solo informa. Activado, se elimina un archivo que nadie ha usado en una semana. Déjalo desactivado si varias instalaciones comparten un mismo bucket de almacenamiento.', + 'Desactivado, la comprobación nocturna solo informa. Activado, se elimina un archivo que nadie ha usado en una semana. Los archivos que eliminas en TREK se borran al instante en ambos casos.', 'storage.reclaim.freeable': 'Se puede liberar', 'storage.reclaim.waiting': 'Sin uso desde hace menos de una semana', - 'storage.reclaim.deleted': 'Liberado en la última comprobación', + 'storage.reclaim.deleted': 'Liberado por la última limpieza nocturna', 'storage.reclaim.nothing': 'No se encontraron archivos sin referencia.', 'storage.reclaim.stopped': 'La última comprobación se detuvo antes de tiempo porque la base de datos se restauró mientras se ejecutaba.', 'storage.reclaim.skip.sources_missing': 'Omitido: no se pudo consultar a todas las partes de TREK', 'storage.reclaim.skip.shared_location': 'Omitido: comparte ubicación con otra categoría', - 'storage.reclaim.skip.too_many_unreferenced': - 'Omitido: demasiados archivos parecen sin uso, posiblemente un bucket compartido', 'storage.reclaim.skip.list_failed': 'Omitido: no se pudieron listar los archivos', 'storage.reclaim.skip.source_failed': 'Omitido: falló una consulta', + 'storage.reclaim.running': 'Comprobando…', + 'storage.reclaim.stoppedFailed': 'La última comprobación se detuvo por un error. Se ejecutará de nuevo esta noche.', + 'storage.reclaim.skip.foreign_install': 'Omitido: otra instalación de TREK usa este almacenamiento', + 'storage.reclaim.skip.owner_unknown': 'Omitido: TREK no pudo marcar este almacenamiento como propio', + 'storage.reclaim.skip.delete_failed': 'Detenido: el almacenamiento rechazó una eliminación', + 'storage.reclaim.skip.interrupted': 'Detenido antes de terminar, las cifras son parciales', + 'storage.reclaim.confirmNeeded': + 'Más de una quinta parte de estos archivos parece sin uso. No se elimina nada aquí hasta que confirmes que es lo esperado.', + 'storage.reclaim.confirm': 'Confirmar', + 'storage.reclaim.confirmed': 'Confirmado. La limpieza nocturna ya puede eliminar estos archivos.', + 'storage.reclaim.error.reclaim_busy': 'Hay otra tarea de almacenamiento en curso. Inténtalo de nuevo en un momento.', + 'storage.reclaim.error.reclaim_demo': 'No disponible en una instancia de demostración.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Ya no hay nada que confirmar. Ejecuta primero una comprobación.', }; export default storage; diff --git a/shared/src/i18n/et/storage.ts b/shared/src/i18n/et/storage.ts index 38fb58819..c7c652459 100644 --- a/shared/src/i18n/et/storage.ts +++ b/shared/src/i18n/et/storage.ts @@ -160,21 +160,32 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'TREK otsib igal ööl üleslaaditud faile, mida ükski reis, koht, päevik, kogu ega kasutaja enam ei kasuta, näiteks pärast reisi kustutamist.', 'storage.reclaim.check': 'Kontrolli kohe', - 'storage.reclaim.checked': 'Kontrollitud {age}', + 'storage.reclaim.checked': 'Viimane kontroll: {age}', 'storage.reclaim.never': 'Pole veel kontrollitud', 'storage.reclaim.delete': 'Kustuta viiteta failid', 'storage.reclaim.deleteHint': - 'Väljas teavitab öine kontroll ainult. Sees kustutatakse fail, mida pole nädala jooksul keegi kasutanud. Jäta välja, kui mitu paigaldust jagavad ühte salve (bucket).', + 'Väljas teavitab öine kontroll ainult. Sees kustutatakse fail, mida pole nädala jooksul keegi kasutanud. TREKis kustutatud failid eemaldatakse mõlemal juhul kohe.', 'storage.reclaim.freeable': 'Vabastatav', 'storage.reclaim.waiting': 'Kasutamata alla nädala', - 'storage.reclaim.deleted': 'Vabastatud viimasel kontrollil', + 'storage.reclaim.deleted': 'Vabastatud viimase öise puhastusega', 'storage.reclaim.nothing': 'Viiteta faile ei leitud.', 'storage.reclaim.stopped': 'Viimane kontroll katkes ennatlikult, sest andmebaas taastati selle töö ajal.', 'storage.reclaim.skip.sources_missing': 'Vahele jäetud: kõigilt TREK-i osadelt ei saanud päringut teha', 'storage.reclaim.skip.shared_location': 'Vahele jäetud: jagab asukohta teise kategooriaga', - 'storage.reclaim.skip.too_many_unreferenced': - 'Vahele jäetud: liiga palju faile paistab kasutamata, võib-olla jagatud salv', 'storage.reclaim.skip.list_failed': 'Vahele jäetud: faile ei saanud loetleda', 'storage.reclaim.skip.source_failed': 'Vahele jäetud: päring ebaõnnestus', + 'storage.reclaim.running': 'Kontrollin…', + 'storage.reclaim.stoppedFailed': 'Viimane kontroll katkes vea tõttu. See käivitub uuesti täna öösel.', + 'storage.reclaim.skip.foreign_install': 'Vahele jäetud: seda salvestusruumi kasutab teine TREK-i paigaldus', + 'storage.reclaim.skip.owner_unknown': 'Vahele jäetud: TREK ei saanud seda salvestusruumi enda omaks märkida', + 'storage.reclaim.skip.delete_failed': 'Peatatud: salvestusruum keeldus kustutamisest', + 'storage.reclaim.skip.interrupted': 'Peatatud enne lõppu, arvud on osalised', + 'storage.reclaim.confirmNeeded': + 'Rohkem kui viiendik neist failidest paistab kasutamata. Siin ei kustutata midagi enne, kui kinnitad, et see on ootuspärane.', + 'storage.reclaim.confirm': 'Kinnita', + 'storage.reclaim.confirmed': 'Kinnitatud. Öine puhastus võib need failid nüüd kustutada.', + 'storage.reclaim.error.reclaim_busy': 'Käib teine salvestusruumi töö. Proovi hetke pärast uuesti.', + 'storage.reclaim.error.reclaim_demo': 'Demopaigaldusel pole saadaval.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Kinnitada pole enam midagi. Käivita esmalt kontroll.', }; export default storage; diff --git a/shared/src/i18n/fr/storage.ts b/shared/src/i18n/fr/storage.ts index 209468bec..709c5d9e5 100644 --- a/shared/src/i18n/fr/storage.ts +++ b/shared/src/i18n/fr/storage.ts @@ -148,22 +148,34 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': "Chaque nuit, TREK recherche les fichiers envoyés qu'aucun voyage, lieu, journal, collection ou utilisateur n'utilise plus, par exemple après la suppression d'un voyage.", 'storage.reclaim.check': 'Vérifier maintenant', - 'storage.reclaim.checked': 'Vérifié {age}', + 'storage.reclaim.checked': 'Dernière vérification : {age}', 'storage.reclaim.never': 'Pas encore vérifié', 'storage.reclaim.delete': 'Supprimer les fichiers non référencés', 'storage.reclaim.deleteHint': - 'Désactivé, la vérification nocturne se contente de signaler. Activé, un fichier inutilisé depuis une semaine est supprimé. Laisse désactivé si plusieurs installations partagent un même bucket de stockage.', + 'Désactivé, la vérification nocturne se contente de signaler. Activé, un fichier inutilisé depuis une semaine est supprimé. Les fichiers que tu supprimes dans TREK sont de toute façon supprimés immédiatement.', 'storage.reclaim.freeable': 'Peut être libéré', 'storage.reclaim.waiting': "Inutilisé depuis moins d'une semaine", - 'storage.reclaim.deleted': 'Libéré lors de la dernière vérification', + 'storage.reclaim.deleted': 'Libéré lors du dernier nettoyage nocturne', 'storage.reclaim.nothing': 'Aucun fichier non référencé trouvé.', 'storage.reclaim.stopped': "La dernière vérification s'est arrêtée prématurément, car la base de données a été restaurée pendant son exécution.", 'storage.reclaim.skip.sources_missing': "Ignoré : toutes les parties de TREK n'ont pas pu être interrogées", 'storage.reclaim.skip.shared_location': 'Ignoré : partage son emplacement avec une autre catégorie', - 'storage.reclaim.skip.too_many_unreferenced': - 'Ignoré : trop de fichiers semblent inutilisés, peut-être un bucket partagé', 'storage.reclaim.skip.list_failed': "Ignoré : la liste des fichiers n'a pas pu être obtenue", 'storage.reclaim.skip.source_failed': 'Ignoré : une recherche a échoué', + 'storage.reclaim.running': 'Vérification…', + 'storage.reclaim.stoppedFailed': + "La dernière vérification s'est arrêtée après une erreur. Elle se relance cette nuit.", + 'storage.reclaim.skip.foreign_install': 'Ignoré : une autre installation de TREK utilise ce stockage', + 'storage.reclaim.skip.owner_unknown': "Ignoré : TREK n'a pas pu marquer ce stockage comme le sien", + 'storage.reclaim.skip.delete_failed': 'Arrêté : le stockage a refusé une suppression', + 'storage.reclaim.skip.interrupted': 'Arrêté avant la fin, les chiffres sont partiels', + 'storage.reclaim.confirmNeeded': + "Plus d'un cinquième de ces fichiers semblent inutilisés. Rien n'est supprimé ici tant que tu n'as pas confirmé que c'est normal.", + 'storage.reclaim.confirm': 'Confirmer', + 'storage.reclaim.confirmed': 'Confirmé. Le nettoyage nocturne peut désormais supprimer ces fichiers.', + 'storage.reclaim.error.reclaim_busy': 'Une autre tâche de stockage est en cours. Réessaie dans un instant.', + 'storage.reclaim.error.reclaim_demo': 'Indisponible sur une instance de démonstration.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': "Il n'y a plus rien à confirmer. Lance d'abord une vérification.", }; export default storage; diff --git a/shared/src/i18n/gr/storage.ts b/shared/src/i18n/gr/storage.ts index 9c6de358c..393c3d909 100644 --- a/shared/src/i18n/gr/storage.ts +++ b/shared/src/i18n/gr/storage.ts @@ -149,22 +149,34 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'Κάθε νύχτα το TREK αναζητά ανεβασμένα αρχεία που δεν χρησιμοποιούνται πια από κανένα ταξίδι, μέρος, ημερολόγιο, συλλογή ή χρήστη, για παράδειγμα μετά τη διαγραφή ενός ταξιδιού.', 'storage.reclaim.check': 'Έλεγχος τώρα', - 'storage.reclaim.checked': 'Έλεγχος {age}', + 'storage.reclaim.checked': 'Τελευταίος έλεγχος: {age}', 'storage.reclaim.never': 'Δεν έχει ελεγχθεί ακόμα', 'storage.reclaim.delete': 'Διαγραφή αρχείων χωρίς αναφορά', 'storage.reclaim.deleteHint': - 'Όταν είναι ανενεργό, ο νυχτερινός έλεγχος μόνο αναφέρει. Όταν είναι ενεργό, διαγράφεται κάθε αρχείο που δεν έχει χρησιμοποιηθεί για μία εβδομάδα. Άφησέ το ανενεργό αν πολλές εγκαταστάσεις μοιράζονται το ίδιο bucket αποθήκευσης.', + 'Όταν είναι ανενεργό, ο νυχτερινός έλεγχος μόνο αναφέρει. Όταν είναι ενεργό, διαγράφεται κάθε αρχείο που δεν έχει χρησιμοποιηθεί για μία εβδομάδα. Τα αρχεία που διαγράφεις στο TREK αφαιρούνται αμέσως σε κάθε περίπτωση.', 'storage.reclaim.freeable': 'Μπορεί να ελευθερωθεί', 'storage.reclaim.waiting': 'Αχρησιμοποίητα για λιγότερο από μία εβδομάδα', - 'storage.reclaim.deleted': 'Ελευθερώθηκε στον τελευταίο έλεγχο', + 'storage.reclaim.deleted': 'Ελευθερώθηκε από τον τελευταίο νυχτερινό καθαρισμό', 'storage.reclaim.nothing': 'Δεν βρέθηκαν αρχεία χωρίς αναφορά.', 'storage.reclaim.stopped': 'Ο τελευταίος έλεγχος σταμάτησε νωρίς επειδή η βάση δεδομένων επαναφέρθηκε κατά την εκτέλεσή του.', 'storage.reclaim.skip.sources_missing': 'Παραλείφθηκε: δεν ήταν δυνατή η ερώτηση σε όλα τα μέρη του TREK', 'storage.reclaim.skip.shared_location': 'Παραλείφθηκε: μοιράζεται τη θέση του με άλλη κατηγορία', - 'storage.reclaim.skip.too_many_unreferenced': - 'Παραλείφθηκε: πάρα πολλά αρχεία φαίνονται αχρησιμοποίητα, πιθανώς κοινόχρηστο bucket', 'storage.reclaim.skip.list_failed': 'Παραλείφθηκε: δεν ήταν δυνατή η λίστα των αρχείων', 'storage.reclaim.skip.source_failed': 'Παραλείφθηκε: μια αναζήτηση απέτυχε', + 'storage.reclaim.running': 'Έλεγχος…', + 'storage.reclaim.stoppedFailed': 'Ο τελευταίος έλεγχος σταμάτησε μετά από σφάλμα. Θα τρέξει ξανά απόψε.', + 'storage.reclaim.skip.foreign_install': 'Παραλείφθηκε: άλλη εγκατάσταση του TREK χρησιμοποιεί αυτή την αποθήκευση', + 'storage.reclaim.skip.owner_unknown': 'Παραλείφθηκε: το TREK δεν μπόρεσε να σημάνει αυτή την αποθήκευση ως δική του', + 'storage.reclaim.skip.delete_failed': 'Διακόπηκε: η αποθήκευση απέρριψε μια διαγραφή', + 'storage.reclaim.skip.interrupted': 'Διακόπηκε πριν ολοκληρωθεί, τα στοιχεία είναι μερικά', + 'storage.reclaim.confirmNeeded': + 'Περισσότερα από το ένα πέμπτο αυτών των αρχείων φαίνονται αχρησιμοποίητα. Τίποτα εδώ δεν διαγράφεται μέχρι να επιβεβαιώσεις ότι αυτό είναι αναμενόμενο.', + 'storage.reclaim.confirm': 'Επιβεβαίωση', + 'storage.reclaim.confirmed': 'Επιβεβαιώθηκε. Ο νυχτερινός καθαρισμός μπορεί πλέον να διαγράψει αυτά τα αρχεία.', + 'storage.reclaim.error.reclaim_busy': 'Εκτελείται άλλη εργασία αποθήκευσης. Δοκίμασε ξανά σε λίγο.', + 'storage.reclaim.error.reclaim_demo': 'Μη διαθέσιμο σε demo εγκατάσταση.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': + 'Δεν υπάρχει πλέον τίποτα προς επιβεβαίωση. Εκτέλεσε πρώτα έναν έλεγχο.', }; export default storage; diff --git a/shared/src/i18n/he/storage.ts b/shared/src/i18n/he/storage.ts index 450621bad..13c7f7f43 100644 --- a/shared/src/i18n/he/storage.ts +++ b/shared/src/i18n/he/storage.ts @@ -131,21 +131,33 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'בכל לילה TREK מחפש קבצים שהועלו ושאף טיול, מקום, יומן, אוסף או משתמש כבר לא משתמשים בהם, למשל אחרי שטיול נמחק.', 'storage.reclaim.check': 'בדיקה עכשיו', - 'storage.reclaim.checked': 'נבדק {age}', + 'storage.reclaim.checked': 'בדיקה אחרונה: {age}', 'storage.reclaim.never': 'טרם נבדק', 'storage.reclaim.delete': 'מחיקת קבצים ללא שימוש', 'storage.reclaim.deleteHint': - 'כשהאפשרות כבויה, הבדיקה הלילית רק מדווחת. כשהיא פועלת, קובץ שלא היה בשימוש במשך שבוע נמחק. השאירו אותה כבויה אם כמה התקנות חולקות אותו באקט אחסון.', + 'כשהאפשרות כבויה, הבדיקה הלילית רק מדווחת. כשהיא פועלת, קובץ שלא היה בשימוש במשך שבוע נמחק. קבצים שאתם מוחקים ב-TREK נמחקים מיד בכל מקרה.', 'storage.reclaim.freeable': 'ניתן לפנות', 'storage.reclaim.waiting': 'לא בשימוש פחות משבוע', - 'storage.reclaim.deleted': 'פונה בבדיקה האחרונה', + 'storage.reclaim.deleted': 'פונה בניקוי הלילי האחרון', 'storage.reclaim.nothing': 'לא נמצאו קבצים ללא שימוש.', 'storage.reclaim.stopped': 'הבדיקה האחרונה נעצרה מוקדם כי מסד הנתונים שוחזר בזמן שהיא רצה.', 'storage.reclaim.skip.sources_missing': 'דולג: לא ניתן היה לשאול את כל חלקי TREK', 'storage.reclaim.skip.shared_location': 'דולג: חולק את המיקום שלו עם קטגוריה אחרת', - 'storage.reclaim.skip.too_many_unreferenced': 'דולג: נראה שיותר מדי קבצים אינם בשימוש, ייתכן שזה באקט משותף', 'storage.reclaim.skip.list_failed': 'דולג: לא ניתן היה לרשום את הקבצים', 'storage.reclaim.skip.source_failed': 'דולג: חיפוש נכשל', + 'storage.reclaim.running': 'בודק…', + 'storage.reclaim.stoppedFailed': 'הבדיקה האחרונה נעצרה אחרי שגיאה. היא תרוץ שוב הלילה.', + 'storage.reclaim.skip.foreign_install': 'דולג: התקנה אחרת של TREK משתמשת באחסון הזה', + 'storage.reclaim.skip.owner_unknown': 'דולג: TREK לא הצליח לסמן את האחסון הזה כשלו', + 'storage.reclaim.skip.delete_failed': 'נעצר: האחסון סירב למחיקה', + 'storage.reclaim.skip.interrupted': 'נעצר לפני שהסתיים, הנתונים חלקיים', + 'storage.reclaim.confirmNeeded': + 'יותר מחמישית מהקבצים האלה נראים בלתי בשימוש. שום דבר כאן לא יימחק עד שתאשרו שזה צפוי.', + 'storage.reclaim.confirm': 'אישור', + 'storage.reclaim.confirmed': 'אושר. הניקוי הלילי יכול למחוק עכשיו את הקבצים האלה.', + 'storage.reclaim.error.reclaim_busy': 'משימת אחסון אחרת רצה כעת. נסו שוב בעוד רגע.', + 'storage.reclaim.error.reclaim_demo': 'לא זמין במופע הדגמה.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'אין יותר מה לאשר. הריצו קודם בדיקה.', }; export default storage; diff --git a/shared/src/i18n/hu/storage.ts b/shared/src/i18n/hu/storage.ts index 706df566a..9e3c87165 100644 --- a/shared/src/i18n/hu/storage.ts +++ b/shared/src/i18n/hu/storage.ts @@ -147,21 +147,32 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'A TREK minden éjjel megkeresi azokat a feltöltött fájlokat, amelyeket már egyetlen utazás, hely, napló, gyűjtemény vagy felhasználó sem használ, például egy utazás törlése után.', 'storage.reclaim.check': 'Ellenőrzés most', - 'storage.reclaim.checked': 'Ellenőrizve: {age}', + 'storage.reclaim.checked': 'Utolsó ellenőrzés: {age}', 'storage.reclaim.never': 'Még nem lett ellenőrizve', 'storage.reclaim.delete': 'Hivatkozás nélküli fájlok törlése', 'storage.reclaim.deleteHint': - 'Kikapcsolva az éjszakai ellenőrzés csak jelent. Bekapcsolva törlődik minden fájl, amelyet egy hete senki nem használt. Hagyd kikapcsolva, ha több telepítés oszt meg egy tárhely-bucketet.', + 'Kikapcsolva az éjszakai ellenőrzés csak jelent. Bekapcsolva törlődik minden fájl, amelyet egy hete senki nem használt. A TREK-ben általad törölt fájlok mindkét esetben azonnal törlődnek.', 'storage.reclaim.freeable': 'Felszabadítható', 'storage.reclaim.waiting': 'Kevesebb mint egy hete nem használt', - 'storage.reclaim.deleted': 'Felszabadítva az utolsó ellenőrzéskor', + 'storage.reclaim.deleted': 'Felszabadítva az utolsó éjszakai tisztításkor', 'storage.reclaim.nothing': 'Nem található hivatkozás nélküli fájl.', 'storage.reclaim.stopped': 'Az utolsó ellenőrzés idő előtt leállt, mert futás közben visszaállították az adatbázist.', 'storage.reclaim.skip.sources_missing': 'Kihagyva: a TREK nem minden része volt lekérdezhető', 'storage.reclaim.skip.shared_location': 'Kihagyva: a helyét egy másik kategóriával osztja meg', - 'storage.reclaim.skip.too_many_unreferenced': - 'Kihagyva: túl sok fájl tűnik használatlannak, lehet, hogy közös bucketről van szó', 'storage.reclaim.skip.list_failed': 'Kihagyva: a fájlokat nem sikerült listázni', 'storage.reclaim.skip.source_failed': 'Kihagyva: egy lekérdezés sikertelen volt', + 'storage.reclaim.running': 'Ellenőrzés…', + 'storage.reclaim.stoppedFailed': 'Az utolsó ellenőrzés hiba miatt leállt. Ma éjjel újra lefut.', + 'storage.reclaim.skip.foreign_install': 'Kihagyva: egy másik TREK-telepítés használja ezt a tárhelyet', + 'storage.reclaim.skip.owner_unknown': 'Kihagyva: a TREK nem tudta a sajátjaként megjelölni ezt a tárhelyet', + 'storage.reclaim.skip.delete_failed': 'Leállítva: a tárhely megtagadott egy törlést', + 'storage.reclaim.skip.interrupted': 'Leállt, mielőtt befejeződött volna, az adatok hiányosak', + 'storage.reclaim.confirmNeeded': + 'Ezeknek a fájloknak több mint ötöde használatlannak tűnik. Itt semmi sem törlődik, amíg meg nem erősíted, hogy ez várható.', + 'storage.reclaim.confirm': 'Megerősítés', + 'storage.reclaim.confirmed': 'Megerősítve. Az éjszakai tisztítás mostantól törölheti ezeket a fájlokat.', + 'storage.reclaim.error.reclaim_busy': 'Egy másik tárhelyfeladat fut. Próbáld újra egy pillanat múlva.', + 'storage.reclaim.error.reclaim_demo': 'Demópéldányon nem érhető el.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Már nincs mit megerősíteni. Előbb futtass egy ellenőrzést.', }; export default storage; diff --git a/shared/src/i18n/id/storage.ts b/shared/src/i18n/id/storage.ts index 053dd162f..64304f38c 100644 --- a/shared/src/i18n/id/storage.ts +++ b/shared/src/i18n/id/storage.ts @@ -142,22 +142,35 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'Setiap malam TREK mencari file yang diunggah tetapi tidak lagi dipakai oleh perjalanan, tempat, jurnal, koleksi, atau pengguna mana pun, misalnya setelah sebuah perjalanan dihapus.', 'storage.reclaim.check': 'Periksa sekarang', - 'storage.reclaim.checked': 'Diperiksa {age}', + 'storage.reclaim.checked': 'Pemeriksaan terakhir: {age}', 'storage.reclaim.never': 'Belum diperiksa', 'storage.reclaim.delete': 'Hapus file tanpa referensi', 'storage.reclaim.deleteHint': - 'Nonaktif, pemeriksaan malam hanya melaporkan. Aktif, file yang tidak dipakai selama seminggu akan dihapus. Biarkan nonaktif jika beberapa instalasi berbagi satu bucket penyimpanan.', + 'Nonaktif, pemeriksaan malam hanya melaporkan. Aktif, file yang tidak dipakai selama seminggu akan dihapus. File yang Anda hapus di TREK tetap langsung dihapus dalam kedua kasus.', 'storage.reclaim.freeable': 'Dapat dibebaskan', 'storage.reclaim.waiting': 'Tidak dipakai kurang dari seminggu', - 'storage.reclaim.deleted': 'Dibebaskan pada pemeriksaan terakhir', + 'storage.reclaim.deleted': 'Dibebaskan oleh pembersihan malam terakhir', 'storage.reclaim.nothing': 'Tidak ada file tanpa referensi.', 'storage.reclaim.stopped': 'Pemeriksaan terakhir berhenti lebih awal karena database dipulihkan saat pemeriksaan berjalan.', 'storage.reclaim.skip.sources_missing': 'Dilewati: tidak semua bagian TREK dapat ditanyai', 'storage.reclaim.skip.shared_location': 'Dilewati: lokasinya sama dengan kategori lain', - 'storage.reclaim.skip.too_many_unreferenced': - 'Dilewati: terlalu banyak file yang tampak tidak terpakai, mungkin bucket bersama', 'storage.reclaim.skip.list_failed': 'Dilewati: daftar file tidak dapat diambil', 'storage.reclaim.skip.source_failed': 'Dilewati: salah satu pencarian gagal', + 'storage.reclaim.running': 'Memeriksa…', + 'storage.reclaim.stoppedFailed': + 'Pemeriksaan terakhir berhenti karena error. Pemeriksaan akan berjalan lagi malam ini.', + 'storage.reclaim.skip.foreign_install': 'Dilewati: instalasi TREK lain memakai penyimpanan ini', + 'storage.reclaim.skip.owner_unknown': 'Dilewati: TREK tidak dapat menandai penyimpanan ini sebagai miliknya', + 'storage.reclaim.skip.delete_failed': 'Dihentikan: penyimpanan menolak penghapusan', + 'storage.reclaim.skip.interrupted': 'Dihentikan sebelum selesai, angkanya belum lengkap', + 'storage.reclaim.confirmNeeded': + 'Lebih dari seperlima file ini tampak tidak terpakai. Tidak ada yang dihapus di sini sampai Anda mengonfirmasi bahwa ini memang wajar.', + 'storage.reclaim.confirm': 'Konfirmasi', + 'storage.reclaim.confirmed': 'Dikonfirmasi. Pembersihan malam kini dapat menghapus file ini.', + 'storage.reclaim.error.reclaim_busy': 'Tugas penyimpanan lain sedang berjalan. Coba lagi sebentar lagi.', + 'storage.reclaim.error.reclaim_demo': 'Tidak tersedia di instans demo.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': + 'Tidak ada lagi yang perlu dikonfirmasi. Jalankan pemeriksaan terlebih dahulu.', }; export default storage; diff --git a/shared/src/i18n/it/storage.ts b/shared/src/i18n/it/storage.ts index cf7641c96..2425f9369 100644 --- a/shared/src/i18n/it/storage.ts +++ b/shared/src/i18n/it/storage.ts @@ -147,21 +147,34 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': "Ogni notte TREK cerca i file caricati che nessun viaggio, luogo, diario, raccolta o utente usa più, per esempio dopo l'eliminazione di un viaggio.", 'storage.reclaim.check': 'Controlla ora', - 'storage.reclaim.checked': 'Controllato {age}', + 'storage.reclaim.checked': 'Ultimo controllo: {age}', 'storage.reclaim.never': 'Non ancora controllato', 'storage.reclaim.delete': 'Elimina i file non referenziati', 'storage.reclaim.deleteHint': - 'Disattivato, il controllo notturno si limita a segnalare. Attivato, un file inutilizzato da una settimana viene eliminato. Lascialo disattivato se più installazioni condividono lo stesso bucket di archiviazione.', + 'Disattivato, il controllo notturno si limita a segnalare. Attivato, un file inutilizzato da una settimana viene eliminato. I file che elimini in TREK vengono comunque rimossi subito.', 'storage.reclaim.freeable': 'Può essere liberato', 'storage.reclaim.waiting': 'Inutilizzati da meno di una settimana', - 'storage.reclaim.deleted': "Liberato durante l'ultimo controllo", 'storage.reclaim.nothing': 'Nessun file non referenziato trovato.', 'storage.reclaim.stopped': "L'ultimo controllo si è interrotto prima del previsto perché il database è stato ripristinato mentre era in corso.", 'storage.reclaim.skip.sources_missing': 'Saltato: non è stato possibile interrogare tutte le parti di TREK', 'storage.reclaim.skip.shared_location': "Saltato: condivide la posizione con un'altra categoria", - 'storage.reclaim.skip.too_many_unreferenced': 'Saltato: troppi file sembrano inutilizzati, forse un bucket condiviso', 'storage.reclaim.skip.list_failed': 'Saltato: non è stato possibile elencare i file', 'storage.reclaim.skip.source_failed': 'Saltato: una ricerca non è riuscita', + 'storage.reclaim.deleted': "Liberato dall'ultima pulizia notturna", + 'storage.reclaim.running': 'Controllo in corso…', + 'storage.reclaim.stoppedFailed': "L'ultimo controllo si è fermato dopo un errore. Verrà eseguito di nuovo stanotte.", + 'storage.reclaim.skip.foreign_install': "Saltato: un'altra installazione di TREK usa questa archiviazione", + 'storage.reclaim.skip.owner_unknown': + 'Saltato: TREK non è riuscito a contrassegnare questa archiviazione come propria', + 'storage.reclaim.skip.delete_failed': "Interrotto: l'archiviazione ha rifiutato un'eliminazione", + 'storage.reclaim.skip.interrupted': 'Interrotto prima della fine, i valori sono parziali', + 'storage.reclaim.confirmNeeded': + 'Più di un quinto di questi file sembra inutilizzato. Nulla qui viene eliminato finché non confermi che è previsto.', + 'storage.reclaim.confirm': 'Conferma', + 'storage.reclaim.confirmed': 'Confermato. La pulizia notturna può ora eliminare questi file.', + 'storage.reclaim.error.reclaim_busy': "Un'altra attività di archiviazione è in corso. Riprova tra un attimo.", + 'storage.reclaim.error.reclaim_demo': "Non disponibile su un'istanza demo.", + 'storage.reclaim.error.reclaim_nothing_to_confirm': "Non c'è più nulla da confermare. Esegui prima un controllo.", }; export default storage; diff --git a/shared/src/i18n/ja/storage.ts b/shared/src/i18n/ja/storage.ts index a83393568..4f6320575 100644 --- a/shared/src/i18n/ja/storage.ts +++ b/shared/src/i18n/ja/storage.ts @@ -139,21 +139,32 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'TREKは毎晩、旅行、場所、ジャーナル、コレクション、ユーザーのいずれからも使われなくなったアップロード済みファイルを探します。たとえば旅行を削除した後などです。', 'storage.reclaim.check': '今すぐチェック', - 'storage.reclaim.checked': '{age}にチェック済み', + 'storage.reclaim.checked': '前回のチェック: {age}', 'storage.reclaim.never': '未チェック', 'storage.reclaim.delete': '参照されていないファイルを削除', 'storage.reclaim.deleteHint': - 'オフの場合、夜間チェックは報告のみ行います。オンの場合、1週間使われていないファイルが削除されます。複数のインストールで1つのストレージバケットを共有している場合は、オフのままにしてください。', + 'オフの場合、夜間チェックは報告のみ行います。オンの場合、1週間使われていないファイルが削除されます。TREKで削除したファイルは、どちらの場合もすぐに削除されます。', 'storage.reclaim.freeable': '解放可能', 'storage.reclaim.waiting': '未使用期間が1週間未満', - 'storage.reclaim.deleted': '前回のチェックで解放', + 'storage.reclaim.deleted': '前回の夜間クリーンアップで解放', 'storage.reclaim.nothing': '参照されていないファイルは見つかりませんでした。', 'storage.reclaim.stopped': '前回のチェックは、実行中にデータベースが復元されたため途中で停止しました。', 'storage.reclaim.skip.sources_missing': 'スキップ: TREKの一部に問い合わせできませんでした', 'storage.reclaim.skip.shared_location': 'スキップ: 場所が別のカテゴリと共有されています', - 'storage.reclaim.skip.too_many_unreferenced': - 'スキップ: 未使用に見えるファイルが多すぎます。共有バケットの可能性があります', 'storage.reclaim.skip.list_failed': 'スキップ: ファイルを一覧取得できませんでした', 'storage.reclaim.skip.source_failed': 'スキップ: 検索に失敗しました', + 'storage.reclaim.running': 'チェック中…', + 'storage.reclaim.stoppedFailed': '前回のチェックはエラーのため停止しました。今夜、再度実行されます。', + 'storage.reclaim.skip.foreign_install': 'スキップ: 別のTREKインストールがこのストレージを使用しています', + 'storage.reclaim.skip.owner_unknown': 'スキップ: TREKがこのストレージを自分のものとして記録できませんでした', + 'storage.reclaim.skip.delete_failed': '停止: ストレージが削除を拒否しました', + 'storage.reclaim.skip.interrupted': '完了前に停止したため、数値は一部のみです', + 'storage.reclaim.confirmNeeded': + 'これらのファイルの5分の1以上が未使用に見えます。これが想定どおりであることを確認するまで、ここでは何も削除されません。', + 'storage.reclaim.confirm': '確認', + 'storage.reclaim.confirmed': '確認しました。夜間クリーンアップでこれらのファイルが削除される可能性があります。', + 'storage.reclaim.error.reclaim_busy': '別のストレージ処理を実行中です。しばらくしてからもう一度お試しください。', + 'storage.reclaim.error.reclaim_demo': 'デモインスタンスでは利用できません。', + 'storage.reclaim.error.reclaim_nothing_to_confirm': '確認するものはもうありません。先にチェックを実行してください。', }; export default storage; diff --git a/shared/src/i18n/ko/storage.ts b/shared/src/i18n/ko/storage.ts index 27f2aab09..e8a5d7d0e 100644 --- a/shared/src/i18n/ko/storage.ts +++ b/shared/src/i18n/ko/storage.ts @@ -136,21 +136,32 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'TREK은 매일 밤 어떤 여행, 장소, 일지, 컬렉션, 사용자도 더 이상 사용하지 않는 업로드 파일을 찾습니다. 예를 들어 여행이 삭제된 뒤에 생기는 파일입니다.', 'storage.reclaim.check': '지금 확인', - 'storage.reclaim.checked': '{age} 확인됨', + 'storage.reclaim.checked': '마지막 확인: {age}', 'storage.reclaim.never': '아직 확인하지 않음', 'storage.reclaim.delete': '참조되지 않는 파일 삭제', 'storage.reclaim.deleteHint': - '끄면 야간 점검은 보고만 합니다. 켜면 일주일 동안 아무도 사용하지 않은 파일이 삭제됩니다. 여러 설치가 하나의 스토리지 버킷을 공유한다면 끄세요.', + '끄면 야간 점검은 보고만 합니다. 켜면 일주일 동안 아무도 사용하지 않은 파일이 삭제됩니다. TREK에서 직접 삭제한 파일은 어느 쪽이든 즉시 삭제됩니다.', 'storage.reclaim.freeable': '확보 가능', 'storage.reclaim.waiting': '일주일 미만 동안 사용 안 됨', - 'storage.reclaim.deleted': '지난 확인에서 확보됨', + 'storage.reclaim.deleted': '지난 야간 정리에서 확보됨', 'storage.reclaim.nothing': '참조되지 않는 파일이 없습니다.', 'storage.reclaim.stopped': '실행 중에 데이터베이스가 복원되어 마지막 확인이 중간에 중단되었습니다.', 'storage.reclaim.skip.sources_missing': '건너뜀: TREK의 일부 구성 요소를 조회할 수 없었습니다', 'storage.reclaim.skip.shared_location': '건너뜀: 다른 카테고리와 위치를 공유합니다', - 'storage.reclaim.skip.too_many_unreferenced': - '건너뜀: 사용되지 않는 것으로 보이는 파일이 너무 많습니다. 공유 버킷일 수 있습니다', 'storage.reclaim.skip.list_failed': '건너뜀: 파일 목록을 가져올 수 없었습니다', 'storage.reclaim.skip.source_failed': '건너뜀: 조회에 실패했습니다', + 'storage.reclaim.running': '확인 중…', + 'storage.reclaim.stoppedFailed': '마지막 확인이 오류로 중단되었습니다. 오늘 밤 다시 실행됩니다.', + 'storage.reclaim.skip.foreign_install': '건너뜀: 다른 TREK 설치가 이 스토리지를 사용 중입니다', + 'storage.reclaim.skip.owner_unknown': '건너뜀: TREK이 이 스토리지를 자신의 것으로 표시할 수 없었습니다', + 'storage.reclaim.skip.delete_failed': '중단됨: 스토리지가 삭제를 거부했습니다', + 'storage.reclaim.skip.interrupted': '끝나기 전에 중단되어 수치가 일부만 반영되었습니다', + 'storage.reclaim.confirmNeeded': + '이 파일의 5분의 1 이상이 사용되지 않는 것으로 보입니다. 예상된 상황이라고 확인하기 전까지는 아무것도 삭제되지 않습니다.', + 'storage.reclaim.confirm': '확인', + 'storage.reclaim.confirmed': '확인되었습니다. 이제 야간 정리에서 이 파일들을 삭제할 수 있습니다.', + 'storage.reclaim.error.reclaim_busy': '다른 스토리지 작업이 실행 중입니다. 잠시 후 다시 시도하세요.', + 'storage.reclaim.error.reclaim_demo': '데모 인스턴스에서는 사용할 수 없습니다.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': '더 이상 확인할 항목이 없습니다. 먼저 점검을 실행하세요.', }; export default storage; diff --git a/shared/src/i18n/nl/storage.ts b/shared/src/i18n/nl/storage.ts index 441389e79..c13e08670 100644 --- a/shared/src/i18n/nl/storage.ts +++ b/shared/src/i18n/nl/storage.ts @@ -146,22 +146,33 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'TREK zoekt elke nacht naar geüploade bestanden die door geen enkele reis, plaats, dagboek, collectie of gebruiker meer worden gebruikt, bijvoorbeeld nadat een reis is verwijderd.', 'storage.reclaim.check': 'Nu controleren', - 'storage.reclaim.checked': 'Gecontroleerd {age}', + 'storage.reclaim.checked': 'Laatste controle: {age}', 'storage.reclaim.never': 'Nog niet gecontroleerd', 'storage.reclaim.delete': 'Niet-gekoppelde bestanden verwijderen', 'storage.reclaim.deleteHint': - 'Uit: de nachtelijke controle rapporteert alleen. Aan: een bestand dat een week lang nergens is gebruikt, wordt verwijderd. Laat dit uit als meerdere installaties één opslagbucket delen.', + 'Uit: de nachtelijke controle rapporteert alleen. Aan: een bestand dat een week lang nergens is gebruikt, wordt verwijderd. Bestanden die je in TREK verwijdert, worden in beide gevallen meteen verwijderd.', 'storage.reclaim.freeable': 'Kan worden vrijgemaakt', 'storage.reclaim.waiting': 'Minder dan een week ongebruikt', - 'storage.reclaim.deleted': 'Vrijgemaakt bij de laatste controle', + 'storage.reclaim.deleted': 'Vrijgemaakt bij de laatste nachtelijke opruiming', 'storage.reclaim.nothing': 'Geen niet-gekoppelde bestanden gevonden.', 'storage.reclaim.stopped': 'De laatste controle is vroegtijdig gestopt omdat de database tijdens het uitvoeren werd hersteld.', 'storage.reclaim.skip.sources_missing': 'Overgeslagen: niet elk onderdeel van TREK kon worden bevraagd', 'storage.reclaim.skip.shared_location': 'Overgeslagen: deelt zijn locatie met een andere categorie', - 'storage.reclaim.skip.too_many_unreferenced': - 'Overgeslagen: te veel bestanden lijken ongebruikt, mogelijk een gedeelde bucket', 'storage.reclaim.skip.list_failed': 'Overgeslagen: de bestanden konden niet worden opgesomd', 'storage.reclaim.skip.source_failed': 'Overgeslagen: een opzoeking is mislukt', + 'storage.reclaim.running': 'Controleren…', + 'storage.reclaim.stoppedFailed': 'De laatste controle is gestopt na een fout. Vannacht wordt hij opnieuw uitgevoerd.', + 'storage.reclaim.skip.foreign_install': 'Overgeslagen: een andere TREK-installatie gebruikt deze opslag', + 'storage.reclaim.skip.owner_unknown': 'Overgeslagen: TREK kon deze opslag niet als de zijne markeren', + 'storage.reclaim.skip.delete_failed': 'Gestopt: de opslag weigerde een verwijdering', + 'storage.reclaim.skip.interrupted': 'Gestopt voordat het klaar was, de cijfers zijn onvolledig', + 'storage.reclaim.confirmNeeded': + 'Meer dan een vijfde van deze bestanden lijkt ongebruikt. Er wordt hier niets verwijderd totdat je bevestigt dat dit klopt.', + 'storage.reclaim.confirm': 'Bevestigen', + 'storage.reclaim.confirmed': 'Bevestigd. De nachtelijke opruiming mag deze bestanden nu verwijderen.', + 'storage.reclaim.error.reclaim_busy': 'Er loopt al een andere opslagtaak. Probeer het zo opnieuw.', + 'storage.reclaim.error.reclaim_demo': 'Niet beschikbaar op een demo-instantie.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Er is niets meer te bevestigen. Voer eerst een controle uit.', }; export default storage; diff --git a/shared/src/i18n/pl/storage.ts b/shared/src/i18n/pl/storage.ts index db7dd37e6..1b481ba55 100644 --- a/shared/src/i18n/pl/storage.ts +++ b/shared/src/i18n/pl/storage.ts @@ -151,22 +151,34 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'Każdej nocy TREK szuka przesłanych plików, których nie używa już żadna podróż, miejsce, dziennik, kolekcja ani użytkownik, na przykład po usunięciu podróży.', 'storage.reclaim.check': 'Sprawdź teraz', - 'storage.reclaim.checked': 'Sprawdzono {age}', + 'storage.reclaim.checked': 'Ostatnia kontrola: {age}', 'storage.reclaim.never': 'Jeszcze nie sprawdzono', 'storage.reclaim.delete': 'Usuń pliki bez powiązań', 'storage.reclaim.deleteHint': - 'Wyłączone: nocna kontrola tylko raportuje. Włączone: plik, którego nic nie używało przez tydzień, jest usuwany. Zostaw wyłączone, jeśli kilka instalacji współdzieli jeden bucket magazynu.', + 'Wyłączone: nocna kontrola tylko raportuje. Włączone: plik, którego nic nie używało przez tydzień, jest usuwany. Pliki usunięte w TREK znikają od razu w obu przypadkach.', 'storage.reclaim.freeable': 'Można zwolnić', 'storage.reclaim.waiting': 'Nieużywane krócej niż tydzień', - 'storage.reclaim.deleted': 'Zwolniono przy ostatniej kontroli', + 'storage.reclaim.deleted': 'Zwolniono podczas ostatniego nocnego czyszczenia', 'storage.reclaim.nothing': 'Nie znaleziono plików bez powiązań.', 'storage.reclaim.stopped': 'Ostatnia kontrola zakończyła się wcześniej, ponieważ w trakcie jej trwania przywrócono bazę danych.', 'storage.reclaim.skip.sources_missing': 'Pominięto: nie dało się zapytać każdej części TREK', 'storage.reclaim.skip.shared_location': 'Pominięto: współdzieli lokalizację z inną kategorią', - 'storage.reclaim.skip.too_many_unreferenced': - 'Pominięto: zbyt wiele plików wygląda na nieużywane, możliwe że to współdzielony bucket', 'storage.reclaim.skip.list_failed': 'Pominięto: nie udało się wylistować plików', 'storage.reclaim.skip.source_failed': 'Pominięto: wyszukiwanie nie powiodło się', + 'storage.reclaim.running': 'Sprawdzanie…', + 'storage.reclaim.stoppedFailed': + 'Ostatnia kontrola zatrzymała się z powodu błędu. Uruchomi się ponownie dziś w nocy.', + 'storage.reclaim.skip.foreign_install': 'Pominięto: inna instalacja TREK korzysta z tego magazynu', + 'storage.reclaim.skip.owner_unknown': 'Pominięto: TREK nie mógł oznaczyć tego magazynu jako własnego', + 'storage.reclaim.skip.delete_failed': 'Zatrzymano: magazyn odmówił usunięcia', + 'storage.reclaim.skip.interrupted': 'Zatrzymano przed zakończeniem, dane są niepełne', + 'storage.reclaim.confirmNeeded': + 'Ponad jedna piąta tych plików wygląda na nieużywane. Nic tutaj nie zostanie usunięte, dopóki nie potwierdzisz, że tak ma być.', + 'storage.reclaim.confirm': 'Potwierdź', + 'storage.reclaim.confirmed': 'Potwierdzono. Nocne czyszczenie może teraz usunąć te pliki.', + 'storage.reclaim.error.reclaim_busy': 'Trwa inne zadanie magazynu. Spróbuj ponownie za chwilę.', + 'storage.reclaim.error.reclaim_demo': 'Niedostępne w instancji demo.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Nie ma już nic do potwierdzenia. Najpierw uruchom kontrolę.', }; export default storage; diff --git a/shared/src/i18n/ru/storage.ts b/shared/src/i18n/ru/storage.ts index 7c13bab07..72fdfa734 100644 --- a/shared/src/i18n/ru/storage.ts +++ b/shared/src/i18n/ru/storage.ts @@ -152,22 +152,33 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'Каждую ночь TREK ищет загруженные файлы, которые больше не используются ни в одной поездке, месте, дневнике, коллекции или профиле пользователя, например после удаления поездки.', 'storage.reclaim.check': 'Проверить сейчас', - 'storage.reclaim.checked': 'Проверено {age}', + 'storage.reclaim.checked': 'Последняя проверка: {age}', 'storage.reclaim.never': 'Ещё не проверялось', 'storage.reclaim.delete': 'Удалять файлы без связей', 'storage.reclaim.deleteHint': - 'Выкл.: ночная проверка только сообщает о файлах. Вкл.: файл, который неделю нигде не использовался, удаляется. Оставьте выключенным, если несколько установок используют один бакет хранилища.', + 'Выкл.: ночная проверка только сообщает о файлах. Вкл.: файл, который неделю нигде не использовался, удаляется. Файлы, удалённые в TREK, в любом случае удаляются сразу.', 'storage.reclaim.freeable': 'Можно освободить', 'storage.reclaim.waiting': 'Не используются менее недели', - 'storage.reclaim.deleted': 'Освобождено при последней проверке', + 'storage.reclaim.deleted': 'Освобождено при последней ночной очистке', 'storage.reclaim.nothing': 'Файлов без связей не найдено.', 'storage.reclaim.stopped': 'Последняя проверка остановлена досрочно, потому что во время её выполнения была восстановлена база данных.', 'storage.reclaim.skip.sources_missing': 'Пропущено: не удалось опросить все части TREK', 'storage.reclaim.skip.shared_location': 'Пропущено: расположение совпадает с другой категорией', - 'storage.reclaim.skip.too_many_unreferenced': - 'Пропущено: слишком много файлов выглядят неиспользуемыми, возможно, это общий бакет', 'storage.reclaim.skip.list_failed': 'Пропущено: не удалось получить список файлов', 'storage.reclaim.skip.source_failed': 'Пропущено: запрос не выполнен', + 'storage.reclaim.running': 'Проверка…', + 'storage.reclaim.stoppedFailed': 'Последняя проверка остановилась из-за ошибки. Сегодня ночью она запустится снова.', + 'storage.reclaim.skip.foreign_install': 'Пропущено: это хранилище использует другая установка TREK', + 'storage.reclaim.skip.owner_unknown': 'Пропущено: TREK не удалось пометить это хранилище как своё', + 'storage.reclaim.skip.delete_failed': 'Остановлено: хранилище отклонило удаление', + 'storage.reclaim.skip.interrupted': 'Остановлено до завершения, данные неполные', + 'storage.reclaim.confirmNeeded': + 'Более пятой части этих файлов выглядят неиспользуемыми. Ничего здесь не будет удалено, пока вы не подтвердите, что так и должно быть.', + 'storage.reclaim.confirm': 'Подтвердить', + 'storage.reclaim.confirmed': 'Подтверждено. Ночная очистка теперь может удалить эти файлы.', + 'storage.reclaim.error.reclaim_busy': 'Выполняется другая задача хранилища. Повторите попытку через минуту.', + 'storage.reclaim.error.reclaim_demo': 'Недоступно в демонстрационной версии.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Подтверждать больше нечего. Сначала запустите проверку.', }; export default storage; diff --git a/shared/src/i18n/sk/storage.ts b/shared/src/i18n/sk/storage.ts index 6de3ebbae..ad5ae916b 100644 --- a/shared/src/i18n/sk/storage.ts +++ b/shared/src/i18n/sk/storage.ts @@ -159,21 +159,32 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'TREK každú noc hľadá nahrané súbory, ktoré už nepoužíva žiadna cesta, miesto, denník, kolekcia ani používateľ, napríklad po odstránení cesty.', 'storage.reclaim.check': 'Skontrolovať teraz', - 'storage.reclaim.checked': 'Skontrolované {age}', + 'storage.reclaim.checked': 'Posledná kontrola: {age}', 'storage.reclaim.never': 'Ešte neskontrolované', 'storage.reclaim.delete': 'Odstraňovať nepoužívané súbory', 'storage.reclaim.deleteHint': - 'Vypnuté: nočná kontrola len hlási. Zapnuté: súbor, ktorý týždeň nikto nepoužil, sa odstráni. Nechajte vypnuté, ak viaceré inštalácie zdieľajú jeden bucket úložiska.', + 'Vypnuté: nočná kontrola len hlási. Zapnuté: súbor, ktorý týždeň nikto nepoužil, sa odstráni. Súbory, ktoré odstránite v TREK, sa v oboch prípadoch odstránia okamžite.', 'storage.reclaim.freeable': 'Možno uvoľniť', 'storage.reclaim.waiting': 'Nepoužité menej ako týždeň', - 'storage.reclaim.deleted': 'Uvoľnené pri poslednej kontrole', + 'storage.reclaim.deleted': 'Uvoľnené pri poslednom nočnom čistení', 'storage.reclaim.nothing': 'Nenašli sa žiadne nepoužívané súbory.', 'storage.reclaim.stopped': 'Posledná kontrola sa predčasne zastavila, pretože sa počas jej behu obnovila databáza.', 'storage.reclaim.skip.sources_missing': 'Preskočené: nebolo možné sa opýtať každej časti TREK', 'storage.reclaim.skip.shared_location': 'Preskočené: zdieľa umiestnenie s inou kategóriou', - 'storage.reclaim.skip.too_many_unreferenced': - 'Preskočené: príliš veľa súborov vyzerá ako nepoužívaných, možno ide o zdieľaný bucket', 'storage.reclaim.skip.list_failed': 'Preskočené: súbory sa nepodarilo vypísať', 'storage.reclaim.skip.source_failed': 'Preskočené: vyhľadávanie zlyhalo', + 'storage.reclaim.running': 'Kontroluje sa…', + 'storage.reclaim.stoppedFailed': 'Posledná kontrola sa zastavila po chybe. Dnes v noci sa spustí znova.', + 'storage.reclaim.skip.foreign_install': 'Preskočené: toto úložisko používa iná inštalácia TREK', + 'storage.reclaim.skip.owner_unknown': 'Preskočené: TREK nemohol označiť toto úložisko ako svoje', + 'storage.reclaim.skip.delete_failed': 'Zastavené: úložisko odmietlo odstránenie', + 'storage.reclaim.skip.interrupted': 'Zastavené pred dokončením, údaje sú neúplné', + 'storage.reclaim.confirmNeeded': + 'Viac ako pätina týchto súborov vyzerá ako nepoužívaná. Nič tu nebude odstránené, kým nepotvrdíte, že je to očakávané.', + 'storage.reclaim.confirm': 'Potvrdiť', + 'storage.reclaim.confirmed': 'Potvrdené. Nočné čistenie môže tieto súbory teraz odstrániť.', + 'storage.reclaim.error.reclaim_busy': 'Beží iná úloha úložiska. Skúste to o chvíľu znova.', + 'storage.reclaim.error.reclaim_demo': 'Nie je dostupné v demo inštancii.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Už nie je čo potvrdzovať. Najprv spustite kontrolu.', }; export default storage; diff --git a/shared/src/i18n/sv/storage.ts b/shared/src/i18n/sv/storage.ts index c15e4fa5c..d4db559c8 100644 --- a/shared/src/i18n/sv/storage.ts +++ b/shared/src/i18n/sv/storage.ts @@ -142,22 +142,33 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'Varje natt letar TREK efter uppladdade filer som ingen resa, plats, dagbok, samling eller användare längre använder, till exempel efter att en resa har raderats.', 'storage.reclaim.check': 'Kontrollera nu', - 'storage.reclaim.checked': 'Kontrollerades {age}', + 'storage.reclaim.checked': 'Senaste kontrollen: {age}', 'storage.reclaim.never': 'Inte kontrollerat ännu', 'storage.reclaim.delete': 'Radera filer utan koppling', 'storage.reclaim.deleteHint': - 'Av: nattkontrollen rapporterar bara. På: en fil som ingenting har använt på en vecka raderas. Låt den vara av om flera installationer delar en lagringsbucket.', + 'Av: nattkontrollen rapporterar bara. På: en fil som ingenting har använt på en vecka raderas. Filer som du raderar i TREK tas bort direkt i båda fallen.', 'storage.reclaim.freeable': 'Kan frigöras', 'storage.reclaim.waiting': 'Oanvända i mindre än en vecka', - 'storage.reclaim.deleted': 'Frigjordes vid senaste kontrollen', + 'storage.reclaim.deleted': 'Frigjordes vid den senaste nattliga rensningen', 'storage.reclaim.nothing': 'Inga filer utan koppling hittades.', 'storage.reclaim.stopped': 'Den senaste kontrollen avbröts i förtid eftersom databasen återställdes medan den pågick.', 'storage.reclaim.skip.sources_missing': 'Hoppades över: alla delar av TREK kunde inte tillfrågas', 'storage.reclaim.skip.shared_location': 'Hoppades över: delar plats med en annan kategori', - 'storage.reclaim.skip.too_many_unreferenced': - 'Hoppades över: för många filer verkar oanvända, möjligen en delad bucket', 'storage.reclaim.skip.list_failed': 'Hoppades över: filerna kunde inte listas', 'storage.reclaim.skip.source_failed': 'Hoppades över: en uppslagning misslyckades', + 'storage.reclaim.running': 'Kontrollerar…', + 'storage.reclaim.stoppedFailed': 'Den senaste kontrollen avbröts efter ett fel. Den körs igen i natt.', + 'storage.reclaim.skip.foreign_install': 'Hoppades över: en annan TREK-installation använder den här lagringen', + 'storage.reclaim.skip.owner_unknown': 'Hoppades över: TREK kunde inte markera den här lagringen som sin egen', + 'storage.reclaim.skip.delete_failed': 'Avbröts: lagringen nekade en radering', + 'storage.reclaim.skip.interrupted': 'Avbröts innan den blev klar, siffrorna är ofullständiga', + 'storage.reclaim.confirmNeeded': + 'Mer än en femtedel av filerna verkar oanvända. Ingenting här raderas förrän du bekräftar att det är väntat.', + 'storage.reclaim.confirm': 'Bekräfta', + 'storage.reclaim.confirmed': 'Bekräftat. Den nattliga rensningen får nu radera de här filerna.', + 'storage.reclaim.error.reclaim_busy': 'En annan lagringsuppgift pågår. Försök igen om en stund.', + 'storage.reclaim.error.reclaim_demo': 'Inte tillgängligt på en demoinstans.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Det finns inget mer att bekräfta. Kör en kontroll först.', }; export default storage; diff --git a/shared/src/i18n/th/storage.ts b/shared/src/i18n/th/storage.ts index 9dc17287b..ad97e6e12 100644 --- a/shared/src/i18n/th/storage.ts +++ b/shared/src/i18n/th/storage.ts @@ -154,21 +154,32 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'ทุกคืน TREK จะค้นหาไฟล์ที่อัปโหลดซึ่งไม่มีทริป สถานที่ บันทึก คอลเลกชัน หรือผู้ใช้คนใดใช้งานอีกต่อไป เช่น หลังจากลบทริป', 'storage.reclaim.check': 'ตรวจสอบตอนนี้', - 'storage.reclaim.checked': 'ตรวจสอบเมื่อ {age}', + 'storage.reclaim.checked': 'ตรวจสอบครั้งล่าสุด: {age}', 'storage.reclaim.never': 'ยังไม่เคยตรวจสอบ', 'storage.reclaim.delete': 'ลบไฟล์ที่ไม่มีการอ้างอิง', 'storage.reclaim.deleteHint': - 'ปิด: การตรวจสอบรายคืนจะรายงานเท่านั้น เปิด: ไฟล์ที่ไม่มีการใช้งานเป็นเวลาหนึ่งสัปดาห์จะถูกลบ ควรปิดไว้หากหลายการติดตั้งใช้ถังเก็บข้อมูลเดียวกัน', + 'ปิด: การตรวจสอบรายคืนจะรายงานเท่านั้น เปิด: ไฟล์ที่ไม่มีการใช้งานเป็นเวลาหนึ่งสัปดาห์จะถูกลบ ไฟล์ที่คุณลบใน TREK จะถูกลบทันทีไม่ว่าจะเปิดหรือปิด', 'storage.reclaim.freeable': 'ว่างได้', 'storage.reclaim.waiting': 'ไม่ได้ใช้งานน้อยกว่าหนึ่งสัปดาห์', - 'storage.reclaim.deleted': 'ว่างแล้วในการตรวจสอบครั้งล่าสุด', + 'storage.reclaim.deleted': 'ว่างแล้วจากการล้างข้อมูลรายคืนครั้งล่าสุด', 'storage.reclaim.nothing': 'ไม่พบไฟล์ที่ไม่มีการอ้างอิง', 'storage.reclaim.stopped': 'การตรวจสอบครั้งล่าสุดหยุดก่อนกำหนด เพราะมีการกู้คืนฐานข้อมูลระหว่างที่ทำงาน', 'storage.reclaim.skip.sources_missing': 'ข้าม: ไม่สามารถสอบถามทุกส่วนของ TREK ได้', 'storage.reclaim.skip.shared_location': 'ข้าม: ใช้ตำแหน่งที่ตั้งเดียวกับหมวดหมู่อื่น', - 'storage.reclaim.skip.too_many_unreferenced': - 'ข้าม: มีไฟล์ที่ดูเหมือนไม่ได้ใช้มากเกินไป อาจเป็นถังเก็บข้อมูลที่ใช้ร่วมกัน', 'storage.reclaim.skip.list_failed': 'ข้าม: ไม่สามารถแสดงรายการไฟล์ได้', 'storage.reclaim.skip.source_failed': 'ข้าม: การค้นหาล้มเหลว', + 'storage.reclaim.running': 'กำลังตรวจสอบ…', + 'storage.reclaim.stoppedFailed': 'การตรวจสอบครั้งล่าสุดหยุดลงเพราะเกิดข้อผิดพลาด จะทำงานอีกครั้งคืนนี้', + 'storage.reclaim.skip.foreign_install': 'ข้าม: การติดตั้ง TREK อื่นใช้ที่เก็บข้อมูลนี้อยู่', + 'storage.reclaim.skip.owner_unknown': 'ข้าม: TREK ไม่สามารถทำเครื่องหมายว่าที่เก็บข้อมูลนี้เป็นของตนได้', + 'storage.reclaim.skip.delete_failed': 'หยุด: ที่เก็บข้อมูลปฏิเสธการลบ', + 'storage.reclaim.skip.interrupted': 'หยุดก่อนเสร็จสิ้น ตัวเลขเป็นเพียงบางส่วน', + 'storage.reclaim.confirmNeeded': + 'ไฟล์เหล่านี้มากกว่าหนึ่งในห้าดูเหมือนไม่ได้ใช้งาน จะไม่มีการลบสิ่งใดจนกว่าคุณจะยืนยันว่าเป็นเรื่องปกติ', + 'storage.reclaim.confirm': 'ยืนยัน', + 'storage.reclaim.confirmed': 'ยืนยันแล้ว การล้างข้อมูลรายคืนอาจลบไฟล์เหล่านี้ได้แล้ว', + 'storage.reclaim.error.reclaim_busy': 'มีงานเกี่ยวกับที่เก็บข้อมูลอื่นกำลังทำงานอยู่ โปรดลองอีกครั้งในอีกสักครู่', + 'storage.reclaim.error.reclaim_demo': 'ไม่พร้อมใช้งานบนอินสแตนซ์สาธิต', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'ไม่มีอะไรให้ยืนยันอีกแล้ว โปรดรันการตรวจสอบก่อน', }; export default storage; diff --git a/shared/src/i18n/tr/storage.ts b/shared/src/i18n/tr/storage.ts index d346b816a..b7746dcd6 100644 --- a/shared/src/i18n/tr/storage.ts +++ b/shared/src/i18n/tr/storage.ts @@ -144,21 +144,32 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'TREK her gece, artık hiçbir gezi, yer, günlük, koleksiyon veya kullanıcı tarafından kullanılmayan yüklenmiş dosyaları arar, örneğin bir gezi silindikten sonra.', 'storage.reclaim.check': 'Şimdi kontrol et', - 'storage.reclaim.checked': 'Kontrol edildi: {age}', + 'storage.reclaim.checked': 'Son kontrol: {age}', 'storage.reclaim.never': 'Henüz kontrol edilmedi', 'storage.reclaim.delete': 'Başvurulmayan dosyaları sil', - 'storage.reclaim.deleteHint': - "Kapalıyken gece kontrolü yalnızca rapor verir. Açıkken bir hafta boyunca hiçbir yerde kullanılmayan dosya silinir. Birden fazla kurulum aynı depolama bucket'ını paylaşıyorsa kapalı bırakın.", 'storage.reclaim.freeable': 'Boşaltılabilir', 'storage.reclaim.waiting': 'Bir haftadan kısa süredir kullanılmıyor', - 'storage.reclaim.deleted': 'Son kontrolde boşaltıldı', + 'storage.reclaim.deleted': 'Son gece temizliğiyle boşaltıldı', 'storage.reclaim.nothing': 'Başvurulmayan dosya bulunamadı.', 'storage.reclaim.stopped': 'Son kontrol erken durdu, çünkü çalışırken veritabanı geri yüklendi.', 'storage.reclaim.skip.sources_missing': "Atlandı: TREK'in her bölümüne sorulamadı", 'storage.reclaim.skip.shared_location': 'Atlandı: konumunu başka bir kategoriyle paylaşıyor', - 'storage.reclaim.skip.too_many_unreferenced': - 'Atlandı: çok fazla dosya kullanılmıyor gibi görünüyor, olasılıkla paylaşılan bir bucket', 'storage.reclaim.skip.list_failed': 'Atlandı: dosyalar listelenemedi', 'storage.reclaim.skip.source_failed': 'Atlandı: bir sorgu başarısız oldu', + 'storage.reclaim.deleteHint': + "Kapalıyken gece kontrolü yalnızca rapor verir. Açıkken bir hafta boyunca hiçbir yerde kullanılmayan dosya silinir. TREK'te sildiğiniz dosyalar her iki durumda da hemen kaldırılır.", + 'storage.reclaim.running': 'Kontrol ediliyor…', + 'storage.reclaim.stoppedFailed': 'Son kontrol bir hata nedeniyle durdu. Bu gece yeniden çalışacak.', + 'storage.reclaim.skip.foreign_install': 'Atlandı: başka bir TREK kurulumu bu depolamayı kullanıyor', + 'storage.reclaim.skip.owner_unknown': 'Atlandı: TREK bu depolamayı kendisine ait olarak işaretleyemedi', + 'storage.reclaim.skip.delete_failed': 'Durduruldu: depolama bir silme işlemini reddetti', + 'storage.reclaim.skip.interrupted': 'Bitmeden durduruldu, rakamlar eksik', + 'storage.reclaim.confirmNeeded': + 'Bu dosyaların beşte birinden fazlası kullanılmıyor gibi görünüyor. Bunun beklenen bir durum olduğunu onaylayana kadar burada hiçbir şey silinmez.', + 'storage.reclaim.confirm': 'Onayla', + 'storage.reclaim.confirmed': 'Onaylandı. Gece temizliği artık bu dosyaları silebilir.', + 'storage.reclaim.error.reclaim_busy': 'Başka bir depolama görevi çalışıyor. Birazdan tekrar deneyin.', + 'storage.reclaim.error.reclaim_demo': 'Demo örneğinde kullanılamaz.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Onaylanacak bir şey kalmadı. Önce bir kontrol çalıştırın.', }; export default storage; diff --git a/shared/src/i18n/uk/storage.ts b/shared/src/i18n/uk/storage.ts index bd9f2a871..efdfe51c1 100644 --- a/shared/src/i18n/uk/storage.ts +++ b/shared/src/i18n/uk/storage.ts @@ -151,21 +151,32 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'Щоночі TREK шукає завантажені файли, які більше не використовує жодна поїздка, місце, щоденник, колекція чи користувач, наприклад після видалення поїздки.', 'storage.reclaim.check': 'Перевірити зараз', - 'storage.reclaim.checked': 'Перевірено {age}', + 'storage.reclaim.checked': 'Остання перевірка: {age}', 'storage.reclaim.never': 'Ще не перевірялося', 'storage.reclaim.delete': 'Видаляти файли без посилань', 'storage.reclaim.deleteHint': - 'Вимкнено: нічна перевірка лише повідомляє. Увімкнено: файл, який тиждень ніщо не використовує, видаляється. Залиште вимкненим, якщо кілька встановлень ділять один бакет сховища.', + 'Вимкнено: нічна перевірка лише повідомляє. Увімкнено: файл, який тиждень ніщо не використовує, видаляється. Файли, які ви видаляєте в TREK, у будь-якому разі видаляються одразу.', 'storage.reclaim.freeable': 'Можна звільнити', 'storage.reclaim.waiting': 'Не використовується менше тижня', - 'storage.reclaim.deleted': 'Звільнено під час останньої перевірки', + 'storage.reclaim.deleted': 'Звільнено під час останнього нічного очищення', 'storage.reclaim.nothing': 'Файлів без посилань не знайдено.', 'storage.reclaim.stopped': 'Остання перевірка зупинилася раніше, бо під час її роботи базу даних було відновлено.', 'storage.reclaim.skip.sources_missing': 'Пропущено: не вдалося опитати всі частини TREK', 'storage.reclaim.skip.shared_location': 'Пропущено: ділить розташування з іншою категорією', - 'storage.reclaim.skip.too_many_unreferenced': - 'Пропущено: забагато файлів здаються невикористаними, можливо, це спільний бакет', 'storage.reclaim.skip.list_failed': 'Пропущено: не вдалося отримати список файлів', 'storage.reclaim.skip.source_failed': 'Пропущено: пошук завершився помилкою', + 'storage.reclaim.running': 'Перевірка…', + 'storage.reclaim.stoppedFailed': 'Остання перевірка зупинилася через помилку. Вона запуститься знову вночі.', + 'storage.reclaim.skip.foreign_install': 'Пропущено: це сховище використовує інше встановлення TREK', + 'storage.reclaim.skip.owner_unknown': 'Пропущено: TREK не зміг позначити це сховище як своє', + 'storage.reclaim.skip.delete_failed': 'Зупинено: сховище відхилило видалення', + 'storage.reclaim.skip.interrupted': 'Зупинено до завершення, цифри неповні', + 'storage.reclaim.confirmNeeded': + "Понад п'ята частина цих файлів виглядає невикористаною. Нічого тут не видаляється, доки ви не підтвердите, що це очікувано.", + 'storage.reclaim.confirm': 'Підтвердити', + 'storage.reclaim.confirmed': 'Підтверджено. Нічне очищення тепер може видаляти ці файли.', + 'storage.reclaim.error.reclaim_busy': 'Виконується інше завдання зі сховищем. Спробуйте за мить.', + 'storage.reclaim.error.reclaim_demo': 'Недоступно в демонстраційному екземплярі.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Підтверджувати більше нічого. Спершу запустіть перевірку.', }; export default storage; diff --git a/shared/src/i18n/vi/storage.ts b/shared/src/i18n/vi/storage.ts index adb1177d5..bc7c2bad8 100644 --- a/shared/src/i18n/vi/storage.ts +++ b/shared/src/i18n/vi/storage.ts @@ -140,21 +140,32 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'Mỗi đêm TREK tìm các tệp đã tải lên mà không chuyến đi, địa điểm, nhật ký, bộ sưu tập hay người dùng nào còn sử dụng, ví dụ sau khi một chuyến đi bị xóa.', 'storage.reclaim.check': 'Kiểm tra ngay', - 'storage.reclaim.checked': 'Đã kiểm tra {age}', + 'storage.reclaim.checked': 'Lần kiểm tra gần nhất: {age}', 'storage.reclaim.never': 'Chưa kiểm tra', 'storage.reclaim.delete': 'Xóa các tệp không còn được tham chiếu', 'storage.reclaim.deleteHint': - 'Tắt: lần kiểm tra hằng đêm chỉ báo cáo. Bật: tệp không được dùng trong một tuần sẽ bị xóa. Hãy để tắt nếu nhiều bản cài đặt dùng chung một bucket lưu trữ.', + 'Tắt: lần kiểm tra hằng đêm chỉ báo cáo. Bật: tệp không được dùng trong một tuần sẽ bị xóa. Tệp bạn xóa trong TREK luôn bị gỡ ngay, dù bật hay tắt.', 'storage.reclaim.freeable': 'Có thể giải phóng', 'storage.reclaim.waiting': 'Chưa dùng dưới một tuần', - 'storage.reclaim.deleted': 'Đã giải phóng ở lần kiểm tra trước', + 'storage.reclaim.deleted': 'Đã giải phóng ở lần dọn dẹp hằng đêm gần nhất', 'storage.reclaim.nothing': 'Không tìm thấy tệp nào không còn được tham chiếu.', 'storage.reclaim.stopped': 'Lần kiểm tra trước đã dừng sớm vì cơ sở dữ liệu được khôi phục trong khi đang chạy.', 'storage.reclaim.skip.sources_missing': 'Bỏ qua: không thể truy vấn mọi phần của TREK', 'storage.reclaim.skip.shared_location': 'Bỏ qua: dùng chung vị trí với một danh mục khác', - 'storage.reclaim.skip.too_many_unreferenced': - 'Bỏ qua: quá nhiều tệp có vẻ không được dùng, có thể là bucket dùng chung', 'storage.reclaim.skip.list_failed': 'Bỏ qua: không thể liệt kê các tệp', 'storage.reclaim.skip.source_failed': 'Bỏ qua: một truy vấn đã thất bại', + 'storage.reclaim.running': 'Đang kiểm tra…', + 'storage.reclaim.stoppedFailed': 'Lần kiểm tra gần nhất đã dừng do có lỗi. Tối nay sẽ chạy lại.', + 'storage.reclaim.skip.foreign_install': 'Bỏ qua: một bản cài đặt TREK khác đang dùng bộ lưu trữ này', + 'storage.reclaim.skip.owner_unknown': 'Bỏ qua: TREK không thể đánh dấu bộ lưu trữ này là của mình', + 'storage.reclaim.skip.delete_failed': 'Đã dừng: bộ lưu trữ từ chối một thao tác xóa', + 'storage.reclaim.skip.interrupted': 'Đã dừng trước khi hoàn tất, số liệu chỉ là một phần', + 'storage.reclaim.confirmNeeded': + 'Hơn một phần năm số tệp này có vẻ không được dùng. Sẽ không có gì bị xóa cho đến khi bạn xác nhận điều này là bình thường.', + 'storage.reclaim.confirm': 'Xác nhận', + 'storage.reclaim.confirmed': 'Đã xác nhận. Lần dọn dẹp hằng đêm giờ có thể xóa các tệp này.', + 'storage.reclaim.error.reclaim_busy': 'Một tác vụ lưu trữ khác đang chạy. Hãy thử lại sau giây lát.', + 'storage.reclaim.error.reclaim_demo': 'Không khả dụng trên bản demo.', + 'storage.reclaim.error.reclaim_nothing_to_confirm': 'Không còn gì để xác nhận. Hãy chạy kiểm tra trước.', }; export default storage; diff --git a/shared/src/i18n/zh-TW/storage.ts b/shared/src/i18n/zh-TW/storage.ts index 508cdffb6..122879e52 100644 --- a/shared/src/i18n/zh-TW/storage.ts +++ b/shared/src/i18n/zh-TW/storage.ts @@ -130,20 +130,31 @@ const storage: TranslationStrings = { 'storage.reclaim.hint': 'TREK 每晚會尋找不再被任何行程、地點、日誌、集合或使用者使用的已上傳檔案,例如行程被刪除之後。', 'storage.reclaim.check': '立即檢查', - 'storage.reclaim.checked': '{age}檢查', + 'storage.reclaim.checked': '上次檢查:{age}', 'storage.reclaim.never': '尚未檢查', 'storage.reclaim.delete': '刪除未被引用的檔案', 'storage.reclaim.deleteHint': - '關閉時,夜間檢查只會回報。開啟後,一週內無人使用的檔案會被刪除。如果多個安裝共用同一個儲存貯體,請保持關閉。', + '關閉時,夜間檢查只會回報。開啟後,一週內無人使用的檔案會被刪除。無論開關與否,你在 TREK 中刪除的檔案都會立即移除。', 'storage.reclaim.freeable': '可釋放', 'storage.reclaim.waiting': '未使用不足一週', - 'storage.reclaim.deleted': '上次檢查已釋放', + 'storage.reclaim.deleted': '上次夜間清理已釋放', 'storage.reclaim.nothing': '未發現未被引用的檔案。', 'storage.reclaim.stopped': '上次檢查提前停止,因為執行期間資料庫被還原了。', 'storage.reclaim.skip.sources_missing': '已略過:無法查詢 TREK 的所有部分', 'storage.reclaim.skip.shared_location': '已略過:與另一個類別共用位置', - 'storage.reclaim.skip.too_many_unreferenced': '已略過:看起來未使用的檔案過多,可能是共用儲存貯體', 'storage.reclaim.skip.list_failed': '已略過:無法列出檔案', 'storage.reclaim.skip.source_failed': '已略過:某次查詢失敗', + 'storage.reclaim.running': '正在檢查…', + 'storage.reclaim.stoppedFailed': '上次檢查因錯誤而停止。今晚會再次執行。', + 'storage.reclaim.skip.foreign_install': '已略過:另一個 TREK 安裝正在使用此儲存空間', + 'storage.reclaim.skip.owner_unknown': '已略過:TREK 無法將此儲存空間標記為自己的', + 'storage.reclaim.skip.delete_failed': '已停止:儲存空間拒絕了一次刪除', + 'storage.reclaim.skip.interrupted': '未完成即停止,數字僅為部分', + 'storage.reclaim.confirmNeeded': '這些檔案中超過五分之一看起來未被使用。在你確認這是預期情況之前,不會刪除任何內容。', + 'storage.reclaim.confirm': '確認', + 'storage.reclaim.confirmed': '已確認。夜間清理現在可以刪除這些檔案。', + 'storage.reclaim.error.reclaim_busy': '另一項儲存任務正在執行。請稍後再試。', + 'storage.reclaim.error.reclaim_demo': '示範實例中無法使用。', + 'storage.reclaim.error.reclaim_nothing_to_confirm': '已無需確認。請先執行一次檢查。', }; export default storage; diff --git a/shared/src/i18n/zh/storage.ts b/shared/src/i18n/zh/storage.ts index 9c819d3e3..810192f69 100644 --- a/shared/src/i18n/zh/storage.ts +++ b/shared/src/i18n/zh/storage.ts @@ -128,20 +128,31 @@ const storage: TranslationStrings = { 'storage.reclaim.title': '未被引用的文件', 'storage.reclaim.hint': 'TREK 每晚会查找不再被任何行程、地点、日志、合集或用户使用的已上传文件,例如行程被删除之后。', 'storage.reclaim.check': '立即检查', - 'storage.reclaim.checked': '{age}检查', + 'storage.reclaim.checked': '上次检查:{age}', 'storage.reclaim.never': '尚未检查', 'storage.reclaim.delete': '删除未被引用的文件', 'storage.reclaim.deleteHint': - '关闭时,夜间检查只做报告。开启后,一周内无人使用的文件会被删除。如果多个安装共用同一个存储桶,请保持关闭。', + '关闭时,夜间检查只做报告。开启后,一周内无人使用的文件会被删除。无论开关与否,你在 TREK 中删除的文件都会立即移除。', 'storage.reclaim.freeable': '可释放', 'storage.reclaim.waiting': '未使用不足一周', - 'storage.reclaim.deleted': '上次检查已释放', + 'storage.reclaim.deleted': '上次夜间清理已释放', 'storage.reclaim.nothing': '未发现未被引用的文件。', 'storage.reclaim.stopped': '上次检查提前停止,因为运行期间数据库被恢复了。', 'storage.reclaim.skip.sources_missing': '已跳过:无法查询 TREK 的所有部分', 'storage.reclaim.skip.shared_location': '已跳过:与另一个类别共用位置', - 'storage.reclaim.skip.too_many_unreferenced': '已跳过:看起来未使用的文件过多,可能是共享存储桶', 'storage.reclaim.skip.list_failed': '已跳过:无法列出文件', 'storage.reclaim.skip.source_failed': '已跳过:某次查询失败', + 'storage.reclaim.running': '正在检查…', + 'storage.reclaim.stoppedFailed': '上次检查因错误而停止。今晚会再次运行。', + 'storage.reclaim.skip.foreign_install': '已跳过:另一个 TREK 安装正在使用此存储', + 'storage.reclaim.skip.owner_unknown': '已跳过:TREK 无法将此存储标记为自己的', + 'storage.reclaim.skip.delete_failed': '已停止:存储拒绝了一次删除', + 'storage.reclaim.skip.interrupted': '未完成即停止,数据仅为部分', + 'storage.reclaim.confirmNeeded': '这些文件中超过五分之一看起来未被使用。在你确认这是预期情况之前,不会删除任何内容。', + 'storage.reclaim.confirm': '确认', + 'storage.reclaim.confirmed': '已确认。夜间清理现在可以删除这些文件。', + 'storage.reclaim.error.reclaim_busy': '另一项存储任务正在运行。请稍后再试。', + 'storage.reclaim.error.reclaim_demo': '演示实例中不可用。', + 'storage.reclaim.error.reclaim_nothing_to_confirm': '已无需确认。请先运行一次检查。', }; export default storage; diff --git a/wiki/Admin-Storage.md b/wiki/Admin-Storage.md index e7a2ffce5..471c0fa63 100644 --- a/wiki/Admin-Storage.md +++ b/wiki/Admin-Storage.md @@ -141,7 +141,7 @@ any more, and the **Unreferenced files** section shows what it found: check deletes them once deletion is on. - **Unused for less than a week**: these get their week first. A file that is used again in the meantime (an undo, a copied trip) starts over. -- **Freed on the last check**: what the last nightly check with deletion on +- **Freed by the last nightly cleanup**: what the last nightly check with deletion on deleted. A check you start by hand never deletes, so it never changes this figure.