diff --git a/i18n/en.json b/i18n/en.json index 67dd06198a..0e55252060 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -177,10 +177,17 @@ "machine_learning_smart_search_enabled": "Enable smart search", "machine_learning_smart_search_enabled_description": "If disabled, images will not be encoded for smart search.", "machine_learning_url_description": "The URL of the machine learning server. If more than one URL is provided, each server will be attempted one-at-a-time until one responds successfully, in order from first to last. Servers that don't respond will be temporarily ignored until they come back online.", + "maintenance_restore_backup": "Restore Backup", + "maintenance_delete_backup": "Delete Backup", + "maintenance_delete_backup_description": "This file will be irrevocably deleted.", + "maintenance_restore_backup_description": "Immich will be wiped and restored from the chosen backup. A backup will be created before continuing.", "maintenance_settings": "Maintenance", "maintenance_settings_description": "Put Immich into maintenance mode.", "maintenance_start": "Start maintenance mode", + "maintenance_upload_backup": "Upload database backup file", "maintenance_start_error": "Failed to start maintenance mode.", + "maintenance_delete_error": "Failed to delete backup.", + "maintenance_upload_backup_error": "Could not upload backup, is it an .sql/.sql.gz file?", "manage_concurrency": "Manage Concurrency", "manage_log_settings": "Manage log settings", "map_dark_style": "Dark style", @@ -1324,12 +1331,26 @@ "loop_videos_description": "Enable to automatically loop a video in the detail viewer.", "main_branch_warning": "You're using a development version; we strongly recommend using a release version!", "main_menu": "Main menu", + "maintenance_action_restore": "Restoring Database", "maintenance_description": "Immich has been put into maintenance mode.", "maintenance_end": "End maintenance mode", "maintenance_end_error": "Failed to end maintenance mode.", "maintenance_logged_in_as": "Currently logged in as {user}", - "maintenance_task_backup": "Creating a backup of the existing database...", - "maintenance_task_restore": "Restoring the chosen backup...", + "maintenance_restore_from_backup": "Restore From Backup", + "maintenance_restore_library": "Restore Your Library", + "maintenance_restore_library_confirm": "If this looks correct, continue to restoring a backup!", + "maintenance_restore_library_description": "Restoring Database", + "maintenance_restore_library_folder_has_files": "{folder} has {count} folder(s)", + "maintenance_restore_library_folder_no_files": "{folder} is missing files!", + "maintenance_restore_library_folder_pass": "readable and writable", + "maintenance_restore_library_folder_read_fail": "not readable", + "maintenance_restore_library_folder_write_fail": "not writable", + "maintenance_restore_library_hint_missing_files": "You may be missing important files", + "maintenance_restore_library_hint_regenerate_later": "You can regenerate these later in settings", + "maintenance_restore_library_hint_storage_template_missing_files": "Using storage template? You may be missing files", + "maintenance_restore_library_loading": "Loading integrity checks and heuristics…", + "maintenance_task_backup": "Creating a backup of the existing database…", + "maintenance_task_restore": "Restoring the chosen backup…", "maintenance_title": "Temporarily Unavailable", "make": "Make", "manage_geolocation": "Manage location", @@ -1916,6 +1937,12 @@ "shared_link_edit_expire_after_option_hours": "{count} hours", "shared_link_edit_expire_after_option_minute": "1 minute", "shared_link_edit_expire_after_option_minutes": "{count} minutes", + "created_day_ago": "Created 1 day ago", + "created_days_ago": "Created {count} days ago", + "created_hour_ago": "Created 1 hour ago", + "created_hours_ago": "Created {count} hours ago", + "created_minute_ago": "Created 1 minute ago", + "created_minutes_ago": "Created {count} minutes ago", "shared_link_edit_expire_after_option_months": "{count} months", "shared_link_edit_expire_after_option_year": "{count} year", "shared_link_edit_password_hint": "Enter the share password", diff --git a/web/src/lib/components/maintenance/MaintenanceBackupsList.svelte b/web/src/lib/components/maintenance/MaintenanceBackupsList.svelte index fe111fe812..a2a0875809 100644 --- a/web/src/lib/components/maintenance/MaintenanceBackupsList.svelte +++ b/web/src/lib/components/maintenance/MaintenanceBackupsList.svelte @@ -18,6 +18,7 @@ IconButton, menuManager, modalManager, + ProgressBar, Stack, Text, type ContextMenuBaseProps, @@ -36,15 +37,15 @@ function mapBackups(filenames: string[]) { return filenames.map((filename) => { const date = /(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})/.exec(filename); - const hoursAgo = date + const minutesAgo = date ? Math.floor( - (+Date.now() - +new Date(`${date[1]}-${date[2]}-${date[3]}T${date[4]}:${date[5]}:${date[6]}`)) / 36e5, + (+Date.now() - +new Date(`${date[1]}-${date[2]}-${date[3]}T${date[4]}:${date[5]}:${date[6]}`)) / 60_000, ) : null; return { filename, - hoursAgo, + minutesAgo, }; }); } @@ -61,9 +62,9 @@ async function restore(filename: string) { const confirm = await modalManager.showDialog({ - confirmText: 'Restore', - title: 'Restore Backup', - prompt: 'Immich will be wiped and restored from the chosen backup. A backup will be created before continuing.', + confirmText: $t('restore'), + title: $t('admin.maintenance_restore_backup'), + prompt: $t('admin.maintenance_restore_backup_description'), }); if (confirm) { @@ -82,9 +83,9 @@ async function remove(filename: string) { const confirm = await modalManager.showDialog({ - confirmText: 'Delete', - title: 'Delete Backup', - prompt: 'This file will be irrevocably deleted.', + confirmText: $t('delete'), + title: $t('admin.maintenance_delete_backup'), + prompt: $t('admin.maintenance_delete_backup_description'), }); if (confirm) { @@ -97,7 +98,7 @@ backups = backups.filter((backup) => backup.filename !== filename); } catch (error) { - handleError(error, 'failed to delete backup i18n'); + handleError(error, $t('admin.maintenance_delete_error')); } finally { deleting.delete(filename); } @@ -114,14 +115,14 @@ target: event.currentTarget as HTMLElement, items: [ { - title: 'Download', + title: $t('download'), icon: mdiDownload, onSelect() { void download(filename); }, }, { - title: 'Delete', + title: $t('delete'), icon: mdiTrashCanOutline, color: 'danger', onSelect() { @@ -153,7 +154,7 @@ const { backups: newList } = await listBackups(); backups = mapBackups(newList); } catch (error) { - handleError(error, 'Could not upload backup, is it an .sql/.sql.gz file?'); + handleError(error, $t('admin.maintenance_upload_backup_error')); } finally { uploadProgress = -1; } @@ -165,15 +166,13 @@ {#if uploadProgress === -1} - Upload database backup file - + {$t('admin.maintenance_upload_backup')} + {:else} - - Uploading... -
-
-
+ + {$t('asset_uploading')} + {/if}
@@ -185,20 +184,48 @@ {backup.filename} - {#if typeof backup.hoursAgo === 'number'} - {#if backup.hoursAgo <= 24} + {#if typeof backup.minutesAgo === 'number'} + + {#if backup.minutesAgo <= 1} + {$t('created_minute_ago')} + {:else if backup.minutesAgo < 60} + {$t('created_minutes_ago', { + values: { + count: backup.minutesAgo, + }, + })} + {:else if backup.minutesAgo < 60 * 2} + {$t('created_hour_ago')} + {:else if backup.minutesAgo < 60 * 24} + {$t('created_hours_ago', { + values: { + count: Math.floor(backup.minutesAgo / 60), + }, + })} + {:else if backup.minutesAgo < 60 * 24 * 2} + {$t('created_day_ago')} + {:else} + {$t('created_days_ago', { + values: { + count: Math.floor(backup.minutesAgo / (60 * 24)), + }, + })} + {/if} + + {/if} {$t('restore')} + handleOpen(event, { position: 'top-right' }, backup.filename)} - aria-label="Open menu" + aria-label={$t('open')} /> diff --git a/web/src/lib/components/maintenance/MaintenanceRestoreFlow.svelte b/web/src/lib/components/maintenance/MaintenanceRestoreFlow.svelte index a2ccab8164..b8fe4b4868 100644 --- a/web/src/lib/components/maintenance/MaintenanceRestoreFlow.svelte +++ b/web/src/lib/components/maintenance/MaintenanceRestoreFlow.svelte @@ -4,6 +4,7 @@ import { Button, Card, CardBody, Heading, HStack, Icon, Scrollable, Stack, Text } from '@immich/ui'; import { mdiAlert, mdiArrowLeft, mdiArrowRight, mdiCheck, mdiClose, mdiRefresh } from '@mdi/js'; import { onMount } from 'svelte'; + import { t } from 'svelte-i18n'; interface Props { end?: () => void; @@ -19,22 +20,11 @@ } onMount(reload); - - const i18nMap = { - 'encoded-video': 'Encoded Video', - library: 'Library', - upload: 'Upload', - profile: 'Profile', - thumbs: 'Thumbs', - backups: 'Backups', - }; {#if stage === 0} - Restore Your Library - Before restoring a database backup, you must ensure your library has been restored or is otherwise already present. + {$t('maintenance_restore_library')} + {$t('maintenance_restore_library_description')} @@ -46,12 +36,10 @@ color={`rgb(var(--immich-ui-${writable ? 'success' : 'danger'}))`} /> {i18nMap[folder as keyof typeof i18nMap]} ({writable - ? 'readable and writable' - : readable - ? 'not writable' - : 'not readable'}) + >{folder} ({$t( + `maintenance_restore_library_folder_${writable ? 'pass' : readable ? 'write_fail' : 'read_fail'}`, + )}) + {/each} {#each integrity.storage as { folder, files } (folder)} @@ -65,47 +53,57 @@ {#if files} - {i18nMap[folder as keyof typeof i18nMap]} has {files} folder(s) + {$t('maintenance_restore_library_folder_has_files', { + values: { + folder, + count: files, + }, + })} {:else} - {i18nMap[folder as keyof typeof i18nMap]} is missing files! + {$t('maintenance_restore_library_folder_no_files', { + values: { + folder, + }, + })} {/if} {#if !files && (folder === 'profile' || folder === 'upload')} - You may be missing files + {$t('maintenance_restore_library_hint_missing_files')} {/if} {#if !files && (folder === 'encoded-video' || folder === 'thumbs')} - You can regenerate these later in settings + {$t('maintenance_restore_library_hint_regenerate_later')} {/if} {#if !files && folder === 'library'} - Using storage template? You may be missing files + {$t('maintenance_restore_library_hint_storage_template_missing_files')} {/if} {/if} {/each} - + {:else} - Loading integrity checks and heuristics... + {$t('maintenance_restore_library_loading')} {/if} - If this looks correct, continue to restoring a backup! + {$t('maintenance_restore_library_confirm')} - - + + {:else} - Restore From Backup + {$t('maintenance_restore_from_backup')} - - + + {/if} diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index d8881e52d4..ca44db7490 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -28,7 +28,7 @@ {$t('getting_started')} diff --git a/web/src/routes/maintenance/+page.svelte b/web/src/routes/maintenance/+page.svelte index 61862650b8..a149b2ca8c 100644 --- a/web/src/routes/maintenance/+page.svelte +++ b/web/src/routes/maintenance/+page.svelte @@ -43,7 +43,7 @@ >
{#if $status?.action === MaintenanceAction.RestoreDatabase && $status.task} - Restoring Database + {$t('maintenance_action_restore')} {#if $status.error}
{error}