diff --git a/cmd/opensave-app/frontend/src/locales/en.json b/cmd/opensave-app/frontend/src/locales/en.json index 92793523..7da165a7 100644 --- a/cmd/opensave-app/frontend/src/locales/en.json +++ b/cmd/opensave-app/frontend/src/locales/en.json @@ -1,21 +1,190 @@ { "settings.language": "Language", "settings.systemDefault": "System default", - "home.title": "Home", + "common.cancel": "Cancel", + "common.nowTracking": "Now tracking \"{name}\"", + "home.addGame.adding": "Adding…", + "home.addGame.changeFolder": "Change folder…", + "home.addGame.chooseFile": "Choose the save file to track", + "home.addGame.name": "Name", + "home.addGame.nameHint": "How it shows in your library, and on your other devices.", + "home.addGame.namePlaceholder": "e.g. Elden Ring", + "home.addGame.savePath": "Save folder or file", + "home.addGame.startTracking": "Start tracking", + "home.addGame.title": "Track a game", + "home.addGame.trackFileInstead": "Track a single file instead…", + "home.library.bulkDone": { + "one": "{verb} {n} game", + "other": "{verb} {n} games" + }, + "home.library.bulkPartial": "{verb} {done} of {total} — {failed}", + "home.library.clearFilters": "Clear filters", + "home.library.collectionsLabel": "Collections", + "home.library.empty": "No games match.", + "home.library.emptyFor": "No games match \"{query}\".", + "home.library.favourite": "Favourite", + "home.library.favouriteTitle": "Add each to Favourites", + "home.library.reverseTitle": "Reverse the order", + "home.library.reversedTitle": "Reversed — put back in order", + "home.library.searchLabel": "Find a game", + "home.library.searchPlaceholder": "Find a game…", + "home.library.select": "Select", + "home.library.selectAll": "Select all ({count})", + "home.library.selectedCount": "{n} selected", + "home.library.showAll": "Show all", + "home.library.showing": "Showing {shown} of {total}.", + "home.library.snapshot": "Snapshot", + "home.library.snapshotTitle": "Take a snapshot of each", + "home.library.sort": "Sort", + "home.library.sync": "Sync", + "home.library.syncTitle": "Sync each with your devices", + "home.library.title": "Library", + "home.library.unselectAll": "Unselect all", + "home.library.untrackSelected": "Untrack selected", + "home.library.verbFavourite": "Added to Favourites:", + "home.library.verbSnapshot": "Took a snapshot of", + "home.library.verbSync": "Started syncing", + "home.library.view": "View", + "home.library.viewHint": "Also in Settings → General.", + "home.library.viewLabel": "Library view", + "home.offered.chooseFolder": "Choose folder…", + "home.offered.decline": "Decline", + "home.offered.declineToast": "You won't be asked about {name} again. Tracking it yourself later still works.", + "home.offered.folderPrompt": "Folder for \"{name}\" on this device", + "home.offered.intro": { + "one": "Another device syncs this game, but this one doesn't know where to keep it yet. Nothing syncs until you choose.", + "other": "Other devices sync these games, but this one doesn't know where to keep them yet. Nothing syncs until you choose." + }, + "home.offered.pathHint": "Kept at {path} on the other device.", + "home.offered.title": "Waiting for a folder", + "home.offered.trackedToast": "{name} is now tracked here", + "home.recent.allActivity": "All activity", + "home.recent.empty": "Nothing yet. Snapshots and syncs show up here as they happen.", + "home.recent.label": "Recent", + "home.scan.alreadyTracked": "Already tracked ({n})", + "home.scan.badResponse": "The scan's answer was not a list of saves. Restart OpenSave and scan again.", + "home.scan.clear": "Clear", + "home.scan.emptyNoMatches": "No matches for this filter.", + "home.scan.emptyNothingDetected": "Nothing detected. You can still track any folder manually.", + "home.scan.exclude": "Exclude", + "home.scan.excludeTitle": "Stop offering this location in future scans", + "home.scan.excludeToast": "\"{name}\" won't be offered in scans again.", + "home.scan.excluding": "Excluding…", + "home.scan.filterPlaceholder": "Filter by name or path…", + "home.scan.folderList.headFound": "{name} was found in {n} places.", + "home.scan.folderList.headTick": "Tick the folders that belong to this save — they are tracked as one game.", + "home.scan.folderList.tagAlt": "another copy — probably an old install", + "home.scan.folderList.tagInside": "already inside the folder above", + "home.scan.folderList.tagLocation": "part of the same save", + "home.scan.folderList.tagSave": "the save folder", + "home.scan.folderList.tagTracked": "already tracked", + "home.scan.foundInFolders": { + "one": "found in {n} folder", + "other": "found in {n} folders" + }, + "home.scan.foundSummary": { + "one": "Found {n} save location — {m} available to track", + "other": "Found {n} save locations — {m} available to track" + }, + "home.scan.foundSummaryEmpty": "Found {n} save locations — {m} available to track, {e} empty hidden", + "home.scan.mergedPartialFail": "Tracked \"{name}\", but {n} folder(s) failed: {detail}", + "home.scan.oneSave": { + "one": "· {n} is one save", + "other": "· {n} are one save" + }, + "home.scan.scanningDetail": "Scanning Steam, emulators, and configured folders…", + "home.scan.selectAll": "Select all ({n})", + "home.scan.showEmpty": "Show {n} empty", + "home.scan.showEmptyTitle": "Folders that exist but hold no files — Steam makes one for every game you own, whether or not saves go there. A game with nothing saved anywhere is in here too.", + "home.scan.showTracked": "Show tracked", + "home.scan.showTrackedTitle": "Also show saves you already track", + "home.scan.subScanning": "Scanning your system…", + "home.scan.tab.all": "All", + "home.scan.tab.emulators": "Emulators", + "home.scan.tab.games": "Games", + "home.scan.tab.repacks": "Repacks", + "home.scan.title": "Scan results", + "home.scan.track": "Track", + "home.scan.trackAll": "Track all {n}", + "home.scan.trackAsOne": "Track as one game", + "home.scan.trackAsOneTitle": "Track everything ticked as a single game, whatever it was grouped under. For a split save the scan did not spot.", + "home.scan.trackPartialFail": "Tracking \"{name}\" — {n} folder(s) could not be added: {detail}", + "home.scan.trackSelected": "Track selected ({n})", + "home.scan.tracked": "Tracked", + "home.scan.trackedAcrossFolders": "Now tracking \"{name}\" across {n} folders", + "home.scan.trackedGames": { + "one": "Tracked {n} game", + "other": "Tracked {n} games" + }, + "home.scan.trackedGamesWithExtras": "Tracked {n} games with {m} extra locations", + "home.scan.type.emulator": "Emulator", + "home.scan.type.game": "Game", + "home.scan.type.repack": "Repack", + "home.setup.chooseWhere": "Choose where", + "home.setup.hideTitle": "Hide this guide", + "home.setup.later": "Later", + "home.setup.noCloudBackup": "No cloud backup", + "home.setup.onlyThisDevice": "Only this device for now", + "home.setup.pairDevice": "Pair a device", + "home.setup.pickFolder": "Pick a folder", + "home.setup.progress": "{n} of 3 done", + "home.setup.skipped": "Skipped", + "home.setup.stepCloud.text": "A copy of every snapshot in Google Drive, OneDrive, Dropbox or a folder of your own.", + "home.setup.stepCloud.title": "Back up to the cloud", + "home.setup.stepDevices.text": "A PC, a Steam Deck or a handheld with OpenSave on it — on your Wi-Fi, or anywhere over the internet.", + "home.setup.stepDevices.title": "Add your other devices", + "home.setup.stepSaves.text": "OpenSave looks through Steam, emulators and the usual save folders, and you choose what to keep.", + "home.setup.stepSaves.title": "Find your game saves", + "home.setup.title": "Get OpenSave set up", + "home.summary.cloudBackup": "Cloud backup", + "home.summary.cloudBackupTitle": "Open Cloud Backup", + "home.summary.cloudNotSetUp": "Not set up", + "home.summary.cloudOff": "Off", + "home.summary.cloudOn": "On", + "home.summary.devicesOnline": "{online} of {paired} online", + "home.summary.devicesTitle": "Open Devices", + "home.summary.lastSnapshot": "Last snapshot", + "home.summary.lastSnapshotTitle": "{name} · {when}", + "home.summary.lowSpace": "Only {size} free on the drive your snapshots are kept on — new ones may not fit.", + "home.summary.nonePaired": "None paired", + "home.summary.noneYet": "None yet", + "home.summary.offline": "offline", + "home.summary.online": "online", + "home.summary.pausedDetail": "Syncing is paused {length}. Snapshots are still taken, and everything catches up when it resumes.", + "home.summary.resumeNow": "Resume now", + "home.summary.seeSpace": "See what takes the space", + "home.summary.sharedSavingTitle": "On disk — {size} less than the snapshots' own sizes, for sharing unchanged files. Open Settings → Storage", + "home.summary.spaceUsed": "Space used", + "home.summary.spaceUsedTitle": "Open Settings → Storage", + "home.tile.inFavourites": "In Favourites", + "home.tile.onBranch": "on {branch}", + "home.tile.played": "{length} played", + "home.tile.snapshots": { + "one": "{n} snapshot", + "other": "{n} snapshots" + }, + "home.viewOptions.autoFit": "As many as fit", + "home.viewOptions.coverStyle": "Cover style", + "home.viewOptions.covers": "Covers", + "home.viewOptions.fixedNote": "With a set number per row, the tiles share the width between them.", + "home.viewOptions.overview": "Show recent activity on Home", + "home.viewOptions.tileSize": "Tile size", + "home.viewOptions.tilesPerRow": "Tiles per row", + "home.chooseFolderDialog": "Choose the save folder to track", + "home.cloudBackup": "Cloud Backup", + "home.devices": "Devices", "home.scanSaves": "Scan saves", "home.scanSavesTitle": "Look for game saves on this device", "home.scanning": "Scanning…", "home.syncAll": "Sync all", + "home.syncAllToast": "Sync triggered for all games", "home.syncPaused": "Syncing is paused", + "home.title": "Home", "home.trackFolder": "Track folder", "home.trackFolderTitle": "Pick a save folder or file to track", - "home.chooseFolderDialog": "Choose the save folder to track", - "home.syncAllToast": "Sync triggered for all games", - "home.welcome.title": "Welcome to OpenSave", + "home.welcome.hint": "Then open {devices} to pair another PC or Steam Deck, or {cloud} to mirror snapshots online.", "home.welcome.intro": "Keep your game saves in sync across every device — no accounts, no cloud lock-in. Start by finding your saves:", "home.welcome.scanForSaves": "Scan for saves", - "home.welcome.trackManually": "Track a folder manually", - "home.welcome.hint": "Then open {devices} to pair another PC or Steam Deck, or {cloud} to mirror snapshots online.", - "home.devices": "Devices", - "home.cloudBackup": "Cloud Backup" + "home.welcome.title": "Welcome to OpenSave", + "home.welcome.trackManually": "Track a folder manually" } diff --git a/cmd/opensave-app/frontend/src/locales/zh-CN.json b/cmd/opensave-app/frontend/src/locales/zh-CN.json index eb06ba2d..12fbb543 100644 --- a/cmd/opensave-app/frontend/src/locales/zh-CN.json +++ b/cmd/opensave-app/frontend/src/locales/zh-CN.json @@ -1,21 +1,169 @@ { "settings.language": "语言", "settings.systemDefault": "跟随系统", - "home.title": "主页", + "common.cancel": "取消", + "common.nowTracking": "已开始跟踪“{name}”", + "home.addGame.adding": "添加中…", + "home.addGame.changeFolder": "更换文件夹…", + "home.addGame.chooseFile": "选择要跟踪的存档文件", + "home.addGame.name": "名称", + "home.addGame.nameHint": "它显示在你的游戏库中,也会显示在其他设备上。", + "home.addGame.namePlaceholder": "例如:Elden Ring", + "home.addGame.savePath": "存档文件夹或文件", + "home.addGame.startTracking": "开始跟踪", + "home.addGame.title": "跟踪游戏", + "home.addGame.trackFileInstead": "改为跟踪单个文件…", + "home.library.bulkDone": "已为 {n} 个游戏{verb}", + "home.library.bulkPartial": "已为 {done}/{total} 个游戏{verb} — {failed}", + "home.library.clearFilters": "清除筛选", + "home.library.collectionsLabel": "收藏夹", + "home.library.empty": "没有符合条件的游戏。", + "home.library.emptyFor": "没有匹配“{query}”的游戏。", + "home.library.favourite": "收藏", + "home.library.favouriteTitle": "将每个游戏加入收藏", + "home.library.reverseTitle": "倒序排列", + "home.library.reversedTitle": "已倒序 — 恢复原来的顺序", + "home.library.searchLabel": "查找游戏", + "home.library.searchPlaceholder": "查找游戏…", + "home.library.select": "选择", + "home.library.selectAll": "全选({count})", + "home.library.selectedCount": "已选 {n} 个", + "home.library.showAll": "显示全部", + "home.library.showing": "显示 {shown}/{total} 个。", + "home.library.snapshot": "快照", + "home.library.snapshotTitle": "为每个游戏创建快照", + "home.library.sort": "排序", + "home.library.sync": "同步", + "home.library.syncTitle": "将每个游戏与你的设备同步", + "home.library.title": "游戏库", + "home.library.unselectAll": "取消全选", + "home.library.untrackSelected": "取消跟踪所选", + "home.library.verbFavourite": "加入收藏", + "home.library.verbSnapshot": "创建快照", + "home.library.verbSync": "完成同步", + "home.library.view": "视图", + "home.library.viewHint": "也可以在设置 → 常规中调整。", + "home.library.viewLabel": "游戏库视图", + "home.offered.chooseFolder": "选择文件夹…", + "home.offered.decline": "拒绝", + "home.offered.declineToast": "不会再询问你关于 {name} 的事,之后仍可自行跟踪。", + "home.offered.folderPrompt": "在本机上为“{name}”选择文件夹", + "home.offered.intro": "其他设备正在同步这些游戏,但本机还不知道该把存档放在哪里。选择文件夹后才会开始同步。", + "home.offered.pathHint": "在另一台设备上位于 {path}。", + "home.offered.title": "等待选择文件夹", + "home.offered.trackedToast": "{name} 已在本机开始跟踪", + "home.recent.allActivity": "全部动态", + "home.recent.empty": "暂无记录。快照和同步会在发生时显示在这里。", + "home.recent.label": "最近动态", + "home.scan.alreadyTracked": "已跟踪 ({n})", + "home.scan.badResponse": "扫描返回的不是存档列表。请重启 OpenSave 后重新扫描。", + "home.scan.clear": "清除选择", + "home.scan.emptyNoMatches": "没有符合此筛选条件的结果。", + "home.scan.emptyNothingDetected": "未检测到任何存档。你仍可以手动跟踪任意文件夹。", + "home.scan.exclude": "排除", + "home.scan.excludeTitle": "不再在后续扫描中显示此位置", + "home.scan.excludeToast": "“{name}”不会再出现在扫描结果中。", + "home.scan.excluding": "正在排除…", + "home.scan.filterPlaceholder": "按名称或路径筛选…", + "home.scan.folderList.headFound": "已在 {n} 个位置中找到 {name}。", + "home.scan.folderList.headTick": "勾选属于此存档的文件夹——它们会作为一个游戏来跟踪。", + "home.scan.folderList.tagAlt": "另一个副本——可能是旧的安装", + "home.scan.folderList.tagInside": "已包含在上面的文件夹中", + "home.scan.folderList.tagLocation": "同一存档的一部分", + "home.scan.folderList.tagSave": "存档文件夹", + "home.scan.folderList.tagTracked": "已跟踪", + "home.scan.foundInFolders": "在 {n} 个文件夹中找到", + "home.scan.foundSummary": "已找到 {n} 个存档位置——{m} 个可跟踪", + "home.scan.foundSummaryEmpty": "已找到 {n} 个存档位置——{m} 个可跟踪,{e} 个空文件夹已隐藏", + "home.scan.mergedPartialFail": "已跟踪“{name}”,但有 {n} 个文件夹失败:{detail}", + "home.scan.oneSave": "· 其中 {n} 个属于同一存档", + "home.scan.scanningDetail": "正在扫描 Steam、模拟器和已配置的文件夹…", + "home.scan.selectAll": "全选 ({n})", + "home.scan.showEmpty": "显示 {n} 个空文件夹", + "home.scan.showEmptyTitle": "这些文件夹确实存在但里面没有文件——Steam 会为你拥有的每个游戏都建一个,无论存档是否放在其中。完全没有存档的游戏也会出现在这里。", + "home.scan.showTracked": "显示已跟踪", + "home.scan.showTrackedTitle": "同时显示已跟踪的存档", + "home.scan.subScanning": "正在扫描你的系统…", + "home.scan.tab.all": "全部", + "home.scan.tab.emulators": "模拟器", + "home.scan.tab.games": "游戏", + "home.scan.tab.repacks": "硬盘版", + "home.scan.title": "扫描结果", + "home.scan.track": "跟踪", + "home.scan.trackAll": "跟踪全部 {n} 个", + "home.scan.trackAsOne": "作为一个游戏跟踪", + "home.scan.trackAsOneTitle": "将勾选的所有文件夹作为一个游戏跟踪,不考虑扫描给出的分组。适用于扫描未能识别的拆分存档。", + "home.scan.trackPartialFail": "正在跟踪“{name}”——有 {n} 个文件夹未能添加:{detail}", + "home.scan.trackSelected": "跟踪所选 ({n})", + "home.scan.tracked": "已跟踪", + "home.scan.trackedAcrossFolders": "已跟踪“{name}”,共 {n} 个文件夹", + "home.scan.trackedGames": "已跟踪 {n} 个游戏", + "home.scan.trackedGamesWithExtras": "已跟踪 {n} 个游戏,并附加 {m} 个位置", + "home.scan.type.emulator": "模拟器", + "home.scan.type.game": "游戏", + "home.scan.type.repack": "硬盘版", + "home.setup.chooseWhere": "选择位置", + "home.setup.hideTitle": "隐藏此引导", + "home.setup.later": "稍后", + "home.setup.noCloudBackup": "不做云备份", + "home.setup.onlyThisDevice": "暂时只用这台设备", + "home.setup.pairDevice": "配对设备", + "home.setup.pickFolder": "选择文件夹", + "home.setup.progress": "已完成 {n} / 3", + "home.setup.skipped": "已跳过", + "home.setup.stepCloud.text": "把每个快照的副本存到 Google Drive、OneDrive、Dropbox 或你自己的文件夹里。", + "home.setup.stepCloud.title": "备份到云端", + "home.setup.stepDevices.text": "一台电脑、一台 Steam Deck 或装有 OpenSave 的掌机——在同一个 Wi-Fi 下,或通过互联网从任意位置连接。", + "home.setup.stepDevices.title": "添加你的其他设备", + "home.setup.stepSaves.text": "OpenSave 会扫描 Steam、模拟器和常见的存档文件夹,由你选择要跟踪的内容。", + "home.setup.stepSaves.title": "找到你的游戏存档", + "home.setup.title": "开始设置 OpenSave", + "home.summary.cloudBackup": "云备份", + "home.summary.cloudBackupTitle": "打开云备份", + "home.summary.cloudNotSetUp": "未设置", + "home.summary.cloudOff": "已关闭", + "home.summary.cloudOn": "已开启", + "home.summary.devicesOnline": "共 {paired} 台,{online} 台在线", + "home.summary.devicesTitle": "打开设备", + "home.summary.lastSnapshot": "最近快照", + "home.summary.lastSnapshotTitle": "{name} · {when}", + "home.summary.lowSpace": "存放快照的磁盘只剩 {size} 可用空间,新的快照可能存不下。", + "home.summary.nonePaired": "未配对", + "home.summary.noneYet": "暂无", + "home.summary.offline": "离线", + "home.summary.online": "在线", + "home.summary.pausedDetail": "同步已暂停({length})。快照仍会创建,恢复后会自动补齐。", + "home.summary.resumeNow": "立即恢复", + "home.summary.seeSpace": "查看占用情况", + "home.summary.sharedSavingTitle": "磁盘占用比快照自身大小之和少 {size},因为未变动的文件只存一份。打开设置 → 存储", + "home.summary.spaceUsed": "已用空间", + "home.summary.spaceUsedTitle": "打开设置 → 存储", + "home.tile.inFavourites": "已在收藏中", + "home.tile.onBranch": "在 {branch} 上", + "home.tile.played": "已游玩 {length}", + "home.tile.snapshots": "{n} 个快照", + "home.viewOptions.autoFit": "自动排满", + "home.viewOptions.coverStyle": "封面样式", + "home.viewOptions.covers": "封面", + "home.viewOptions.fixedNote": "指定每行数量后,磁贴会平分这一行的宽度。", + "home.viewOptions.overview": "在主页显示最近动态", + "home.viewOptions.tileSize": "磁贴大小", + "home.viewOptions.tilesPerRow": "每行磁贴数", + "home.chooseFolderDialog": "选择要跟踪的存档文件夹", + "home.cloudBackup": "云备份", + "home.devices": "设备", "home.scanSaves": "扫描存档", "home.scanSavesTitle": "查找本机上的游戏存档", "home.scanning": "扫描中…", "home.syncAll": "全部同步", + "home.syncAllToast": "已触发全部游戏的同步", "home.syncPaused": "同步已暂停", + "home.title": "主页", "home.trackFolder": "跟踪文件夹", "home.trackFolderTitle": "选择要跟踪的存档文件夹或文件", - "home.chooseFolderDialog": "选择要跟踪的存档文件夹", - "home.syncAllToast": "已触发全部游戏的同步", - "home.welcome.title": "欢迎使用 OpenSave", + "home.welcome.hint": "然后打开 {devices} 配对另一台电脑或 Steam Deck,或打开 {cloud} 将快照镜像到云端。", "home.welcome.intro": "让你的游戏存档在所有设备间保持同步——无需账号,不被云端绑架。先找到你的存档:", "home.welcome.scanForSaves": "扫描存档", - "home.welcome.trackManually": "手动跟踪文件夹", - "home.welcome.hint": "然后打开 {devices} 配对另一台电脑或 Steam Deck,或打开 {cloud} 将快照镜像到云端。", - "home.devices": "设备", - "home.cloudBackup": "云备份" + "home.welcome.title": "欢迎使用 OpenSave", + "home.welcome.trackManually": "手动跟踪文件夹" } diff --git a/cmd/opensave-app/frontend/src/views/home/AddGameCard.svelte b/cmd/opensave-app/frontend/src/views/home/AddGameCard.svelte index 9b9952b3..8c95beed 100644 --- a/cmd/opensave-app/frontend/src/views/home/AddGameCard.svelte +++ b/cmd/opensave-app/frontend/src/views/home/AddGameCard.svelte @@ -13,6 +13,7 @@ import { navigate, toast } from '../../lib/stores.js'; import { api, native } from '../../lib/api.js'; import { folderName } from '../../lib/filedrop.js'; + import { t } from '../../lib/i18n.js'; const dispatch = createEventDispatcher(); @@ -45,7 +46,7 @@ onMount(() => nameInput?.focus()); async function pickFolder() { - const dir = await native.selectDirectory('Choose the save folder to track'); + const dir = await native.selectDirectory($t('home.chooseFolderDialog')); if (dir) { typed = false; path = dir; @@ -53,7 +54,7 @@ } async function pickFile() { - const file = await native.selectFile('Choose the save file to track'); + const file = await native.selectFile($t('home.addGame.chooseFile')); if (file) { typed = false; path = file; @@ -65,7 +66,7 @@ adding = true; try { const game = await api.post('/api/games', { name: name.trim(), savePath: path }); - toast(`Now tracking "${game.name ?? name}"`, 'success'); + toast($t('common.nowTracking', { name: game.name ?? name }), 'success'); dispatch('close'); navigate('game', { gameId: game.id }); } catch (e) { @@ -76,39 +77,39 @@ } - dispatch('close')}> + dispatch('close')}>
- Save folder or file + {$t('home.addGame.savePath')} {#if canPick}
{path}
- - + +
{:else} {/if}
- + (typed = true)} on:keydown={(e) => e.key === 'Enter' && add()} /> - How it shows in your library, and on your other devices. + {$t('home.addGame.nameHint')}
- +
diff --git a/cmd/opensave-app/frontend/src/views/home/HomeSummary.svelte b/cmd/opensave-app/frontend/src/views/home/HomeSummary.svelte index 5e68ce4f..b2713df2 100644 --- a/cmd/opensave-app/frontend/src/views/home/HomeSummary.svelte +++ b/cmd/opensave-app/frontend/src/views/home/HomeSummary.svelte @@ -24,6 +24,7 @@ import { api } from '../../lib/api.js'; import { timeAgo } from '../../lib/timeago.js'; import { libraryView } from '../../lib/libraryview.js'; + import { t } from '../../lib/i18n.js'; import RecentActivity from './RecentActivity.svelte'; /** [{game, status}] for every tracked game, from the library. */ @@ -71,19 +72,19 @@ $: online = paired.filter((p) => p.status === 'online'); $: devices = paired.length === 0 - ? 'None paired' + ? $t('home.summary.nonePaired') : paired.length === 1 - ? `${paired[0].name} · ${online.length ? 'online' : 'offline'}` - : `${online.length} of ${paired.length} online`; + ? `${paired[0].name} · ${online.length ? $t('home.summary.online') : $t('home.summary.offline')}` + : $t('home.summary.devicesOnline', { online: online.length, paired: paired.length }); // Set up, not just switched on: a fresh install has backup on with no // folder chosen, which backs up nothing. $: cloudOn = !!$settings?.cloudSync?.ready; $: cloud = cloudOn - ? (providerById($settings.cloudSync.provider)?.label ?? 'On') + ? (providerById($settings.cloudSync.provider)?.label ?? $t('home.summary.cloudOn')) : $settings?.cloudSync?.enabled - ? 'Not set up' - : 'Off'; + ? $t('home.summary.cloudNotSetUp') + : $t('home.summary.cloudOff'); $: space = spaceUsed(Object.fromEntries(rows.map((r) => [r.game.id, r.game]))); @@ -100,41 +101,41 @@

{summary.headline}

{#if lowSpace !== null}

- Only {fmtSize(lowSpace)} free on the drive your snapshots are kept on — new ones may not fit. - + {$t('home.summary.lowSpace', { size: fmtSize(lowSpace) })} +

{/if} {#if paused} -

- Syncing is paused {pauseLength($syncPause, now)}. Snapshots are still taken, and everything catches up when it - resumes. -

+

{$t('home.summary.pausedDetail', { length: pauseLength($syncPause, now) })}

{/if} {#if paused} - + {/if}
- - -
- Last snapshot - {newest ? timeAgo(newest.at, now) : 'None yet'} +
+ {$t('home.summary.lastSnapshot')} + {newest ? timeAgo(newest.at, now) : $t('home.summary.noneYet')}
diff --git a/cmd/opensave-app/frontend/src/views/home/LibraryGrid.svelte b/cmd/opensave-app/frontend/src/views/home/LibraryGrid.svelte index e122cd85..66c79d5c 100644 --- a/cmd/opensave-app/frontend/src/views/home/LibraryGrid.svelte +++ b/cmd/opensave-app/frontend/src/views/home/LibraryGrid.svelte @@ -12,6 +12,7 @@ // once (e.g. clear a batch of wrongly-tracked entries) without going in and // out of each one. import { navigate, toast } from '../../lib/stores.js'; + import { t } from '../../lib/i18n.js'; import { api } from '../../lib/api.js'; import Camera from 'lucide-svelte/icons/camera'; import RefreshCw from 'lucide-svelte/icons/refresh-cw'; @@ -146,13 +147,14 @@ } bulkBusy = false; const done = games.length - failed.length; - if (failed.length) toast(`${verb} ${done} of ${games.length} — ${failed.join('; ')}`, 'error'); - else toast(`${verb} ${done} ${done === 1 ? 'game' : 'games'}`, 'success'); + if (failed.length) toast($t('home.library.bulkPartial', { verb, done, total: games.length, failed: failed.join('; ') }), 'error'); + else toast($t('home.library.bulkDone', { verb, n: done }), 'success'); } - const snapshotPicked = () => forEachPicked('Took a snapshot of', (g) => api.post(`/api/games/${g.id}/snapshot`, { comment: '' })); - const syncPicked = () => forEachPicked('Started syncing', (g) => api.post(`/api/games/${g.id}/sync`, {})); + const snapshotPicked = () => + forEachPicked($t('home.library.verbSnapshot'), (g) => api.post(`/api/games/${g.id}/snapshot`, { comment: '' })); + const syncPicked = () => forEachPicked($t('home.library.verbSync'), (g) => api.post(`/api/games/${g.id}/sync`, {})); const favouritePicked = () => - forEachPicked('Added to Favourites:', (g) => api.post('/api/collections/favourites/games', { gameId: g.id, in: true })); + forEachPicked($t('home.library.verbFavourite'), (g) => api.post('/api/collections/favourites/games', { gameId: g.id, in: true })); // "All" is what is on screen: selecting games a filter is hiding, to then // untrack them, would act on things nobody was looking at. $: allSelected = shown.length > 0 && shown.every((r) => libSelected.has(r.game.id)); @@ -176,30 +178,52 @@ />
-

Library

- +

{$t('home.library.title')}

+
{#if selectMode} - {libSelected.size} selected + {$t('home.library.selectedCount', { n: libSelected.size })} - - - - + {:else}
- Tile size + {$t('home.viewOptions.tileSize')}
{#each Object.entries(TILE_SIZES) as [id, label]}
{#if $libraryView.columns !== 'auto'} -

With a set number per row, the tiles share the width between them.

+

{$t('home.viewOptions.fixedNote')}

{/if}
diff --git a/cmd/opensave-app/frontend/src/views/home/OfferedGames.svelte b/cmd/opensave-app/frontend/src/views/home/OfferedGames.svelte index a69986ec..d039a1cb 100644 --- a/cmd/opensave-app/frontend/src/views/home/OfferedGames.svelte +++ b/cmd/opensave-app/frontend/src/views/home/OfferedGames.svelte @@ -9,6 +9,7 @@ import { gameList, toast } from '../../lib/stores.js'; import { withUndo, hiddenKeys } from '../../lib/undo.js'; import { api, native } from '../../lib/api.js'; + import { t, segments } from '../../lib/i18n.js'; let offeredGames = []; async function load() { @@ -24,11 +25,11 @@ $: if ($gameList) load(); async function place(offer) { - const dir = await native.selectDirectory(`Folder for "${offer.name}" on this device`); + const dir = await native.selectDirectory($t('home.offered.folderPrompt', { name: offer.name })); if (!dir) return; try { await api.post(`/api/offered-games/${offer.gameId}/place`, { path: dir }); - toast(`${offer.name} is now tracked here`, 'success'); + toast($t('home.offered.trackedToast', { name: offer.name }), 'success'); await load(); } catch (e) { toast(e.message, 'error'); @@ -37,7 +38,7 @@ function decline(offer) { withUndo({ - message: `You won't be asked about ${offer.name} again. Tracking it yourself later still works.`, + message: $t('home.offered.declineToast', { name: offer.name }), keys: [`offer:${offer.gameId}`], stillThere: () => offeredGames.some((o) => o.gameId === offer.gameId), run: async () => { @@ -51,23 +52,21 @@ {#if shownOffers.length > 0}
-

Waiting for a folder

-

- {shownOffers.length === 1 ? 'Another device syncs this game' : 'Other devices sync these games'}, - but this one doesn't know where to keep - {shownOffers.length === 1 ? 'it' : 'them'} yet. Nothing syncs until you choose. -

+

{$t('home.offered.title')}

+

{$t('home.offered.intro', { n: shownOffers.length })}

{#each shownOffers as offer (offer.gameId + offer.peerId)}
{offer.name} - - Kept at {offer.peerPath} on the other device. - + {#each segments($t('home.offered.pathHint'), { path: offer.peerPath }, ['path']) as part}{#if part.bold}{part.text}{:else}{part.text}{/if}{/each}
- - + +
{/each} diff --git a/cmd/opensave-app/frontend/src/views/home/RecentActivity.svelte b/cmd/opensave-app/frontend/src/views/home/RecentActivity.svelte index faf9af7c..828316a4 100644 --- a/cmd/opensave-app/frontend/src/views/home/RecentActivity.svelte +++ b/cmd/opensave-app/frontend/src/views/home/RecentActivity.svelte @@ -19,6 +19,7 @@ import { gameCover } from '../../lib/api.js'; import { recentEvents } from '../../lib/overview.js'; import { timeAgo } from '../../lib/timeago.js'; + import { t } from '../../lib/i18n.js'; const MOST = 6; const MIN_WIDTH = 210; @@ -43,12 +44,12 @@
- Recent - + {$t('home.recent.label')} +
{#if events.length === 0} -

Nothing yet. Snapshots and syncs show up here as they happen.

+

{$t('home.recent.empty')}

{:else} {#each shown as e (`${e.game.id}:${e.kind}:${e.at}`)}