diff --git a/apps/app-frontend/src/components/ui/instance_settings/BackupExclusionSelector.vue b/apps/app-frontend/src/components/ui/instance_settings/BackupExclusionSelector.vue new file mode 100644 index 0000000..7544988 --- /dev/null +++ b/apps/app-frontend/src/components/ui/instance_settings/BackupExclusionSelector.vue @@ -0,0 +1,132 @@ + + + diff --git a/apps/app-frontend/src/components/ui/instance_settings/BackupSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/BackupSettings.vue new file mode 100644 index 0000000..5299c11 --- /dev/null +++ b/apps/app-frontend/src/components/ui/instance_settings/BackupSettings.vue @@ -0,0 +1,675 @@ + + + diff --git a/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue b/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue index b5869c2..d7fa727 100644 --- a/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue +++ b/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue @@ -20,6 +20,7 @@ import type { PlatformTag } from '@modrinth/utils' import { useQuery, useQueryClient } from '@tanstack/vue-query' import { computed, nextTick, ref, watch } from 'vue' +import BackupSettings from '@/components/ui/instance_settings/BackupSettings.vue' import CoreComponentsSettings from '@/components/ui/instance_settings/CoreComponentsSettings.vue' import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue' import HooksSettings from '@/components/ui/instance_settings/HooksSettings.vue' @@ -105,6 +106,14 @@ const tabs = computed(() => [ icon: WrenchIcon, content: InstallationSettings, }, + { + name: defineMessage({ + id: 'instance.settings.tabs.backups', + defaultMessage: 'Backups', + }), + icon: FileArchiveIcon, + content: BackupSettings, + }, { // Core component editing is instance-specific and advanced, so it is intentionally excluded from first-run onboarding. name: defineMessage({ diff --git a/apps/app-frontend/src/components/ui/settings/BackupRepositorySettings.vue b/apps/app-frontend/src/components/ui/settings/BackupRepositorySettings.vue new file mode 100644 index 0000000..46e3dd8 --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/BackupRepositorySettings.vue @@ -0,0 +1,167 @@ + + + diff --git a/apps/app-frontend/src/components/ui/settings/StorageBackupSettings.vue b/apps/app-frontend/src/components/ui/settings/StorageBackupSettings.vue index b1c7803..b557d8e 100644 --- a/apps/app-frontend/src/components/ui/settings/StorageBackupSettings.vue +++ b/apps/app-frontend/src/components/ui/settings/StorageBackupSettings.vue @@ -1,4 +1,5 @@ @@ -6,6 +7,7 @@ import StorageSettings from './StorageSettings.vue' diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index 9841f94..9373038 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -10000,5 +10000,170 @@ }, "app.navigation.screenshots": { "message": "Screenshots" + }, + "instance.backups.added-size": { + "message": "{size} newly stored" + }, + "instance.backups.all-included": { + "message": "All instance files and folders are included." + }, + "instance.backups.backup-now": { + "message": "Back up now" + }, + "instance.backups.cancel": { + "message": "Cancel backup" + }, + "instance.backups.choose": { + "message": "Everything is backed up by default. Add files or folders that should be excluded." + }, + "instance.backups.delete": { + "message": "Delete backup" + }, + "instance.backups.delete-body": { + "message": "This snapshot will be permanently deleted. Unused stored files will also be removed." + }, + "instance.backups.delete-title": { + "message": "Delete this backup?" + }, + "instance.backups.description": { + "message": "Create deduplicated snapshots of the whole instance while it is closed." + }, + "instance.backups.disable": { + "message": "Disable backups" + }, + "instance.backups.disable-blocked": { + "message": "Delete every snapshot before disabling backups." + }, + "instance.backups.edit-exclusions": { + "message": "Edit exclusions" + }, + "instance.backups.empty": { + "message": "No backups have been created yet." + }, + "instance.backups.enable": { + "message": "Enable backups" + }, + "instance.backups.excluded-paths": { + "message": "Excluded: {paths}" + }, + "instance.backups.exclusions.add-file": { + "message": "Add excluded file" + }, + "instance.backups.exclusions.add-folder": { + "message": "Add excluded folder" + }, + "instance.backups.exclusions.empty": { + "message": "No files or folders are excluded." + }, + "instance.backups.exclusions.remove": { + "message": "Remove {path} from exclusions" + }, + "instance.backups.loading": { + "message": "Loading backup settings..." + }, + "instance.backups.progress-bytes": { + "message": "{processed} of {total}" + }, + "instance.backups.restore": { + "message": "Restore backup" + }, + "instance.backups.restore-body": { + "message": "Upcoming operations: add {added, plural, one {# file} other {# files}}, modify {modified, plural, one {# file} other {# files}}, and delete {deleted, plural, one {# file} other {# files}}. Continue?" + }, + "instance.backups.restore-title": { + "message": "Restore this backup?" + }, + "instance.backups.save-exclusions": { + "message": "Save exclusions" + }, + "instance.backups.snapshot-meta": { + "message": "{files, plural, one {# file} other {# files}} · {size}" + }, + "instance.backups.snapshots": { + "message": "Snapshots" + }, + "instance.backups.stage.applying": { + "message": "Applying changed files" + }, + "instance.backups.stage.failed": { + "message": "Backup failed" + }, + "instance.backups.stage.hashing": { + "message": "Hashing files" + }, + "instance.backups.stage.interrupted": { + "message": "Backup operation was interrupted" + }, + "instance.backups.stage.materializing": { + "message": "Preparing changed files" + }, + "instance.backups.stage.preview": { + "message": "Analyzing restore changes" + }, + "instance.backups.stage.queued": { + "message": "Waiting to start" + }, + "instance.backups.stage.saving": { + "message": "Saving snapshot index" + }, + "instance.backups.stage.scanning": { + "message": "Scanning files" + }, + "instance.backups.stage.validating": { + "message": "Validating restore" + }, + "instance.backups.title": { + "message": "Instance backups" + }, + "instance.backups.unavailable": { + "message": "Backups are unavailable" + }, + "instance.backups.unavailable.direct-linked": { + "message": "Instances linked directly to another launcher cannot be backed up." + }, + "instance.backups.unavailable.external-directory": { + "message": "Instances that use an external game directory cannot be backed up." + }, + "instance.backups.unavailable.not-installed": { + "message": "Finish installing this instance before enabling backups." + }, + "instance.backups.unavailable.root-link": { + "message": "Instances whose root folder is a link cannot be backed up." + }, + "instance.backups.unavailable.root-missing": { + "message": "The instance folder is missing." + }, + "instance.backups.unavailable.symlink": { + "message": "Imported symbolic-link instances cannot be backed up." + }, + "settings.backups.repository.change": { + "message": "Change folder" + }, + "settings.backups.repository.description": { + "message": "Deduplicated instance backup data and its independent index database." + }, + "settings.backups.repository.location": { + "message": "Location" + }, + "settings.backups.repository.location-description": { + "message": "Changing the location moves the complete repository. Choose a new or empty folder." + }, + "settings.backups.repository.moving": { + "message": "Moving repository..." + }, + "settings.backups.repository.moving-progress": { + "message": "Moving repository: {processed} of {total}" + }, + "settings.backups.repository.title": { + "message": "Backup repository" + }, + "settings.backups.repository.unavailable": { + "message": "Repository unavailable" + }, + "settings.backups.repository.usage": { + "message": "Storage usage" + }, + "settings.backups.repository.usage-description": { + "message": "{stored} stored for {logical} of backup data across {count, plural, one {# snapshot} other {# snapshots}}." } } diff --git a/apps/app-frontend/src/locales/zh-CN/index.json b/apps/app-frontend/src/locales/zh-CN/index.json index 9b367e0..a2ffa43 100644 --- a/apps/app-frontend/src/locales/zh-CN/index.json +++ b/apps/app-frontend/src/locales/zh-CN/index.json @@ -10764,5 +10764,170 @@ }, "app.navigation.screenshots": { "message": "截图" + }, + "instance.backups.added-size": { + "message": "新增存储 {size}" + }, + "instance.backups.all-included": { + "message": "实例中的所有文件和文件夹都会纳入备份。" + }, + "instance.backups.backup-now": { + "message": "立即备份" + }, + "instance.backups.cancel": { + "message": "取消备份" + }, + "instance.backups.choose": { + "message": "默认备份实例中的全部内容,请添加不需要备份的文件或文件夹。" + }, + "instance.backups.delete": { + "message": "删除备份" + }, + "instance.backups.delete-body": { + "message": "此快照将被永久删除,不再使用的仓库文件也会一并清理。" + }, + "instance.backups.delete-title": { + "message": "删除此备份?" + }, + "instance.backups.description": { + "message": "在实例关闭时,为整个实例创建去重快照。" + }, + "instance.backups.disable": { + "message": "关闭备份" + }, + "instance.backups.disable-blocked": { + "message": "必须先删除全部快照才能关闭备份。" + }, + "instance.backups.edit-exclusions": { + "message": "编辑排除项" + }, + "instance.backups.empty": { + "message": "尚未创建任何备份。" + }, + "instance.backups.enable": { + "message": "启用备份" + }, + "instance.backups.excluded-paths": { + "message": "不备份:{paths}" + }, + "instance.backups.exclusions.add-file": { + "message": "添加忽略文件" + }, + "instance.backups.exclusions.add-folder": { + "message": "添加忽略文件夹" + }, + "instance.backups.exclusions.empty": { + "message": "没有忽略任何文件或文件夹。" + }, + "instance.backups.exclusions.remove": { + "message": "从排除项中移除 {path}" + }, + "instance.backups.loading": { + "message": "正在加载备份设置……" + }, + "instance.backups.progress-bytes": { + "message": "已处理 {processed},共 {total}" + }, + "instance.backups.restore": { + "message": "恢复备份" + }, + "instance.backups.restore-body": { + "message": "即将进行的操作:新增 {added} 个文件,修改 {modified} 个文件,删除 {deleted} 个文件。是否继续?" + }, + "instance.backups.restore-title": { + "message": "恢复此备份?" + }, + "instance.backups.save-exclusions": { + "message": "保存排除项" + }, + "instance.backups.snapshot-meta": { + "message": "{files, plural, other {# 个文件}} · {size}" + }, + "instance.backups.snapshots": { + "message": "快照" + }, + "instance.backups.stage.applying": { + "message": "正在应用变更文件" + }, + "instance.backups.stage.failed": { + "message": "备份失败" + }, + "instance.backups.stage.hashing": { + "message": "正在计算文件哈希" + }, + "instance.backups.stage.interrupted": { + "message": "备份任务已中断" + }, + "instance.backups.stage.materializing": { + "message": "正在准备变更文件" + }, + "instance.backups.stage.preview": { + "message": "正在分析恢复变更" + }, + "instance.backups.stage.queued": { + "message": "等待开始" + }, + "instance.backups.stage.saving": { + "message": "正在保存快照索引" + }, + "instance.backups.stage.scanning": { + "message": "正在扫描文件" + }, + "instance.backups.stage.validating": { + "message": "正在验证恢复任务" + }, + "instance.backups.title": { + "message": "实例备份" + }, + "instance.backups.unavailable": { + "message": "备份不可用" + }, + "instance.backups.unavailable.direct-linked": { + "message": "不支持备份直接关联到其他启动器的实例。" + }, + "instance.backups.unavailable.external-directory": { + "message": "不支持备份使用外部游戏目录的实例。" + }, + "instance.backups.unavailable.not-installed": { + "message": "请先完成实例安装,再启用备份。" + }, + "instance.backups.unavailable.root-link": { + "message": "不支持备份根文件夹为链接的实例。" + }, + "instance.backups.unavailable.root-missing": { + "message": "实例文件夹不存在。" + }, + "instance.backups.unavailable.symlink": { + "message": "不支持备份以符号链接导入的实例。" + }, + "settings.backups.repository.change": { + "message": "更改文件夹" + }, + "settings.backups.repository.description": { + "message": "存放去重后的实例备份数据及其独立索引数据库。" + }, + "settings.backups.repository.location": { + "message": "位置" + }, + "settings.backups.repository.location-description": { + "message": "更改位置会移动完整仓库。请选择新的或空的文件夹。" + }, + "settings.backups.repository.moving": { + "message": "正在移动仓库……" + }, + "settings.backups.repository.moving-progress": { + "message": "正在移动备份仓库:已处理 {processed},共 {total}" + }, + "settings.backups.repository.title": { + "message": "备份文件仓库" + }, + "settings.backups.repository.unavailable": { + "message": "仓库不可用" + }, + "settings.backups.repository.usage": { + "message": "存储占用" + }, + "settings.backups.repository.usage-description": { + "message": "实际存储 {stored},对应 {logical} 的备份数据,共 {count, plural, other {# 个快照}}。" } } diff --git a/apps/app-frontend/src/locales/zh-TW/index.json b/apps/app-frontend/src/locales/zh-TW/index.json index 912d919..ac75760 100644 --- a/apps/app-frontend/src/locales/zh-TW/index.json +++ b/apps/app-frontend/src/locales/zh-TW/index.json @@ -10195,5 +10195,170 @@ }, "app.instance.dependencies.zoom-out": { "message": "縮小" + }, + "instance.backups.added-size": { + "message": "新增存储 {size}" + }, + "instance.backups.all-included": { + "message": "实例中的所有文件和文件夹都会纳入备份。" + }, + "instance.backups.backup-now": { + "message": "立即备份" + }, + "instance.backups.cancel": { + "message": "取消备份" + }, + "instance.backups.choose": { + "message": "默认备份实例中的全部内容,请添加不需要备份的文件或文件夹。" + }, + "instance.backups.delete": { + "message": "删除备份" + }, + "instance.backups.delete-body": { + "message": "此快照将被永久删除,不再使用的仓库文件也会一并清理。" + }, + "instance.backups.delete-title": { + "message": "删除此备份?" + }, + "instance.backups.description": { + "message": "在实例关闭时,为整个实例创建去重快照。" + }, + "instance.backups.disable": { + "message": "关闭备份" + }, + "instance.backups.disable-blocked": { + "message": "必须先删除全部快照才能关闭备份。" + }, + "instance.backups.edit-exclusions": { + "message": "编辑排除项" + }, + "instance.backups.empty": { + "message": "尚未创建任何备份。" + }, + "instance.backups.enable": { + "message": "启用备份" + }, + "instance.backups.excluded-paths": { + "message": "不备份:{paths}" + }, + "instance.backups.exclusions.add-file": { + "message": "添加忽略文件" + }, + "instance.backups.exclusions.add-folder": { + "message": "添加忽略文件夹" + }, + "instance.backups.exclusions.empty": { + "message": "没有忽略任何文件或文件夹。" + }, + "instance.backups.exclusions.remove": { + "message": "从排除项中移除 {path}" + }, + "instance.backups.loading": { + "message": "正在加载备份设置……" + }, + "instance.backups.progress-bytes": { + "message": "已处理 {processed},共 {total}" + }, + "instance.backups.restore": { + "message": "恢复备份" + }, + "instance.backups.restore-body": { + "message": "即将进行的操作:新增 {added} 个文件,修改 {modified} 个文件,删除 {deleted} 个文件。是否继续?" + }, + "instance.backups.restore-title": { + "message": "恢复此备份?" + }, + "instance.backups.save-exclusions": { + "message": "保存排除项" + }, + "instance.backups.snapshot-meta": { + "message": "{files, plural, other {# 个文件}} · {size}" + }, + "instance.backups.snapshots": { + "message": "快照" + }, + "instance.backups.stage.applying": { + "message": "正在应用变更文件" + }, + "instance.backups.stage.failed": { + "message": "备份失败" + }, + "instance.backups.stage.hashing": { + "message": "正在计算文件哈希" + }, + "instance.backups.stage.interrupted": { + "message": "备份任务已中断" + }, + "instance.backups.stage.materializing": { + "message": "正在准备变更文件" + }, + "instance.backups.stage.preview": { + "message": "正在分析恢复变更" + }, + "instance.backups.stage.queued": { + "message": "等待开始" + }, + "instance.backups.stage.saving": { + "message": "正在保存快照索引" + }, + "instance.backups.stage.scanning": { + "message": "正在扫描文件" + }, + "instance.backups.stage.validating": { + "message": "正在验证恢复任务" + }, + "instance.backups.title": { + "message": "实例备份" + }, + "instance.backups.unavailable": { + "message": "备份不可用" + }, + "instance.backups.unavailable.direct-linked": { + "message": "不支持备份直接关联到其他启动器的实例。" + }, + "instance.backups.unavailable.external-directory": { + "message": "不支持备份使用外部游戏目录的实例。" + }, + "instance.backups.unavailable.not-installed": { + "message": "请先完成实例安装,再启用备份。" + }, + "instance.backups.unavailable.root-link": { + "message": "不支持备份根文件夹为链接的实例。" + }, + "instance.backups.unavailable.root-missing": { + "message": "实例文件夹不存在。" + }, + "instance.backups.unavailable.symlink": { + "message": "不支持备份以符号链接导入的实例。" + }, + "settings.backups.repository.change": { + "message": "更改文件夹" + }, + "settings.backups.repository.description": { + "message": "存放去重后的实例备份数据及其独立索引数据库。" + }, + "settings.backups.repository.location": { + "message": "位置" + }, + "settings.backups.repository.location-description": { + "message": "更改位置会移动完整仓库。请选择新的或空的文件夹。" + }, + "settings.backups.repository.moving": { + "message": "正在移动仓库……" + }, + "settings.backups.repository.moving-progress": { + "message": "正在移动备份仓库:已处理 {processed},共 {total}" + }, + "settings.backups.repository.title": { + "message": "备份文件仓库" + }, + "settings.backups.repository.unavailable": { + "message": "仓库不可用" + }, + "settings.backups.repository.usage": { + "message": "存储占用" + }, + "settings.backups.repository.usage-description": { + "message": "实际存储 {stored},对应 {logical} 的备份数据,共 {count, plural, other {# 个快照}}。" } }