diff --git a/mac-app/Sources/CloudMachineAgent/CloudMachineAgent.swift b/mac-app/Sources/CloudMachineAgent/CloudMachineAgent.swift index 075f846..65c5daa 100644 --- a/mac-app/Sources/CloudMachineAgent/CloudMachineAgent.swift +++ b/mac-app/Sources/CloudMachineAgent/CloudMachineAgent.swift @@ -21,6 +21,7 @@ struct CloudMachineAgent: AsyncParsableCommand { VMStatusCommand.self, VMUpdateCommand.self, VMGuardCommand.self, + VMEncrypt.self, BackupHealthCommand.self, PrepareShutdown.self, SetLimit.self, diff --git a/mac-app/Sources/CloudMachineAgent/EncryptCommands.swift b/mac-app/Sources/CloudMachineAgent/EncryptCommands.swift new file mode 100644 index 0000000..1ad9407 --- /dev/null +++ b/mac-app/Sources/CloudMachineAgent/EncryptCommands.swift @@ -0,0 +1,106 @@ +import ArgumentParser +import CloudMachineCore +import Foundation + +// Client-side encryption of the bands on Google Drive - see DriveEncryption. + +struct VMEncrypt: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "vm-encrypt", + abstract: L10n.tr( + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain." + ), + subcommands: [ + VMEncryptStatus.self, VMEncryptSetup.self, VMEncryptCopy.self, VMEncryptSwitch.self, + VMEncryptRollback.self, VMEncryptDeletePlain.self, + ], + defaultSubcommand: VMEncryptStatus.self) +} + +private func finish(_ outcome: DriveEncryption.Outcome) throws { + print(outcome.message) + if !outcome.succeeded { throw ExitCode.failure } +} + +struct VMEncryptStatus: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "status", abstract: L10n.tr("Shows which folder the machine uses and the copy.")) + + func run() async throws { + for line in await DriveEncryption.status() { print(line) } + } +} + +struct VMEncryptSetup: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "setup", + abstract: L10n.tr( + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data." + )) + + func run() async throws { + // getpass reads from the terminal without echo; never an argument (ps). + guard let first = getpass(L10n.tr("Encryption password: ")).map({ String(cString: $0) }), + let second = getpass(L10n.tr("Again: ")).map({ String(cString: $0) }) + else { throw ExitCode.failure } + guard first == second else { + print(L10n.tr("The passwords differ - nothing was changed.")) + throw ExitCode.failure + } + try finish(await DriveEncryption.setup(password: first)) + } +} + +struct VMEncryptCopy: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "copy", + abstract: L10n.tr( + "Copies the plain backup into the encrypted folder, in the background; backups go on.")) + + func run() async throws { try finish(await DriveEncryption.copy()) } +} + +struct VMEncryptSwitch: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "switch", + abstract: L10n.tr( + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays." + )) + + func run() async throws { try finish(await DriveEncryption.move(.encrypt)) } +} + +struct VMEncryptRollback: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "rollback", + abstract: L10n.tr( + "The switch the other way: decrypts what changed back into the plain folder and uses it again." + )) + + func run() async throws { try finish(await DriveEncryption.move(.decrypt)) } +} + +struct VMEncryptDeletePlain: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "delete-plain", + abstract: L10n.tr( + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one." + )) + + @Flag( + name: .long, + help: ArgumentHelp( + L10n.tr("The encryption password and salt are kept outside this Mac."))) + var secretsSavedElsewhere = false + + @Flag( + name: .long, + help: ArgumentHelp(L10n.tr("A file was restored from the encrypted backup."))) + var restoreChecked = false + + func run() async throws { + try finish( + await DriveEncryption.deletePlain( + secretsSavedElsewhere: secretsSavedElsewhere, restoreChecked: restoreChecked)) + } +} diff --git a/mac-app/Sources/CloudMachineCore/DriveEncryption.swift b/mac-app/Sources/CloudMachineCore/DriveEncryption.swift new file mode 100644 index 0000000..0b31f0a --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/DriveEncryption.swift @@ -0,0 +1,795 @@ +import Foundation +import Security + +/// Client-side encryption of the bands on Google Drive with rclone `crypt`. +/// +/// Why rclone and not Time Machine's own "Encrypt backups": Time Machine +/// encrypts the disk image itself, and `vm-prepare-bundle` would then have to +/// re-band an encrypted image (`hdiutil convert` of an encrypted sparse +/// bundle answered "Permission denied" on macOS 27, 03.11.2026), and whether +/// Time Machine still finds the password for a converted image cannot be +/// tried without the live destination. `crypt` sits below everything Time +/// Machine sees: the image, Samba and punchfs are unchanged, and an existing +/// backup can be moved under encryption without a new full backup - the bands +/// are copied once, encrypted, next to the plain ones. +/// +/// The move, in order, each step refusing rather than warning: +/// 1. `setup` - the `crypt` remote (`gdrive-crypt`, over +/// `gdrive:CloudMachine/-crypt`), on the Mac and in the machine. +/// 2. `copy` - plain bands and metadata to the encrypted folder, in the +/// background in the machine, while backups go on. +/// 3. `switch` - with Time Machine stopped and nothing left to upload: a last +/// sync, `rclone cryptcheck` of every file, then the machine mounts the +/// encrypted folder. The plain folder stays. +/// 4. `delete-plain` - only after a backup has finished into the encrypted +/// folder and its metadata was copied there; moved to Drive's bin. +/// `rollback` is `switch` the other way (decrypting back) until step 4. +public enum DriveEncryption { + /// The rclone remote of type `crypt`. + public static let cryptRemoteName = "gdrive-crypt" + /// The bands inside it; the metadata copy is `tm-meta` (`VMLayout.metaRemote`). + public static let encryptedRemote = "\(cryptRemoteName):tm" + /// What the marker file holds once the machine mounts the encrypted folder. + static let markerTag = "rclone-crypt" + /// The Keychain service of the encryption password and salt. + public static let keychainService = "cloudmachine-drive-crypt" + public static let passwordAccount = "password" + public static let saltAccount = "salt" + /// The background copy in the machine (`systemd-run`). + static let copyUnit = "cm-encrypt-copy" + static let copyLog = "/var/log/cm-encrypt-copy.log" + + /// `~/Library/Application Support/CloudMachine/drive-encryption`: written by + /// `switch` LAST, after the machine has moved; `VMLayout.remote` follows it. + /// A throwaway machine has its own, so trying `switch` on `cmtest` cannot + /// move what `cmsmb`'s agents use. + static var markerFile: URL { + CMPaths.appSupportDir.appendingPathComponent(markerName(instance: VMLayout.instanceName)) + } + + static func markerName(instance: String) -> String { + instance == "cmsmb" ? "drive-encryption" : "drive-encryption-\(instance)" + } + + /// A throwaway machine must name its own Drive folder: everything here + /// derives from `VMLayout.plainRemote`, and `delete-plain` on `cmtest` + /// with the default folder would delete the live backup. + /// It must also have its own rclone.conf (decision 04.11): `setup` appends + /// the `crypt` section to it, and on the Mac's own file that section would + /// point the live machine's next `vm-setup` at the throwaway folder and + /// block the real `setup`. Its Keychain entry is its own by name + /// (`keychainService(instance:)`), so the live password and salt are never + /// overwritten. + static func throwawayRefusal( + instance: String, remoteOverride: String?, rcloneConfig: String?, defaultConfig: String + ) -> String? { + guard instance != "cmsmb" else { return nil } + guard remoteOverride != nil else { + return L10n.tr( + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.", + instance) + } + func normalized(_ path: String) -> String { + URL(fileURLWithPath: path).standardizedFileURL.resolvingSymlinksInPath().path + } + guard let rcloneConfig, !rcloneConfig.isEmpty, + normalized(rcloneConfig) != normalized(defaultConfig) + else { + return L10n.tr( + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.", + instance, defaultConfig, keychainService(instance: instance)) + } + return nil + } + + static var currentThrowawayRefusal: String? { + throwawayRefusal( + instance: VMLayout.instanceName, remoteOverride: VMLayout.override("CLOUDMACHINE_VM_REMOTE"), + rcloneConfig: ProcessInfo.processInfo.environment["RCLONE_CONFIG"], + defaultConfig: VMLayout.defaultHostRcloneConfig.path) + } + + /// The Keychain service of the password and salt: the live machine's is + /// `cloudmachine-drive-crypt`, a throwaway machine's carries its name. + public static func keychainService(instance: String) -> String { + instance == "cmsmb" ? keychainService : "\(keychainService)-\(instance)" + } + + public static var isEnabled: Bool { marker(in: markerFile) != nil } + /// When the machine moved to the encrypted folder; `nil` = it has not. + public static var switchedAt: Date? { marker(in: markerFile) } + + static func marker(in file: URL) -> Date? { + guard let text = try? String(contentsOf: file, encoding: .utf8) else { return nil } + return marker(text: text) + } + + /// `rclone-crypt `. Anything else is no marker: a half + /// written file must not move the machine to a folder it never copied to. + static func marker(text: String) -> Date? { + let fields = text.split(whereSeparator: \.isWhitespace) + guard fields.count == 2, fields[0] == markerTag, let seconds = TimeInterval(fields[1]) else { + return nil + } + return Date(timeIntervalSince1970: seconds) + } + + static func markerText(switchedAt: Date) -> String { + "\(markerTag) \(Int(switchedAt.timeIntervalSince1970))\n" + } + + /// `gdrive:CloudMachine/-crypt`, what the `crypt` remote wraps (the + /// plain folder with `-crypt` instead of `-tm`). Its own folder: `crypt` + /// cannot list a folder that also holds plain names. + public static var cryptRoot: String { cryptRoot(plain: VMLayout.plainRemote) } + + static func cryptRoot(plain: String) -> String { + (plain.hasSuffix("-tm") ? String(plain.dropLast(3)) : plain) + "-crypt" + } + + /// The section added to `rclone.conf`. Names are encrypted too: band + /// names say little, but folder names would say which Mac this is. + static func configSection(cryptRoot: String, obscuredPassword: String, obscuredSalt: String) + -> String + { + """ + + [\(cryptRemoteName)] + type = crypt + remote = \(cryptRoot) + password = \(obscuredPassword) + password2 = \(obscuredSalt) + filename_encryption = standard + directory_name_encryption = true + + """ + } + + /// Whether `rclone.conf` text already defines the `crypt` remote. + static func hasSection(_ config: String) -> Bool { + config.split(whereSeparator: \.isNewline).contains { + $0.trimmingCharacters(in: .whitespaces) == "[\(cryptRemoteName)]" + } + } + + // MARK: - Moving between the folders (pure, tested) + + /// Which way `switch` moves the bands: `encrypt` (plain -> encrypted) or, + /// for `rollback`, `decrypt`. + public enum Direction: Sendable { + case encrypt, decrypt + } + + public struct Remotes: Equatable, Sendable { + public var plain: String + public var encrypted: String + + public init(plain: String, encrypted: String = DriveEncryption.encryptedRemote) { + self.plain = plain + self.encrypted = encrypted + } + + func source(_ direction: Direction) -> String { + direction == .encrypt ? plain : encrypted + } + func target(_ direction: Direction) -> String { + direction == .encrypt ? encrypted : plain + } + } + + static func rclone(_ args: String) -> String { + "/usr/local/bin/rclone --config \(VMLayout.Guest.rcloneConfig) \(args)" + } + + /// The top of the `crypt` remote, listed with `--crypt-strict-names`: a + /// folder written with another password or salt fails it. Without it a + /// wrong key is silent - `gdrive-crypt:tm` encrypts to another folder name, + /// `sync` uploads everything again next to the real copy and `cryptcheck` + /// agrees with itself. + static var cryptRootListing: String { + rclone("lsf --dirs-only --crypt-strict-names \(VMLayout.shellQuoted(cryptRemoteName + ":"))") + } + + /// The background copy: bands, then metadata. `copy`, not `sync` - it + /// runs while backups go on, and `switch` does the exact last pass. + /// `--drive-stop-on-upload-limit` ends it at Google's daily limit instead of + /// retrying all night; running `copy` again picks up where it stopped. + static func copyScript(_ remotes: Remotes) -> String { + let flags = "--transfers 4 --drive-chunk-size 32M --drive-stop-on-upload-limit" + let p = VMLayout.shellQuoted + return [ + // RemainAfterExit keeps a finished copy visible as `exited`/`success`; + // without it the unit is unloaded and reads like one that never ran. + "systemctl stop \(copyUnit) 2>/dev/null || true", + "systemctl reset-failed \(copyUnit) 2>/dev/null || true", + "systemd-run --unit \(copyUnit) -p RemainAfterExit=yes -p Nice=10 /bin/bash -c " + + p( + "set -euo pipefail; exec >>\(copyLog) 2>&1; date; " + // Empty on the first run (`lsf` fails on a folder that does not + // exist yet); a resumed copy must keep the key. + + rclone("mkdir \(p(cryptRemoteName + ":"))") + "; " + + cryptRootListing + " >/dev/null; " + + rclone("copy \(p(remotes.plain)) \(p(remotes.encrypted)) \(flags)") + "; " + + rclone( + "copy \(p(remotes.plain + "-meta")) \(p(remotes.encrypted + "-meta")) \(flags)") + + "; date; echo CM-COPY-DONE"), + ].joined(separator: "\n") + } + + static let copyStateScript = + "systemctl show -p LoadState -p ActiveState -p SubState -p Result \(copyUnit)" + + /// The background copy, from `copyStateScript`. + public enum CopyState: Equatable, Sendable { + /// No such unit: never started, or the machine restarted since. + case notRun + case running + case done + /// Ended badly; the unit's `Result` (`exit-code`, ...). + case failed(String) + /// Not that output. + case unknown + } + + static func copyState(showOutput: String) -> CopyState { + var fields: [String: String] = [:] + for line in showOutput.split(whereSeparator: \.isNewline) { + let parts = line.split(separator: "=", maxSplits: 1).map(String.init) + if parts.count == 2 { fields[parts[0]] = parts[1] } + } + guard let load = fields["LoadState"], let active = fields["ActiveState"] else { + return .unknown + } + if load == "not-found" { return .notRun } + let result = fields["Result"] ?? "" + if result != "success" { return .failed(result) } + if active == "active", fields["SubState"] == "exited" { return .done } + if active == "active" || active == "activating" { return .running } + return .failed(active) + } + + /// What the crypt root's listing must show once the copy has run: `tm/` + /// and `tm-meta/` for `gdrive-crypt:tm`; for a nested folder only its first + /// component, which holds both. + static func encryptedFolderChecks(_ remotes: Remotes) -> [String] { + let path = remotes.encrypted.split(separator: ":", maxSplits: 1).last.map(String.init) ?? "" + let parts = path.split(separator: "/").map(String.init) + guard let first = parts.first else { return [] } + return parts.count == 1 ? ["\(first)/", "\(first)-meta/"] : ["\(first)/"] + } + + /// `password`, `password2` and `remote` of the `crypt` section. + static func cryptKeys(config: String) -> [String: String] { + var inSection = false + var keys: [String: String] = [:] + for raw in config.split(whereSeparator: \.isNewline) { + let line = raw.trimmingCharacters(in: .whitespaces) + if line.hasPrefix("[") { + inSection = line == "[\(cryptRemoteName)]" + continue + } + guard inSection else { continue } + let parts = line.split(separator: "=", maxSplits: 1).map { + $0.trimmingCharacters(in: .whitespaces) + } + if parts.count == 2, ["password", "password2", "remote"].contains(parts[0]) { + keys[parts[0]] = parts[1] + } + } + return keys + } + + /// The machine must use the Mac's key: `setup` wrote the same section to + /// both, so any difference is an edit or another `setup`. A key wrong from + /// the first copy on is consistent with itself, and only this sees it. + static func keyRefusal(mac: String?, machine: String?) -> String? { + guard let mac, let machine else { + return L10n.tr( + "Could not read the '%@' section on the Mac or in the machine.", cryptRemoteName) + } + let macKeys = cryptKeys(config: mac) + guard macKeys["password"] != nil, macKeys["password2"] != nil, macKeys["remote"] != nil else { + return L10n.tr( + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup", + cryptRemoteName) + } + guard cryptKeys(config: machine) == macKeys else { + return L10n.tr( + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.", + cryptRemoteName) + } + return nil + } + + static let machineSectionScript = + "sed -n '/^\\[\(cryptRemoteName)\\]/,/^\\[/p' \(VMLayout.Guest.rcloneConfig)" + + /// The Mac's and the machine's sections compared, for `copy` and `move`. + static func currentKeyRefusal() async -> String? { + let mac = try? String(contentsOf: VMLayout.hostRcloneConfig, encoding: .utf8) + let machine = try? await VMLayout.rootShell(machineSectionScript, timeout: 60) + return keyRefusal(mac: mac, machine: machine?.succeeded == true ? machine?.stdout : nil) + } + + /// When the listing found folders another key wrote. + static func syncFailureHint(_ output: String, cryptRoot: String) -> String? { + guard output.contains("undecryptable") else { return nil } + return L10n.tr( + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.", + cryptRoot, cryptRoot) + } + + /// The last pass before the machine moves, with Time Machine stopped: sync + /// bands and metadata from `direction`'s source to its target, then + /// `cryptcheck` every file (contents, not just sizes). `set -e`: any + /// difference fails the script, and nothing is moved. + static func finalSyncScript(_ remotes: Remotes, direction: Direction) -> String { + let p = VMLayout.shellQuoted + let flags = "--transfers 8 --drive-chunk-size 32M" + // The copy has run: both folders are there and readable with this key. + var keyCheck = ["listing=$(\(cryptRootListing))"] + for folder in encryptedFolderChecks(remotes) { + keyCheck.append("grep -qx " + p(folder) + " <<<\"$listing\"") + } + let rest = [ + rclone("sync \(p(remotes.source(direction))) \(p(remotes.target(direction))) \(flags)"), + rclone( + "sync \(p(remotes.source(direction) + "-meta")) \(p(remotes.target(direction) + "-meta")) \(flags)" + ), + // cryptcheck takes the plain side first, whichever way the copy went. + rclone("cryptcheck \(p(remotes.plain)) \(p(remotes.encrypted))"), + rclone("cryptcheck \(p(remotes.plain + "-meta")) \(p(remotes.encrypted + "-meta"))"), + "echo CM-CHECKED", + ] + return (["set -euo pipefail"] + keyCheck + rest).joined(separator: "\n") + } + + /// Before anything stops: the machine runs this app's template (the unit + /// names the move restarts) and mounts `oldRemote`, the folder the move + /// starts from. A machine set up by hand, or already moved, is refused. + static func preflightScript(oldRemote: String) -> String { + [ + "set -euo pipefail", + "systemctl cat rclone-tm punchfs tm-bands-bind smbd >/dev/null", + "test -s /etc/cloudmachine/bundle.env", + "grep -qx \(VMLayout.shellQuoted(VMLayout.envLine("CM_REMOTE", oldRemote))) \(VMLayout.Guest.vmEnv)", + "echo CM-PREFLIGHT", + ].joined(separator: "\n") + } + + /// rclone's buffer for a remote: `vfs//` and `vfsMeta/...`. + static func cachePaths(remote: String) -> [String] { + let parts = remote.split(separator: ":", maxSplits: 1).map(String.init) + let name = parts[0] + let path = parts.count > 1 ? parts[1] : "" + return ["vfs", "vfsMeta"].map { + "\(VMLayout.Guest.cacheDir)/\($0)/\(name)" + (path.isEmpty ? "" : "/\(path)") + } + } + + /// Points the machine at `newRemote`. Runs after `finalSyncScript`; Samba + /// was stopped before that sync (`move`), so nothing has written since. + /// + /// The old remote's buffer is deleted: everything in it is on Drive (the + /// queue is checked empty again here, after Samba stopped), and a second + /// 120 GB buffer would not fit the machine's disk. Any failure puts + /// `vm.env` back and starts the old layout again (`trap`). + static func repointScript(oldRemote: String, newRemote: String, stamp: String) -> String { + let env = VMLayout.Guest.vmEnv + let saved = "\(env).before-\(stamp)" + let units = "rclone-tm punchfs tm-bands-bind" + let queue = + "curl -s -m 20 -X POST http://\(VMLayout.Guest.rcAddress)/vfs/queue" + + " | python3 -c 'import json,sys; print(sum(i.get(\"size\",0) for i in json.load(sys.stdin)[\"queue\"]))'" + return [ + // `move` stopped Samba before the last sync; until the way back is + // armed below, any failure must at least start it again. + "set -euo pipefail", + "trap 'systemctl start smbd' ERR", + preflightScript(oldRemote: oldRemote), + "B=$(. /etc/cloudmachine/bundle.env && echo \"/srv/tm-local/$CM_BUNDLE/bands\")", + "before=$(find \"$B/\" -type f | wc -l)", + "test \"$before\" -gt 0", + "systemctl stop smbd", + "unsent=$(\(queue))", + "if [ \"$unsent\" != 0 ]; then systemctl start smbd; echo \"unsent $unsent bytes\" >&2; exit 3; fi", + "cp -p \(env) \(VMLayout.shellQuoted(saved))", + "restore() { cp -p \(VMLayout.shellQuoted(saved)) \(env); systemctl restart \(units); systemctl start smbd; }", + "trap 'echo \"FAILED - the previous folder is back\" >&2; restore' ERR", + "systemctl stop tm-bands-bind punchfs rclone-tm", + "printf '%s\\n' \(VMLayout.shellQuoted(VMLayout.envLine("CM_REMOTE", newRemote))) > \(env)", + "rm -rf " + cachePaths(remote: oldRemote).map(VMLayout.shellQuoted).joined(separator: " "), + "systemctl start rclone-tm punchfs tm-bands-bind", + "mountpoint -q \"$B\"", + "after=$(find \"$B/\" -type f | wc -l)", + "test \"$after\" -eq \"$before\"", + "systemctl start smbd", + "trap - ERR", + "echo CM-REPOINTED $before", + ].joined(separator: "\n") + } + + /// Moves the plain folders to Drive's bin (30 days to change one's mind), + /// not `--drive-use-trash=false` like the machine's own deletes. + static func deletePlainScript(plain: String) -> String { + let p = VMLayout.shellQuoted + return [ + "set -euo pipefail", + rclone("purge --drive-use-trash=true \(p(plain))"), + rclone("purge --drive-use-trash=true \(p(plain + "-meta"))"), + "echo CM-DELETED", + ].joined(separator: "\n") + } + + // MARK: - Gates (pure, tested) + + /// What `switch` and `rollback` check before touching anything. `nil` in a + /// field = not read, which refuses like a bad answer. + public struct SwitchFacts: Sendable { + public var direction: Direction + public var enabled: Bool + public var cryptRemoteInMachine: Bool? + public var timeMachineRunning: Bool? + public var reading: VMLayout.Reading? + public var copyState: CopyState + /// `VMLayout.guardMarker` exists: the guard paused Time Machine and + /// resumes it by itself (`tmutil startbackup`), automatic backups or not. + public var guardHoldsPause: Bool + } + + static func switchRefusal(_ facts: SwitchFacts) -> String? { + switch (facts.direction, facts.enabled) { + case (.encrypt, true): + return L10n.tr("The machine already uses the encrypted folder - nothing to switch.") + case (.decrypt, false): + return L10n.tr("The machine does not use the encrypted folder - nothing to roll back.") + default: break + } + guard facts.cryptRemoteInMachine == true else { + return L10n.tr( + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup", + cryptRemoteName) + } + guard let running = facts.timeMachineRunning else { + return L10n.tr("tmutil did not answer - not switching while Time Machine's state is unknown.") + } + if running { + return L10n.tr( + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again." + ) + } + guard let buffer = facts.reading?.buffer, let unsent = buffer.unsentBytes else { + return L10n.tr("No answer from rclone in the machine - the upload queue is unknown.") + } + if unsent > 0 || buffer.uploadsInProgress > 0 || buffer.erroredFiles > 0 { + return L10n.tr( + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status", + "\(unsent >> 20)", "\(buffer.uploadsInProgress)", "\(buffer.erroredFiles)") + } + if facts.guardHoldsPause { + return L10n.tr( + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done." + ) + } + // The copy must have run and ended well; the last sync then moves only + // what changed since. Without it that sync is the whole backup, run with + // Time Machine off for hours. + if facts.direction == .encrypt, facts.copyState != .done { + return L10n.tr( + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.", + "\(facts.copyState)") + } + return nil + } + + /// What `delete-plain` checks. The plain folder is the one backup known + /// to be good until the encrypted one has proved itself. + public struct DeleteFacts: Sendable { + public var switchedAt: Date? + public var lastBackup: Date? + public var lastMetaSync: Date? + public var reading: VMLayout.Reading? + public var secretsSavedElsewhere: Bool + public var restoreChecked: Bool + } + + static func deleteRefusal(_ facts: DeleteFacts) -> String? { + guard let switchedAt = facts.switchedAt else { + return L10n.tr( + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it." + ) + } + guard facts.secretsSavedElsewhere else { + return L10n.tr( + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere." + ) + } + guard facts.restoreChecked else { + return L10n.tr( + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked." + ) + } + guard let lastBackup = facts.lastBackup else { + return L10n.tr( + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting." + ) + } + if lastBackup <= switchedAt { + return L10n.tr( + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.", + BackupHealth.stamp(lastBackup), BackupHealth.stamp(switchedAt)) + } + guard let meta = facts.lastMetaSync, meta > lastBackup else { + return L10n.tr( + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting." + ) + } + guard let buffer = facts.reading?.buffer, buffer.unsentBytes == 0, + buffer.uploadsInProgress == 0, buffer.erroredFiles == 0 + else { + return L10n.tr( + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting." + ) + } + return nil + } + + // MARK: - Steps + + public struct Outcome: Sendable { + public var succeeded: Bool + public var message: String + } + + static func fail(_ message: String) -> Outcome { + CMLogger.log("vm-encrypt: \(message.replacingOccurrences(of: "\n", with: " "))") + return Outcome(succeeded: false, message: message) + } + + /// A random salt: 32 bytes, base64. + static func randomSalt() -> String? { + var bytes = [UInt8](repeating: 0, count: 32) + guard SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) == errSecSuccess else { + return nil + } + return Data(bytes).base64EncodedString() + } + + /// `rclone obscure`, the secret through the environment (not `ps`). + static func obscure(_ secret: String) async -> String? { + let result = try? await ProcessRunner.run( + "/bin/sh", ["-c", "printf '%s' \"$CM_SECRET\" | \"$CM_RCLONE\" obscure -"], + env: ["CM_SECRET": secret, "CM_RCLONE": CMTooling.managedRclonePath.path], timeout: 30) + guard let result, result.succeeded else { return nil } + let value = result.stdout.trimmingCharacters(in: .whitespacesAndNewlines) + return value.isEmpty ? nil : value + } + + /// Step 1. Adds the `crypt` remote to rclone.conf on the Mac and in the + /// machine, and keeps the password and salt in the Keychain. Moves no data. + /// Refuses if the remote exists anywhere: a second password would make the + /// first copy unreadable. + public static func setup(password: String) async -> Outcome { + if let refusal = currentThrowawayRefusal { return fail(refusal) } + guard password.count >= 12 else { + return fail(L10n.tr("The password must have at least 12 characters.")) + } + let hostConfig = VMLayout.hostRcloneConfig + guard let existing = try? String(contentsOf: hostConfig, encoding: .utf8) else { + return fail(L10n.tr("Could not read %@.", hostConfig.path)) + } + if hasSection(existing) { + return fail( + L10n.tr( + "'%@' is already in %@ - not replacing its password.", cryptRemoteName, hostConfig.path)) + } + guard + let inMachine = try? await VMLayout.rootShell( + "grep -qx '\\[\(cryptRemoteName)\\]' \(VMLayout.Guest.rcloneConfig) && echo YES || echo NO", + timeout: 60), inMachine.succeeded + else { + return fail( + L10n.tr("The machine does not answer - is it running? cloudmachine-agent vm-status")) + } + if inMachine.stdout.contains("YES") { + return fail( + L10n.tr( + "'%@' is already in the machine's rclone.conf - not replacing its password.", + cryptRemoteName)) + } + guard let salt = randomSalt(), let obscuredPassword = await obscure(password), + let obscuredSalt = await obscure(salt) + else { return fail(L10n.tr("rclone obscure failed.")) } + let section = configSection( + cryptRoot: cryptRoot, obscuredPassword: obscuredPassword, + obscuredSalt: obscuredSalt) + + // The Keychain first: a remote whose password exists nowhere else is + // the one mistake here that cannot be undone. + let service = keychainService(instance: VMLayout.instanceName) + do { + try await KeychainStore.save(password, account: passwordAccount, service: service) + try await KeychainStore.save(salt, account: saltAccount, service: service) + } catch { + return fail(error.localizedDescription) + } + let staging = VMLayout.hostDir.appendingPathComponent("crypt-section-\(UUID().uuidString)") + defer { try? FileManager.default.removeItem(at: staging) } + guard + FileManager.default.createFile( + atPath: staging.path, contents: Data(section.utf8), attributes: [.posixPermissions: 0o600]) + else { return fail(L10n.tr("Could not write %@.", staging.path)) } + let remoteStaging = "/tmp/cm-crypt-\(UUID().uuidString)" + guard + let copied = try? await VMLayout.limactl( + ["copy", staging.path, "\(VMLayout.instanceName):\(remoteStaging)"], timeout: 120), + copied.succeeded, + let appended = try? await VMLayout.rootShell( + "set -e; cat \(remoteStaging) >> \(VMLayout.Guest.rcloneConfig); rm -f \(remoteStaging)", + timeout: 60), appended.succeeded + else { return fail(L10n.tr("Could not add the remote to the machine's rclone.conf.")) } + do { + let handle = try FileHandle(forWritingTo: hostConfig) + try handle.seekToEnd() + try handle.write(contentsOf: Data(section.utf8)) + try handle.close() + } catch { + return fail( + L10n.tr("Could not add the remote to %@: %@", hostConfig.path, error.localizedDescription)) + } + CMLogger.log( + "vm-encrypt: setup - \(cryptRemoteName) over \(cryptRoot)") + return Outcome( + succeeded: true, + message: L10n.tr( + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy", + cryptRemoteName, cryptRoot, service, salt)) + } + + static var remotes: Remotes { Remotes(plain: VMLayout.plainRemote) } + + /// Step 2. Starts the background copy in the machine and returns. + public static func copy() async -> Outcome { + if let refusal = currentThrowawayRefusal { return fail(refusal) } + if isEnabled { return fail(L10n.tr("The machine already uses the encrypted folder.")) } + if let refusal = await currentKeyRefusal() { return fail(refusal) } + let shown = try? await VMLayout.rootShell(copyStateScript, timeout: 60) + if shown.map({ copyState(showOutput: $0.stdout) }) == .running { + return fail( + L10n.tr("The copy is already running - see: cloudmachine-agent vm-encrypt status")) + } + let started = try? await VMLayout.rootShell(copyScript(remotes), timeout: 120) + guard started?.succeeded == true else { + return fail(L10n.tr("Could not start the copy: %@", VMLayout.errorTail(started))) + } + return Outcome( + succeeded: true, + message: L10n.tr( + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status", + copyUnit, copyLog)) + } + + /// Steps 3 and `rollback`. + public static func move(_ direction: Direction) async -> Outcome { + if let refusal = currentThrowawayRefusal { return fail(refusal) } + let inMachine = try? await VMLayout.rootShell( + "grep -qx '\\[\(cryptRemoteName)\\]' \(VMLayout.Guest.rcloneConfig) && echo YES || echo NO", + timeout: 60) + let shown = try? await VMLayout.rootShell(copyStateScript, timeout: 60) + let facts = SwitchFacts( + direction: direction, enabled: isEnabled, + cryptRemoteInMachine: inMachine?.succeeded == true + ? inMachine?.stdout.contains("YES") : nil, + timeMachineRunning: await TimeMachineStatus.runningState(), + reading: await VMLayout.currentReading(), + copyState: shown?.succeeded == true ? copyState(showOutput: shown!.stdout) : .unknown, + guardHoldsPause: FileManager.default.fileExists(atPath: VMLayout.guardMarker.path)) + if let refusal = switchRefusal(facts) { return fail(refusal) } + if let refusal = await currentKeyRefusal() { return fail(refusal) } + let old = direction == .encrypt ? remotes.plain : remotes.encrypted + let new = direction == .encrypt ? remotes.encrypted : remotes.plain + let preflight = try? await VMLayout.rootShell(preflightScript(oldRemote: old), timeout: 60) + guard preflight?.stdout.contains("CM-PREFLIGHT") == true else { + return fail( + L10n.tr( + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@", + old, VMLayout.errorTail(preflight))) + } + + // Samba off BEFORE the last sync: a backup started now (by hand, by the + // guard) cannot write a band that the sync has already passed. + guard (try? await VMLayout.rootShell("systemctl stop smbd", timeout: 120))?.succeeded == true + else { return fail(L10n.tr("Could not stop smbd in the machine.")) } + CMLogger.log("vm-encrypt: \(direction) - last sync and cryptcheck") + let synced = try? await VMLayout.rootShell( + finalSyncScript(remotes, direction: direction), timeout: 6 * 3600) + guard synced?.succeeded == true, synced?.stdout.contains("CM-CHECKED") == true else { + _ = try? await VMLayout.rootShell("systemctl start smbd", timeout: 120) + let tail = VMLayout.errorTail(synced) + let hint = synced.flatMap { syncFailureHint($0.stderr + $0.stdout, cryptRoot: cryptRoot) } + return fail( + L10n.tr("The last sync or cryptcheck failed - the machine was NOT moved: %@", tail) + + (hint.map { "\n" + $0 } ?? "")) + } + let moved = try? await VMLayout.rootShell( + repointScript(oldRemote: old, newRemote: new, stamp: VMBundlePrep.stamp()), timeout: 1800) + guard moved?.succeeded == true, moved?.stdout.contains("CM-REPOINTED") == true else { + // The script starts Samba on its own failures; not if it never ran + // to the end (no answer from limactl). Idempotent either way. + _ = try? await VMLayout.rootShell("systemctl start smbd", timeout: 120) + return fail( + L10n.tr( + "Moving the machine failed; it went back to %@: %@", old, VMLayout.errorTail(moved))) + } + do { + if direction == .encrypt { + try FileManager.default.createDirectory( + at: markerFile.deletingLastPathComponent(), withIntermediateDirectories: true) + try markerText(switchedAt: Date()).write(to: markerFile, atomically: true, encoding: .utf8) + } else { + try FileManager.default.removeItem(at: markerFile) + } + } catch { + return fail( + L10n.tr( + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.", + new, markerFile.path, error.localizedDescription)) + } + CMLogger.log("vm-encrypt: \(direction) done - machine on \(new)") + return Outcome( + succeeded: true, + message: L10n.tr( + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable", + new, old)) + } + + /// Step 4. + public static func deletePlain(secretsSavedElsewhere: Bool, restoreChecked: Bool) async + -> Outcome + { + if let refusal = currentThrowawayRefusal { return fail(refusal) } + let plist = BackupHealth.loadPreferences(BackupHealth.preferencesPath) + let meta = try? await VMLayout.rootShell( + "stat -c %Y \(VMLayout.Guest.metaSyncStamp)", timeout: 60) + let facts = DeleteFacts( + switchedAt: switchedAt, + lastBackup: plist.flatMap { BackupHealth.dates(inPreferences: $0).lastSuccess }, + lastMetaSync: meta.flatMap { $0.succeeded ? metaSyncDate($0.stdout) : nil }, + reading: await VMLayout.currentReading(), + secretsSavedElsewhere: secretsSavedElsewhere, restoreChecked: restoreChecked) + if let refusal = deleteRefusal(facts) { return fail(refusal) } + let deleted = try? await VMLayout.rootShell( + deletePlainScript(plain: VMLayout.plainRemote), timeout: 3600) + guard deleted?.succeeded == true, deleted?.stdout.contains("CM-DELETED") == true else { + return fail(L10n.tr("Deleting the plain folder failed: %@", VMLayout.errorTail(deleted))) + } + return Outcome( + succeeded: true, + message: L10n.tr( + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).", + VMLayout.plainRemote)) + } + + /// `stat -c %Y` of the metadata stamp. + static func metaSyncDate(_ statOutput: String) -> Date? { + TimeInterval(statOutput.trimmingCharacters(in: .whitespacesAndNewlines)) + .map(Date.init(timeIntervalSince1970:)) + } + + /// Read-only: where things stand. + public static func status() async -> [String] { + var out = [ + L10n.tr("Machine uses: %@", isEnabled ? encryptedRemote : VMLayout.plainRemote), + L10n.tr("Encrypted folder: %@", cryptRoot), + ] + if let switchedAt { + out.append(L10n.tr("Switched: %@", BackupHealth.stamp(switchedAt))) + } + let shown = try? await VMLayout.rootShell(copyStateScript, timeout: 60) + let state = shown?.succeeded == true ? copyState(showOutput: shown!.stdout) : .unknown + out.append(L10n.tr("Copy: %@", "\(state)")) + let tail = try? await VMLayout.rootShell("tail -n 3 \(copyLog) 2>/dev/null", timeout: 60) + if let text = tail?.stdout, !text.isEmpty { out.append(text.trimmingCharacters(in: .newlines)) } + return out + } +} diff --git a/mac-app/Sources/CloudMachineCore/L10n.swift b/mac-app/Sources/CloudMachineCore/L10n.swift index 34709a5..efd42f2 100644 --- a/mac-app/Sources/CloudMachineCore/L10n.swift +++ b/mac-app/Sources/CloudMachineCore/L10n.swift @@ -75,28 +75,36 @@ public enum L10n { /// Every language except English (the keys themselves). static let translations: [Language: [String: String]] = [ .pl: polish, - .ar: L10nTranslations.ar, - .da: L10nTranslations.da, - .de: L10nTranslations.de, - .es: L10nTranslations.es, - .fi: L10nTranslations.fi, - .fr: L10nTranslations.fr, - .he: L10nTranslations.he, - .it: L10nTranslations.it, - .ja: L10nTranslations.ja, - .ko: L10nTranslations.ko, - .ms: L10nTranslations.ms, - .nb: L10nTranslations.nb, - .nl: L10nTranslations.nl, - .ptBR: L10nTranslations.ptBR, - .ru: L10nTranslations.ru, - .sv: L10nTranslations.sv, - .th: L10nTranslations.th, - .tr: L10nTranslations.tr, - .zhHans: L10nTranslations.zhHans, - .zhHant: L10nTranslations.zhHant, + .ar: merged(L10nTranslations.ar, L10nTranslations.arEncryption), + .da: merged(L10nTranslations.da, L10nTranslations.daEncryption), + .de: merged(L10nTranslations.de, L10nTranslations.deEncryption), + .es: merged(L10nTranslations.es, L10nTranslations.esEncryption), + .fi: merged(L10nTranslations.fi, L10nTranslations.fiEncryption), + .fr: merged(L10nTranslations.fr, L10nTranslations.frEncryption), + .he: merged(L10nTranslations.he, L10nTranslations.heEncryption), + .it: merged(L10nTranslations.it, L10nTranslations.itEncryption), + .ja: merged(L10nTranslations.ja, L10nTranslations.jaEncryption), + .ko: merged(L10nTranslations.ko, L10nTranslations.koEncryption), + .ms: merged(L10nTranslations.ms, L10nTranslations.msEncryption), + .nb: merged(L10nTranslations.nb, L10nTranslations.nbEncryption), + .nl: merged(L10nTranslations.nl, L10nTranslations.nlEncryption), + .ptBR: merged(L10nTranslations.ptBR, L10nTranslations.ptBREncryption), + .ru: merged(L10nTranslations.ru, L10nTranslations.ruEncryption), + .sv: merged(L10nTranslations.sv, L10nTranslations.svEncryption), + .th: merged(L10nTranslations.th, L10nTranslations.thEncryption), + .tr: merged(L10nTranslations.tr, L10nTranslations.trEncryption), + .zhHans: merged(L10nTranslations.zhHans, L10nTranslations.zhHansEncryption), + .zhHant: merged(L10nTranslations.zhHant, L10nTranslations.zhHantEncryption), ] + /// A language's main table and its vm-encrypt table + /// (`L10nTranslations+Encryption-.swift`). The tables share no key. + private static func merged(_ main: [String: String], _ part: [String: String]) + -> [String: String] + { + main.merging(part) { first, _ in first } + } + /// All Polish tables merged. Split into files so that parts of the code can /// be translated in parallel without editing one shared dictionary. static let polish: [String: String] = { @@ -115,6 +123,7 @@ public enum L10n { L10nPolish.safety, L10nPolish.vm, L10nPolish.machine, + L10nPolish.encryption, ] } diff --git a/mac-app/Sources/CloudMachineCore/L10nPolish+Encryption.swift b/mac-app/Sources/CloudMachineCore/L10nPolish+Encryption.swift new file mode 100644 index 0000000..4822769 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nPolish+Encryption.swift @@ -0,0 +1,122 @@ +// Polish translations, keyed by the English text passed to `L10n.tr`. +// Encryption of the backup on Google Drive: vm-encrypt. +extension L10nPolish { + static let encryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ wraz z metadanymi jest w koszu Google Drive (trzymany 30 dni, nadal liczony do limitu miejsca).", + "'%@' is already in %@ - not replacing its password.": + "'%@' już jest w %@ - nie podmieniam jego hasła.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' już jest w rclone.conf maszyny - nie podmieniam jego hasła.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ zawiera foldery, których ten klucz nie odczyta: inne hasło albo sól w maszynie albo resztki wcześniejszej próby. Porównaj rclone.conf maszyny z tym na Macu; resztki trzeba usunąć z %@ ręcznie.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' jest niepełny albo go brak w rclone.conf na Macu - uruchom: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ potrzebuje własnego rclone.conf - ustaw RCLONE_CONFIG na kopię, nie %@. Jej wpis w pęku kluczy to usługa %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "Nie udało się odczytać sekcji '%@' na Macu albo w maszynie.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "'%@' w maszynie (hasło, sól albo folder) różni się od tego na Macu - kopię dałoby się odczytać tylko kluczem maszyny. Niczego nie przenoszę.", + "A file was restored from the encrypted backup.": + "Plik został odtworzony z zaszyfrowanej kopii.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "Dodano '%@' (%@) na Macu i w maszynie; hasło i sól są w pęku kluczy (usługa %@).\nSÓL: %@\nZapisz teraz hasło ORAZ tę sól poza tym Makiem - bez obu zaszyfrowanej kopii nie da się odczytać. Dalej: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Dodaje zaszyfrowany remote na Macu i w maszynie (pyta o hasło). Nie przenosi danych.", + "Again: ": + "Jeszcze raz: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Kopiuje niezaszyfrowaną kopię do zaszyfrowanego folderu, w tle; kopie trwają dalej.", + "Copy: %@": + "Kopiowanie: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ bez CLOUDMACHINE_VM_REMOTE użyłby folderu działającej kopii - ustaw oba.", + "Could not add the remote to %@: %@": + "Nie udało się dodać remote do %@: %@", + "Could not add the remote to the machine's rclone.conf.": + "Nie udało się dodać remote do rclone.conf maszyny.", + "Could not read %@.": + "Nie udało się odczytać %@.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Nie udało się odczytać, kiedy Time Machine ostatnio skończył kopię (pełny dostęp do dysku?) - nie usuwam.", + "Could not start the copy: %@": + "Nie udało się uruchomić kopiowania: %@", + "Could not write %@.": + "Nie udało się zapisać %@.", + "Deleting the plain folder failed: %@": + "Usuwanie niezaszyfrowanego folderu nie powiodło się: %@", + "Encrypted folder: %@": + "Folder szyfrowany: %@", + "Encryption password: ": + "Hasło szyfrowania: ", + "Machine uses: %@": + "Maszyna używa: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Przenosi kopię na Google Drive pod szyfrowanie rclone: setup, copy, switch, potem delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Przenosi niezaszyfrowany folder do kosza Google Drive - dopiero gdy kopia skończyła się do zaszyfrowanego.", + "Moving the machine failed; it went back to %@: %@": + "Przełączenie maszyny nie powiodło się; wróciła do %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Brak odpowiedzi rclone w maszynie - kolejka wysyłki nieznana.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Żadna kopia nie skończyła się jeszcze do zaszyfrowanego folderu (ostatnia %@, przełączenie %@) - nie usuwam.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Nie wszystko jest jeszcze na Google Drive (%@ MB niewysłane, %@ w wysyłce, %@ z błędem). Poczekaj na koniec wysyłki: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Najpierw odtwórz plik z zaszyfrowanej kopii (Time Machine, Przeglądaj), potem potwierdź przez --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Pokazuje, którego folderu używa maszyna, i stan kopiowania.", + "Switched: %@": + "Przełączono: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "Kopiowanie już trwa - zobacz: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "Kopiowanie trwa w maszynie (%@, log %@). Kopie w tym czasie trwają dalej. Sprawdzaj: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "Kopiowanie do zaszyfrowanego folderu nie skończyło się dobrze (%@) - uruchom: cloudmachine-agent vm-encrypt copy i poczekaj na nie.", + "The encryption password and salt are kept outside this Mac.": + "Hasło szyfrowania i sól są zapisane poza tym Makiem.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "Strażnik trzyma Time Machine wstrzymany i wznowiłby go w trakcie przełączania. Poczekaj, aż wznowi, a wysyłka się skończy.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "Metadane obrazu nie zostały skopiowane do zaszyfrowanego folderu od ostatniej kopii - nie usuwam.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "Ostatnia synchronizacja albo cryptcheck nie powiodły się - maszyna NIE została przełączona: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "Maszyna już używa zaszyfrowanego folderu - nie ma czego przełączać.", + "The machine already uses the encrypted folder.": + "Maszyna już używa zaszyfrowanego folderu.", + "The machine does not use the encrypted folder - nothing to roll back.": + "Maszyna nie używa zaszyfrowanego folderu - nie ma czego cofać.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "Maszyna nie używa zaszyfrowanego folderu - kopią jest folder niezaszyfrowany. Nie usuwam go.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "Maszyna nie ma remote '%@' w rclone.conf - uruchom: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "Maszyna nie jest na szablonie tej aplikacji albo nie używa %@ (najpierw cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "Maszyna używa teraz %@, ale nie udało się zaktualizować %@ (%@) - popraw to, zanim cokolwiek uruchomi vm-setup.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "Maszyna używa teraz %@. %@ zostaje. Włącz z powrotem automatyczne kopie: sudo tmutil enable", + "The password must have at least 12 characters.": + "Hasło musi mieć co najmniej 12 znaków.", + "The passwords differ - nothing was changed.": + "Hasła się różnią - niczego nie zmieniono.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "Przełączenie w drugą stronę: odszyfrowuje zmiany z powrotem do niezaszyfrowanego folderu i znów go używa.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "Wysyłka do zaszyfrowanego folderu się nie skończyła (albo rclone nie odpowiedział) - nie usuwam.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine robi kopię. Wyłącz automatyczne kopie (sudo tmutil disable), zatrzymaj ją (tmutil stopbackup) i uruchom to ponownie.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Przy zatrzymanym Time Machine: ostatnia synchronizacja, cryptcheck, potem maszyna używa zaszyfrowanego folderu. Niezaszyfrowany zostaje.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Bez hasła szyfrowania i soli zaszyfrowanej kopii nie da się odczytać. Zapisz oba poza tym Makiem (menedżer haseł, papier), potem potwierdź przez --secrets-saved-elsewhere.", + "rclone obscure failed.": + "rclone obscure nie powiodło się.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil nie odpowiedział - nie przełączam, dopóki stan Time Machine jest nieznany.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ar.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ar.swift new file mode 100644 index 0000000..85a0ae4 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ar.swift @@ -0,0 +1,121 @@ +// Arabic translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let arEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ وبيانات تعريفه في سلة مهملات Google Drive (تُحفظ 30 يومًا وتظل محسوبة ضمن الحصة).", + "'%@' is already in %@ - not replacing its password.": + "'%@' موجود بالفعل في %@ - لن تُستبدل كلمة مروره.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' موجود بالفعل في rclone.conf الخاص بالجهاز الافتراضي - لن تُستبدل كلمة مروره.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "يحتوي %@ على مجلدات لا يستطيع هذا المفتاح قراءتها: كلمة مرور أخرى أو ملح آخر في الجهاز الافتراضي، أو بقايا محاولة سابقة. قارن rclone.conf الخاص بالجهاز الافتراضي بالموجود على الـ Mac؛ يجب حذف البقايا من %@ يدويًا.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' غير مكتمل أو غير موجود في rclone.conf على الـ Mac - شغّل: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "يحتاج CLOUDMACHINE_VM_INSTANCE=%@ إلى ملف rclone.conf خاص به - اضبط RCLONE_CONFIG على نسخة، وليس على %@. إدخاله في سلسلة المفاتيح هو الخدمة %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "تعذرت قراءة القسم '%@' على الـ Mac أو في الجهاز الافتراضي.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "'%@' في الجهاز الافتراضي (كلمة المرور أو الملح أو المجلد) يختلف عن الموجود على الـ Mac - لن تُقرأ النسخة إلا بمفتاح الجهاز الافتراضي. لن يُنقل أي شيء.", + "A file was restored from the encrypted backup.": + "تمت استعادة ملف من النسخة الاحتياطية المشفرة.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "تمت إضافة '%@' (%@) على الـ Mac وفي الجهاز الافتراضي؛ كلمة المرور والملح في سلسلة المفاتيح (الخدمة %@).\nالملح: %@\nاحفظ الآن كلمة المرور وهذا الملح خارج هذا الـ Mac - فبدونهما لا يمكن قراءة النسخة الاحتياطية المشفرة. التالي: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "يضيف الاتصال البعيد المشفر على الـ Mac وفي الجهاز الافتراضي (يطلب كلمة المرور). لا ينقل أي بيانات.", + "Again: ": + "مرة أخرى: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "ينسخ النسخة الاحتياطية غير المشفرة إلى المجلد المشفر في الخلفية؛ وتستمر النسخ الاحتياطية.", + "Copy: %@": + "النسخ: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "سيستخدم CLOUDMACHINE_VM_INSTANCE=%@ بدون CLOUDMACHINE_VM_REMOTE مجلد النسخة الاحتياطية الحالية - اضبط الاثنين.", + "Could not add the remote to %@: %@": + "تعذرت إضافة الاتصال البعيد إلى %@: %@", + "Could not add the remote to the machine's rclone.conf.": + "تعذرت إضافة الاتصال البعيد إلى rclone.conf الخاص بالجهاز الافتراضي.", + "Could not read %@.": + "تعذرت قراءة %@.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "تعذر معرفة متى أنهى Time Machine آخر نسخة احتياطية (الوصول الكامل إلى القرص؟) - لن يُحذف شيء.", + "Could not start the copy: %@": + "تعذر بدء النسخ: %@", + "Could not write %@.": + "تعذرت كتابة %@.", + "Deleting the plain folder failed: %@": + "فشل حذف المجلد غير المشفر: %@", + "Encrypted folder: %@": + "المجلد المشفر: %@", + "Encryption password: ": + "كلمة مرور التشفير: ", + "Machine uses: %@": + "الجهاز يستخدم: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "ينقل النسخة الاحتياطية على Google Drive إلى تشفير rclone: setup ثم copy ثم switch ثم delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "ينقل المجلد غير المشفر إلى سلة مهملات Google Drive - فقط بعد اكتمال نسخة احتياطية في المجلد المشفر.", + "Moving the machine failed; it went back to %@: %@": + "فشل تحويل الجهاز الافتراضي؛ وعاد إلى %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "لا رد من rclone في الجهاز الافتراضي - قائمة انتظار الرفع غير معروفة.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "لم تكتمل أي نسخة احتياطية في المجلد المشفر بعد (الأخيرة %@، التحويل %@) - لن يُحذف شيء.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "ليس كل شيء على Google Drive بعد (%@ MB لم تُرسل، %@ قيد الرفع، %@ فشلت). انتظر حتى ينتهي الرفع: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "استعد أولًا ملفًا من النسخة الاحتياطية المشفرة (Time Machine، تصفح)، ثم أكّد باستخدام --restore-checked.", + "Shows which folder the machine uses and the copy.": + "يعرض المجلد الذي يستخدمه الجهاز الافتراضي وحالة النسخ.", + "Switched: %@": + "التحويل: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "النسخ قيد التشغيل بالفعل - راجع: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "يعمل النسخ في الجهاز الافتراضي (%@، السجل %@). تستمر النسخ الاحتياطية في أثناء ذلك. للتحقق: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "لم ينته النسخ إلى المجلد المشفر بنجاح (%@) - شغّل: cloudmachine-agent vm-encrypt copy وانتظره.", + "The encryption password and salt are kept outside this Mac.": + "كلمة مرور التشفير والملح محفوظان خارج هذا الـ Mac.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "يُبقي حارس المخزن المؤقت Time Machine متوقفًا مؤقتًا وقد يستأنفه أثناء التحويل. انتظر حتى يُستأنف وينتهي الرفع.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "لم تُنسخ بيانات تعريف الصورة إلى المجلد المشفر منذ آخر نسخة احتياطية - لن يُحذف شيء.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "فشلت المزامنة الأخيرة أو cryptcheck - لم يُحوَّل الجهاز الافتراضي: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "يستخدم الجهاز الافتراضي المجلد المشفر بالفعل - لا شيء للتحويل.", + "The machine already uses the encrypted folder.": + "يستخدم الجهاز الافتراضي المجلد المشفر بالفعل.", + "The machine does not use the encrypted folder - nothing to roll back.": + "لا يستخدم الجهاز الافتراضي المجلد المشفر - لا شيء للتراجع عنه.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "لا يستخدم الجهاز الافتراضي المجلد المشفر - المجلد غير المشفر هو النسخة الاحتياطية. لن يُحذف.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "لا يوجد اتصال بعيد '%@' في rclone.conf الخاص بالجهاز الافتراضي - شغّل: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "الجهاز الافتراضي لا يعمل بقالب هذا التطبيق أو لا يستخدم %@ (شغّل أولًا cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "يستخدم الجهاز الافتراضي الآن %@، لكن تعذر تحديث %@ (%@) - أصلحه قبل أن يشغّل أي شيء آخر الأمر vm-setup.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "يستخدم الجهاز الافتراضي الآن %@. يُحتفظ بـ %@. أعد تفعيل النسخ الاحتياطية التلقائية: sudo tmutil enable", + "The password must have at least 12 characters.": + "يجب أن تتكون كلمة المرور من 12 حرفًا على الأقل.", + "The passwords differ - nothing was changed.": + "كلمتا المرور مختلفتان - لم يتغير شيء.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "التحويل في الاتجاه المعاكس: يفك تشفير ما تغيّر ويعيده إلى المجلد غير المشفر ويستخدمه مجددًا.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "لم ينته الرفع إلى المجلد المشفر (أو لم يرد rclone) - لن يُحذف شيء.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "يُجري Time Machine نسخًا احتياطيًا. عطّل النسخ الاحتياطية التلقائية (sudo tmutil disable)، وأوقف النسخ الجاري (tmutil stopbackup)، ثم شغّل هذا مجددًا.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "مع إيقاف Time Machine: مزامنة أخيرة ثم cryptcheck ثم يستخدم الجهاز الافتراضي المجلد المشفر. يبقى المجلد غير المشفر.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "بدون كلمة مرور التشفير والملح لا يمكن قراءة النسخة الاحتياطية المشفرة. احفظ الاثنين خارج هذا الـ Mac (مدير كلمات مرور، ورق)، ثم أكّد باستخدام --secrets-saved-elsewhere.", + "rclone obscure failed.": + "فشل rclone obscure.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "لم يرد tmutil - لن يتم التحويل ما دامت حالة Time Machine غير معروفة.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-da.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-da.swift new file mode 100644 index 0000000..62d685a --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-da.swift @@ -0,0 +1,121 @@ +// Danish translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let daEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ og dets metadata ligger i Google Drives papirkurv (gemmes i 30 dage og tæller stadig med i kvoten).", + "'%@' is already in %@ - not replacing its password.": + "'%@' findes allerede i %@ – adgangskoden erstattes ikke.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' findes allerede i maskinens rclone.conf – adgangskoden erstattes ikke.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ indeholder mapper, som denne nøgle ikke kan læse: en anden adgangskode eller salt i maskinen eller rester fra et tidligere forsøg. Sammenlign maskinens rclone.conf med Mac'ens; rester skal fjernes fra %@ manuelt.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' er ufuldstændig eller mangler i Mac'ens rclone.conf – kør: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ skal have sin egen rclone.conf – sæt RCLONE_CONFIG til en kopi, ikke %@. Dens post i nøgleringen er tjenesten %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "Afsnittet '%@' kunne ikke læses på Mac'en eller i maskinen.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "Maskinens '%@' (adgangskode, salt eller mappe) afviger fra Mac'ens – kopien ville kun kunne læses med maskinens nøgle. Intet flyttes.", + "A file was restored from the encrypted backup.": + "En fil blev gendannet fra den krypterede sikkerhedskopi.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@' (%@) er tilføjet på Mac'en og i maskinen; adgangskode og salt ligger i nøgleringen (tjeneste %@).\nSALT: %@\nGem nu adgangskoden OG dette salt uden for denne Mac – uden begge kan den krypterede sikkerhedskopi ikke læses. Næste: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Tilføjer det krypterede remote på Mac'en og i maskinen (spørger om adgangskoden). Flytter ingen data.", + "Again: ": + "Igen: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Kopierer den ukrypterede sikkerhedskopi til den krypterede mappe i baggrunden; sikkerhedskopieringen fortsætter.", + "Copy: %@": + "Kopi: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ uden CLOUDMACHINE_VM_REMOTE ville bruge mappen med den aktive sikkerhedskopi – sæt begge.", + "Could not add the remote to %@: %@": + "Kunne ikke tilføje remote til %@: %@", + "Could not add the remote to the machine's rclone.conf.": + "Kunne ikke tilføje remote til maskinens rclone.conf.", + "Could not read %@.": + "Kunne ikke læse %@.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Kunne ikke læse, hvornår Time Machine sidst gennemførte en sikkerhedskopi (Fuld diskadgang?) – intet slettes.", + "Could not start the copy: %@": + "Kunne ikke starte kopieringen: %@", + "Could not write %@.": + "Kunne ikke skrive %@.", + "Deleting the plain folder failed: %@": + "Sletning af den ukrypterede mappe mislykkedes: %@", + "Encrypted folder: %@": + "Krypteret mappe: %@", + "Encryption password: ": + "Krypteringsadgangskode: ", + "Machine uses: %@": + "Maskinen bruger: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Flytter sikkerhedskopien på Google Drive ind under rclones kryptering: setup, copy, switch og derefter delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Flytter den ukrypterede mappe til Google Drives papirkurv – først når en sikkerhedskopi er gennemført i den krypterede.", + "Moving the machine failed; it went back to %@: %@": + "Omstillingen af maskinen mislykkedes; den er tilbage på %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Intet svar fra rclone i maskinen – uploadkøen er ukendt.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Endnu ingen sikkerhedskopi er gennemført i den krypterede mappe (seneste %@, omstillet %@) – intet slettes.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Ikke alt er på Google Drive endnu (%@ MB ikke sendt, %@ uploades, %@ mislykkedes). Vent, til uploaden er færdig: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Gendan først en fil fra den krypterede sikkerhedskopi (Time Machine, Gennemse), og bekræft derefter med --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Viser, hvilken mappe maskinen bruger, og hvordan kopieringen står.", + "Switched: %@": + "Omstillet: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "Kopieringen kører allerede – se: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "Kopieringen kører i maskinen (%@, log %@). Sikkerhedskopieringen fortsætter imens. Tjek: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "Kopieringen til den krypterede mappe er ikke afsluttet korrekt (%@) – kør: cloudmachine-agent vm-encrypt copy, og vent på den.", + "The encryption password and salt are kept outside this Mac.": + "Krypteringsadgangskoden og saltet opbevares uden for denne Mac.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "Vagten holder Time Machine sat på pause og ville genoptage den under omstillingen. Vent, til den er genoptaget, og uploaden er færdig.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "Billedets metadata er ikke kopieret til den krypterede mappe siden seneste sikkerhedskopi – intet slettes.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "Den sidste synkronisering eller cryptcheck mislykkedes – maskinen blev IKKE flyttet: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "Maskinen bruger allerede den krypterede mappe – intet at omstille.", + "The machine already uses the encrypted folder.": + "Maskinen bruger allerede den krypterede mappe.", + "The machine does not use the encrypted folder - nothing to roll back.": + "Maskinen bruger ikke den krypterede mappe – intet at rulle tilbage.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "Maskinen bruger ikke den krypterede mappe – den ukrypterede mappe er sikkerhedskopien. Den slettes ikke.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "Maskinen har intet remote '%@' i sin rclone.conf – kør: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "Maskinen bruger ikke denne apps skabelon eller bruger ikke %@ (først cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "Maskinen bruger nu %@, men %@ kunne ikke opdateres (%@) – ret det, før noget andet kører vm-setup.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "Maskinen bruger nu %@. %@ bevares. Slå automatisk sikkerhedskopiering til igen: sudo tmutil enable", + "The password must have at least 12 characters.": + "Adgangskoden skal have mindst 12 tegn.", + "The passwords differ - nothing was changed.": + "Adgangskoderne er forskellige – intet blev ændret.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "Omstillingen den anden vej: dekrypterer det ændrede tilbage i den ukrypterede mappe og bruger den igen.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "Uploaden til den krypterede mappe er ikke færdig (eller rclone svarede ikke) – intet slettes.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine er ved at sikkerhedskopiere. Slå automatisk sikkerhedskopiering fra (sudo tmutil disable), stop den (tmutil stopbackup), og kør derefter dette igen.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Med Time Machine stoppet: sidste synkronisering, cryptcheck, derefter bruger maskinen den krypterede mappe. Den ukrypterede bliver.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Uden krypteringsadgangskoden og saltet kan den krypterede sikkerhedskopi ikke læses. Gem begge uden for denne Mac (en adgangskodeadministrator, papir), og bekræft derefter med --secrets-saved-elsewhere.", + "rclone obscure failed.": + "rclone obscure mislykkedes.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil svarede ikke – ingen omstilling, så længe Time Machines tilstand er ukendt.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-de.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-de.swift new file mode 100644 index 0000000..703d7d9 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-de.swift @@ -0,0 +1,121 @@ +// German translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let deEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ und seine Metadaten liegen im Papierkorb von Google Drive (30 Tage aufbewahrt, zählen weiter zum Speicherkontingent).", + "'%@' is already in %@ - not replacing its password.": + "'%@' ist bereits in %@ – sein Passwort wird nicht ersetzt.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' ist bereits in der rclone.conf der Maschine – sein Passwort wird nicht ersetzt.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ enthält Ordner, die dieser Schlüssel nicht lesen kann: ein anderes Passwort oder Salt in der Maschine oder Reste eines früheren Versuchs. Vergleiche die rclone.conf der Maschine mit der des Mac; Reste müssen von Hand aus %@ entfernt werden.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' fehlt oder ist unvollständig in der rclone.conf des Mac – ausführen: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ braucht eine eigene rclone.conf – setze RCLONE_CONFIG auf eine Kopie, nicht auf %@. Ihr Schlüsselbund-Eintrag ist der Dienst %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "Der Abschnitt '%@' konnte auf dem Mac oder in der Maschine nicht gelesen werden.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "'%@' der Maschine (Passwort, Salt oder Ordner) weicht von dem des Mac ab – die Kopie wäre nur mit dem Schlüssel der Maschine lesbar. Es wird nichts verschoben.", + "A file was restored from the encrypted backup.": + "Eine Datei wurde aus dem verschlüsselten Backup wiederhergestellt.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@' (%@) wurde auf dem Mac und in der Maschine hinzugefügt; Passwort und Salt liegen im Schlüsselbund (Dienst %@).\nSALT: %@\nBewahre das Passwort UND dieses Salt jetzt außerhalb dieses Mac auf – ohne beide lässt sich das verschlüsselte Backup nicht lesen. Weiter: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Fügt den verschlüsselten Remote auf dem Mac und in der Maschine hinzu (fragt nach dem Passwort). Verschiebt keine Daten.", + "Again: ": + "Noch einmal: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Kopiert das unverschlüsselte Backup im Hintergrund in den verschlüsselten Ordner; Backups laufen weiter.", + "Copy: %@": + "Kopie: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ ohne CLOUDMACHINE_VM_REMOTE würde den Ordner des laufenden Backups verwenden – setze beide.", + "Could not add the remote to %@: %@": + "Der Remote konnte nicht zu %@ hinzugefügt werden: %@", + "Could not add the remote to the machine's rclone.conf.": + "Der Remote konnte nicht zur rclone.conf der Maschine hinzugefügt werden.", + "Could not read %@.": + "%@ konnte nicht gelesen werden.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Es ließ sich nicht lesen, wann Time Machine zuletzt ein Backup abgeschlossen hat (Festplattenvollzugriff?) – es wird nichts gelöscht.", + "Could not start the copy: %@": + "Die Kopie konnte nicht gestartet werden: %@", + "Could not write %@.": + "%@ konnte nicht geschrieben werden.", + "Deleting the plain folder failed: %@": + "Löschen des unverschlüsselten Ordners fehlgeschlagen: %@", + "Encrypted folder: %@": + "Verschlüsselter Ordner: %@", + "Encryption password: ": + "Verschlüsselungspasswort: ", + "Machine uses: %@": + "Maschine nutzt: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Verschiebt das Backup auf Google Drive unter die Verschlüsselung von rclone: setup, copy, switch, dann delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Verschiebt den unverschlüsselten Ordner in den Papierkorb von Google Drive – erst nachdem ein Backup im verschlüsselten abgeschlossen wurde.", + "Moving the machine failed; it went back to %@: %@": + "Das Umstellen der Maschine ist fehlgeschlagen; sie nutzt wieder %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Keine Antwort von rclone in der Maschine – die Upload-Warteschlange ist unbekannt.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Noch kein Backup wurde im verschlüsselten Ordner abgeschlossen (letztes %@, umgestellt %@) – es wird nichts gelöscht.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Noch ist nicht alles auf Google Drive (%@ MB nicht gesendet, %@ im Upload, %@ fehlgeschlagen). Warte, bis der Upload fertig ist: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Stelle zuerst eine Datei aus dem verschlüsselten Backup wieder her (Time Machine, Durchsuchen) und bestätige dann mit --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Zeigt, welchen Ordner die Maschine nutzt, und den Stand der Kopie.", + "Switched: %@": + "Umgestellt: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "Die Kopie läuft bereits – siehe: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "Die Kopie läuft in der Maschine (%@, Protokoll %@). Backups laufen währenddessen weiter. Prüfen: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "Die Kopie in den verschlüsselten Ordner ist nicht erfolgreich beendet (%@) – ausführen: cloudmachine-agent vm-encrypt copy, und abwarten.", + "The encryption password and salt are kept outside this Mac.": + "Verschlüsselungspasswort und Salt werden außerhalb dieses Mac aufbewahrt.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "Der Wächter hält Time Machine angehalten und würde es während der Umstellung fortsetzen. Warte, bis es fortgesetzt und der Upload fertig ist.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "Die Metadaten des Images wurden seit dem letzten Backup nicht in den verschlüsselten Ordner kopiert – es wird nichts gelöscht.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "Die letzte Synchronisierung oder cryptcheck ist fehlgeschlagen – die Maschine wurde NICHT umgestellt: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "Die Maschine nutzt bereits den verschlüsselten Ordner – nichts umzustellen.", + "The machine already uses the encrypted folder.": + "Die Maschine nutzt bereits den verschlüsselten Ordner.", + "The machine does not use the encrypted folder - nothing to roll back.": + "Die Maschine nutzt den verschlüsselten Ordner nicht – nichts zurückzunehmen.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "Die Maschine nutzt den verschlüsselten Ordner nicht – der unverschlüsselte Ordner ist das Backup. Er wird nicht gelöscht.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "Die Maschine hat keinen Remote '%@' in ihrer rclone.conf – ausführen: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "Die Maschine nutzt nicht die Vorlage dieser App oder nicht %@ (zuerst cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "Die Maschine nutzt jetzt %@, aber %@ konnte nicht aktualisiert werden (%@) – korrigiere das, bevor irgendetwas vm-setup ausführt.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "Die Maschine nutzt jetzt %@. %@ bleibt erhalten. Automatische Backups wieder einschalten: sudo tmutil enable", + "The password must have at least 12 characters.": + "Das Passwort muss mindestens 12 Zeichen haben.", + "The passwords differ - nothing was changed.": + "Die Passwörter stimmen nicht überein – nichts wurde geändert.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "Die Umstellung in die andere Richtung: entschlüsselt das Geänderte zurück in den unverschlüsselten Ordner und nutzt ihn wieder.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "Der Upload in den verschlüsselten Ordner ist nicht fertig (oder rclone hat nicht geantwortet) – es wird nichts gelöscht.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine erstellt gerade ein Backup. Schalte automatische Backups aus (sudo tmutil disable), stoppe es (tmutil stopbackup) und führe dies dann erneut aus.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Bei gestopptem Time Machine: letzte Synchronisierung, cryptcheck, dann nutzt die Maschine den verschlüsselten Ordner. Der unverschlüsselte bleibt.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Ohne Verschlüsselungspasswort und Salt lässt sich das verschlüsselte Backup nicht lesen. Bewahre beides außerhalb dieses Mac auf (Passwortmanager, Papier) und bestätige dann mit --secrets-saved-elsewhere.", + "rclone obscure failed.": + "rclone obscure ist fehlgeschlagen.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil hat nicht geantwortet – keine Umstellung, solange der Zustand von Time Machine unbekannt ist.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-es.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-es.swift new file mode 100644 index 0000000..5c49f2d --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-es.swift @@ -0,0 +1,121 @@ +// Spanish translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let esEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ y sus metadatos están en la papelera de Google Drive (se guardan 30 días y siguen contando en la cuota).", + "'%@' is already in %@ - not replacing its password.": + "'%@' ya está en %@: no se sustituye su contraseña.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' ya está en el rclone.conf de la máquina: no se sustituye su contraseña.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ contiene carpetas que esta clave no puede leer: otra contraseña o sal en la máquina, o restos de un intento anterior. Compara el rclone.conf de la máquina con el del Mac; los restos hay que eliminarlos de %@ a mano.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' está incompleto o falta en el rclone.conf del Mac. Ejecuta: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ necesita su propio rclone.conf: define RCLONE_CONFIG con una copia, no con %@. Su entrada del Llavero es el servicio %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "No se pudo leer la sección '%@' en el Mac ni en la máquina.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "'%@' de la máquina (contraseña, sal o carpeta) es distinto del del Mac: la copia solo se podría leer con la clave de la máquina. No se mueve nada.", + "A file was restored from the encrypted backup.": + "Se restauró un archivo de la copia cifrada.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "Se añadió '%@' (%@) en el Mac y en la máquina; la contraseña y la sal están en el Llavero (servicio %@).\nSAL: %@\nGuarda ahora la contraseña Y esta sal fuera de este Mac: sin ambas no se puede leer la copia cifrada. Siguiente: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Añade el remoto cifrado en el Mac y en la máquina (pide la contraseña). No mueve datos.", + "Again: ": + "Otra vez: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Copia la copia de seguridad sin cifrar a la carpeta cifrada, en segundo plano; las copias continúan.", + "Copy: %@": + "Copia: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ sin CLOUDMACHINE_VM_REMOTE usaría la carpeta de la copia en uso: define ambas.", + "Could not add the remote to %@: %@": + "No se pudo añadir el remoto a %@: %@", + "Could not add the remote to the machine's rclone.conf.": + "No se pudo añadir el remoto al rclone.conf de la máquina.", + "Could not read %@.": + "No se pudo leer %@.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "No se pudo saber cuándo terminó Time Machine su última copia (¿Acceso total al disco?): no se elimina nada.", + "Could not start the copy: %@": + "No se pudo iniciar la copia: %@", + "Could not write %@.": + "No se pudo escribir %@.", + "Deleting the plain folder failed: %@": + "Falló la eliminación de la carpeta sin cifrar: %@", + "Encrypted folder: %@": + "Carpeta cifrada: %@", + "Encryption password: ": + "Contraseña de cifrado: ", + "Machine uses: %@": + "La máquina usa: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Pasa la copia de Google Drive al cifrado de rclone: setup, copy, switch y después delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Mueve la carpeta sin cifrar a la papelera de Google Drive, solo después de que una copia haya terminado en la cifrada.", + "Moving the machine failed; it went back to %@: %@": + "Falló el cambio de la máquina; volvió a %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Sin respuesta de rclone en la máquina: la cola de subida es desconocida.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Todavía no ha terminado ninguna copia en la carpeta cifrada (la última %@, cambio %@): no se elimina nada.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Aún no está todo en Google Drive (%@ MB sin enviar, %@ subiendo, %@ con error). Espera a que termine la subida: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Restaura primero un archivo de la copia cifrada (Time Machine, Explorar) y después confirma con --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Muestra qué carpeta usa la máquina y el estado de la copia.", + "Switched: %@": + "Cambiado: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "La copia ya está en marcha. Consulta: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "La copia se ejecuta en la máquina (%@, registro %@). Mientras tanto, las copias continúan. Comprobar: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "La copia a la carpeta cifrada no terminó bien (%@). Ejecuta: cloudmachine-agent vm-encrypt copy y espera a que termine.", + "The encryption password and salt are kept outside this Mac.": + "La contraseña de cifrado y la sal se guardan fuera de este Mac.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "El vigilante mantiene Time Machine en pausa y la reanudaría durante el cambio. Espera a que se reanude y termine la subida.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "Los metadatos de la imagen no se han copiado a la carpeta cifrada desde la última copia: no se elimina nada.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "Falló la última sincronización o cryptcheck: la máquina NO se ha cambiado: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "La máquina ya usa la carpeta cifrada: no hay nada que cambiar.", + "The machine already uses the encrypted folder.": + "La máquina ya usa la carpeta cifrada.", + "The machine does not use the encrypted folder - nothing to roll back.": + "La máquina no usa la carpeta cifrada: no hay nada que revertir.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "La máquina no usa la carpeta cifrada: la carpeta sin cifrar es la copia. No se elimina.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "La máquina no tiene el remoto '%@' en su rclone.conf. Ejecuta: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "La máquina no usa la plantilla de esta app o no usa %@ (primero cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "La máquina usa ahora %@, pero no se pudo actualizar %@ (%@): corrígelo antes de que algo ejecute vm-setup.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "La máquina usa ahora %@. %@ se conserva. Vuelve a activar las copias automáticas: sudo tmutil enable", + "The password must have at least 12 characters.": + "La contraseña debe tener al menos 12 caracteres.", + "The passwords differ - nothing was changed.": + "Las contraseñas no coinciden: no se ha cambiado nada.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "El cambio en sentido contrario: descifra lo que cambió de vuelta a la carpeta sin cifrar y vuelve a usarla.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "La subida a la carpeta cifrada no ha terminado (o rclone no respondió): no se elimina nada.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine está haciendo una copia. Desactiva las copias automáticas (sudo tmutil disable), detenla (tmutil stopbackup) y vuelve a ejecutar esto.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Con Time Machine detenida: última sincronización, cryptcheck y después la máquina usa la carpeta cifrada. La sin cifrar se conserva.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Sin la contraseña de cifrado y la sal no se puede leer la copia cifrada. Guarda ambas fuera de este Mac (un gestor de contraseñas, papel) y después confirma con --secrets-saved-elsewhere.", + "rclone obscure failed.": + "Falló rclone obscure.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil no respondió: no se cambia mientras el estado de Time Machine sea desconocido.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-fi.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-fi.swift new file mode 100644 index 0000000..14cc8d4 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-fi.swift @@ -0,0 +1,121 @@ +// Finnish translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let fiEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ ja sen metatiedot ovat Google Driven roskakorissa (säilytetään 30 päivää, lasketaan edelleen kiintiöön).", + "'%@' is already in %@ - not replacing its password.": + "'%@' on jo kohteessa %@ – sen salasanaa ei korvata.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' on jo virtuaalikoneen rclone.conf-tiedostossa – sen salasanaa ei korvata.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ sisältää kansioita, joita tämä avain ei pysty lukemaan: virtuaalikoneessa on eri salasana tai suola, tai jäänteitä aiemmasta yrityksestä. Vertaa virtuaalikoneen rclone.conf-tiedostoa Macin tiedostoon; jäänteet on poistettava kohteesta %@ käsin.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' on puutteellinen tai puuttuu Macin rclone.conf-tiedostosta – suorita: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ tarvitsee oman rclone.conf-tiedoston – aseta RCLONE_CONFIG osoittamaan kopioon, ei tiedostoon %@. Sen avainnippumerkintä on palvelu %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "Osiota '%@' ei voitu lukea Macissa tai virtuaalikoneessa.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "Virtuaalikoneen '%@' (salasana, suola tai kansio) eroaa Macin vastaavasta – kopion voisi lukea vain virtuaalikoneen avaimella. Mitään ei siirretä.", + "A file was restored from the encrypted backup.": + "Tiedosto palautettiin salatusta varmuuskopiosta.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@' (%@) lisättiin Maciin ja virtuaalikoneeseen; salasana ja suola ovat avainnipussa (palvelu %@).\nSUOLA: %@\nSäilytä nyt salasana JA tämä suola tämän Macin ulkopuolella – ilman molempia salattua varmuuskopiota ei voi lukea. Seuraavaksi: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Lisää salatun etäkohteen Maciin ja virtuaalikoneeseen (kysyy salasanaa). Ei siirrä tietoja.", + "Again: ": + "Uudelleen: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Kopioi salaamattoman varmuuskopion salattuun kansioon taustalla; varmuuskopiointi jatkuu.", + "Copy: %@": + "Kopio: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ ilman CLOUDMACHINE_VM_REMOTE-muuttujaa käyttäisi käytössä olevan varmuuskopion kansiota – aseta molemmat.", + "Could not add the remote to %@: %@": + "Etäkohdetta ei voitu lisätä kohteeseen %@: %@", + "Could not add the remote to the machine's rclone.conf.": + "Etäkohdetta ei voitu lisätä virtuaalikoneen rclone.conf-tiedostoon.", + "Could not read %@.": + "Kohdetta %@ ei voitu lukea.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Ei voitu lukea, milloin Time Machine viimeksi sai varmuuskopion valmiiksi (Täysi levyn käyttö?) – mitään ei poisteta.", + "Could not start the copy: %@": + "Kopiointia ei voitu käynnistää: %@", + "Could not write %@.": + "Kohteeseen %@ ei voitu kirjoittaa.", + "Deleting the plain folder failed: %@": + "Salaamattoman kansion poistaminen epäonnistui: %@", + "Encrypted folder: %@": + "Salattu kansio: %@", + "Encryption password: ": + "Salauksen salasana: ", + "Machine uses: %@": + "Kone käyttää: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Siirtää Google Driven varmuuskopion rclonen salauksen alle: setup, copy, switch ja sitten delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Siirtää salaamattoman kansion Google Driven roskakoriin – vasta kun varmuuskopio on valmistunut salattuun kansioon.", + "Moving the machine failed; it went back to %@: %@": + "Virtuaalikoneen siirto epäonnistui; se palasi kohteeseen %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Ei vastausta virtuaalikoneen rclonelta – lähetysjonon tila on tuntematon.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Yksikään varmuuskopio ei ole vielä valmistunut salattuun kansioon (viimeisin %@, siirretty %@) – mitään ei poisteta.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Kaikki ei ole vielä Google Drivessa (%@ Mt lähettämättä, %@ lähetyksessä, %@ epäonnistui). Odota, että lähetys valmistuu: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Palauta ensin tiedosto salatusta varmuuskopiosta (Time Machine, Selaa) ja vahvista sitten valinnalla --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Näyttää, mitä kansiota virtuaalikone käyttää, ja kopioinnin tilan.", + "Switched: %@": + "Siirretty: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "Kopiointi on jo käynnissä – katso: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "Kopiointi on käynnissä virtuaalikoneessa (%@, loki %@). Varmuuskopiointi jatkuu sillä aikaa. Tarkista: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "Kopiointi salattuun kansioon ei päättynyt onnistuneesti (%@) – suorita: cloudmachine-agent vm-encrypt copy ja odota sen valmistumista.", + "The encryption password and salt are kept outside this Mac.": + "Salauksen salasana ja suola säilytetään tämän Macin ulkopuolella.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "Vahti pitää Time Machinen keskeytettynä ja jatkaisi sitä siirron aikana. Odota, että se jatkuu ja lähetys valmistuu.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "Levykuvan metatietoja ei ole kopioitu salattuun kansioon viimeisimmän varmuuskopion jälkeen – mitään ei poisteta.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "Viimeinen synkronointi tai cryptcheck epäonnistui – virtuaalikonetta EI siirretty: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "Virtuaalikone käyttää jo salattua kansiota – ei siirrettävää.", + "The machine already uses the encrypted folder.": + "Virtuaalikone käyttää jo salattua kansiota.", + "The machine does not use the encrypted folder - nothing to roll back.": + "Virtuaalikone ei käytä salattua kansiota – ei peruttavaa.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "Virtuaalikone ei käytä salattua kansiota – salaamaton kansio on varmuuskopio. Sitä ei poisteta.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "Virtuaalikoneen rclone.conf-tiedostossa ei ole etäkohdetta '%@' – suorita: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "Virtuaalikone ei ole tämän sovelluksen mallin mukainen tai ei käytä kohdetta %@ (ensin cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "Virtuaalikone käyttää nyt kohdetta %@, mutta kohdetta %@ ei voitu päivittää (%@) – korjaa se, ennen kuin mikään muu suorittaa vm-setupin.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "Virtuaalikone käyttää nyt kohdetta %@. %@ säilytetään. Ota automaattiset varmuuskopiot taas käyttöön: sudo tmutil enable", + "The password must have at least 12 characters.": + "Salasanassa on oltava vähintään 12 merkkiä.", + "The passwords differ - nothing was changed.": + "Salasanat eroavat – mitään ei muutettu.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "Siirto toiseen suuntaan: purkaa muuttuneen salauksen takaisin salaamattomaan kansioon ja ottaa sen uudelleen käyttöön.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "Lähetys salattuun kansioon ei ole valmis (tai rclone ei vastannut) – mitään ei poisteta.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine varmuuskopioi parhaillaan. Poista automaattiset varmuuskopiot käytöstä (sudo tmutil disable), pysäytä se (tmutil stopbackup) ja suorita tämä sitten uudelleen.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Kun Time Machine on pysäytetty: viimeinen synkronointi, cryptcheck, sitten virtuaalikone käyttää salattua kansiota. Salaamaton jää.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Ilman salauksen salasanaa ja suolaa salattua varmuuskopiota ei voi lukea. Säilytä molemmat tämän Macin ulkopuolella (salasanojen hallinta, paperi) ja vahvista sitten valinnalla --secrets-saved-elsewhere.", + "rclone obscure failed.": + "rclone obscure epäonnistui.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil ei vastannut – siirtoa ei tehdä, kun Time Machinen tila on tuntematon.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-fr.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-fr.swift new file mode 100644 index 0000000..a89b080 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-fr.swift @@ -0,0 +1,121 @@ +// French translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let frEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ et ses métadonnées sont dans la corbeille de Google Drive (conservés 30 jours, toujours comptés dans le quota).", + "'%@' is already in %@ - not replacing its password.": + "'%@' est déjà dans %@ – son mot de passe n'est pas remplacé.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' est déjà dans le rclone.conf de la machine – son mot de passe n'est pas remplacé.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ contient des dossiers que cette clé ne peut pas lire : un autre mot de passe ou sel dans la machine, ou des restes d'un essai précédent. Comparez le rclone.conf de la machine avec celui du Mac ; les restes doivent être supprimés de %@ à la main.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' est incomplet ou absent du rclone.conf du Mac – exécutez : cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ a besoin de son propre rclone.conf – réglez RCLONE_CONFIG sur une copie, pas sur %@. Son entrée du trousseau est le service %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "Impossible de lire la section '%@' sur le Mac ou dans la machine.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "'%@' de la machine (mot de passe, sel ou dossier) diffère de celui du Mac – la copie ne serait lisible qu'avec la clé de la machine. Rien n'est déplacé.", + "A file was restored from the encrypted backup.": + "Un fichier a été restauré depuis la sauvegarde chiffrée.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@' (%@) ajouté sur le Mac et dans la machine ; le mot de passe et le sel sont dans le trousseau (service %@).\nSEL : %@\nConservez maintenant le mot de passe ET ce sel hors de ce Mac – sans les deux, la sauvegarde chiffrée est illisible. Ensuite : cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Ajoute le distant chiffré sur le Mac et dans la machine (demande le mot de passe). Ne déplace aucune donnée.", + "Again: ": + "Encore une fois : ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Copie la sauvegarde non chiffrée dans le dossier chiffré, en arrière-plan ; les sauvegardes continuent.", + "Copy: %@": + "Copie : %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ sans CLOUDMACHINE_VM_REMOTE utiliserait le dossier de la sauvegarde en service – définissez les deux.", + "Could not add the remote to %@: %@": + "Impossible d'ajouter le distant à %@ : %@", + "Could not add the remote to the machine's rclone.conf.": + "Impossible d'ajouter le distant au rclone.conf de la machine.", + "Could not read %@.": + "Impossible de lire %@.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Impossible de savoir quand Time Machine a terminé sa dernière sauvegarde (Accès complet au disque ?) – aucune suppression.", + "Could not start the copy: %@": + "Impossible de lancer la copie : %@", + "Could not write %@.": + "Impossible d'écrire %@.", + "Deleting the plain folder failed: %@": + "La suppression du dossier non chiffré a échoué : %@", + "Encrypted folder: %@": + "Dossier chiffré : %@", + "Encryption password: ": + "Mot de passe de chiffrement : ", + "Machine uses: %@": + "La machine utilise : %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Place la sauvegarde sur Google Drive sous le chiffrement de rclone : setup, copy, switch, puis delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Place le dossier non chiffré dans la corbeille de Google Drive – seulement après qu'une sauvegarde s'est terminée dans le dossier chiffré.", + "Moving the machine failed; it went back to %@: %@": + "Le basculement de la machine a échoué ; elle est revenue à %@ : %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Aucune réponse de rclone dans la machine – la file d'envoi est inconnue.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Aucune sauvegarde ne s'est encore terminée dans le dossier chiffré (dernière %@, basculement %@) – aucune suppression.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Tout n'est pas encore sur Google Drive (%@ Mo non envoyés, %@ en cours d'envoi, %@ en échec). Attendez la fin de l'envoi : cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Restaurez d'abord un fichier depuis la sauvegarde chiffrée (Time Machine, Parcourir), puis confirmez avec --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Indique quel dossier la machine utilise et où en est la copie.", + "Switched: %@": + "Basculé : %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "La copie est déjà en cours – voir : cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "La copie s'exécute dans la machine (%@, journal %@). Les sauvegardes continuent pendant ce temps. Vérifier : cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "La copie vers le dossier chiffré ne s'est pas bien terminée (%@) – exécutez : cloudmachine-agent vm-encrypt copy, et attendez-la.", + "The encryption password and salt are kept outside this Mac.": + "Le mot de passe de chiffrement et le sel sont conservés hors de ce Mac.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "Le gardien maintient Time Machine en pause et la relancerait pendant le basculement. Attendez qu'elle reprenne et que l'envoi soit terminé.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "Les métadonnées de l'image n'ont pas été copiées dans le dossier chiffré depuis la dernière sauvegarde – aucune suppression.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "La dernière synchronisation ou cryptcheck a échoué – la machine n'a PAS basculé : %@", + "The machine already uses the encrypted folder - nothing to switch.": + "La machine utilise déjà le dossier chiffré – rien à basculer.", + "The machine already uses the encrypted folder.": + "La machine utilise déjà le dossier chiffré.", + "The machine does not use the encrypted folder - nothing to roll back.": + "La machine n'utilise pas le dossier chiffré – rien à annuler.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "La machine n'utilise pas le dossier chiffré – le dossier non chiffré est la sauvegarde. Il n'est pas supprimé.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "La machine n'a pas de distant '%@' dans son rclone.conf – exécutez : cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "La machine n'est pas sur le modèle de cette app ou n'utilise pas %@ (d'abord cloudmachine-agent vm-update) : %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "La machine utilise maintenant %@, mais %@ n'a pas pu être mis à jour (%@) – corrigez-le avant que quoi que ce soit n'exécute vm-setup.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "La machine utilise maintenant %@. %@ est conservé. Réactivez les sauvegardes automatiques : sudo tmutil enable", + "The password must have at least 12 characters.": + "Le mot de passe doit comporter au moins 12 caractères.", + "The passwords differ - nothing was changed.": + "Les mots de passe diffèrent – rien n'a été modifié.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "Le basculement dans l'autre sens : déchiffre ce qui a changé vers le dossier non chiffré et l'utilise à nouveau.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "L'envoi vers le dossier chiffré n'est pas terminé (ou rclone n'a pas répondu) – aucune suppression.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine est en train de sauvegarder. Désactivez les sauvegardes automatiques (sudo tmutil disable), arrêtez-la (tmutil stopbackup), puis relancez ceci.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Time Machine arrêtée : dernière synchronisation, cryptcheck, puis la machine utilise le dossier chiffré. Le dossier non chiffré reste.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Sans le mot de passe de chiffrement et le sel, la sauvegarde chiffrée est illisible. Conservez les deux hors de ce Mac (gestionnaire de mots de passe, papier), puis confirmez avec --secrets-saved-elsewhere.", + "rclone obscure failed.": + "rclone obscure a échoué.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil n'a pas répondu – pas de basculement tant que l'état de Time Machine est inconnu.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-he.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-he.swift new file mode 100644 index 0000000..7c8b3a3 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-he.swift @@ -0,0 +1,121 @@ +// Hebrew translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let heEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ והמטא-נתונים שלו נמצאים בפח של Google Drive (נשמרים 30 יום ועדיין נספרים במכסה).", + "'%@' is already in %@ - not replacing its password.": + "'%@' כבר נמצא ב-%@ - הסיסמה שלו לא תוחלף.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' כבר נמצא ב-rclone.conf של המכונה - הסיסמה שלו לא תוחלף.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ מכיל תיקיות שהמפתח הזה לא יכול לקרוא: סיסמה או מלח אחרים במכונה, או שאריות מניסיון קודם. השווה את rclone.conf של המכונה לזה שב-Mac; יש להסיר את השאריות מ-%@ באופן ידני.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' חסר או לא שלם ב-rclone.conf של ה-Mac - הרץ: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ צריכה קובץ rclone.conf משלה - הגדר את RCLONE_CONFIG לעותק, לא ל-%@. הרשומה שלה במחזיק המפתחות היא השירות %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "לא ניתן היה לקרוא את המקטע '%@' ב-Mac או במכונה.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "'%@' של המכונה (סיסמה, מלח או תיקייה) שונה מזה שב-Mac - את העותק ניתן היה לקרוא רק עם המפתח של המכונה. שום דבר לא מועבר.", + "A file was restored from the encrypted backup.": + "קובץ שוחזר מהגיבוי המוצפן.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@' (%@) נוסף ב-Mac ובמכונה; הסיסמה והמלח נמצאים במחזיק המפתחות (שירות %@).\nמלח: %@\nשמור עכשיו את הסיסמה ואת המלח הזה מחוץ ל-Mac הזה - בלי שניהם אי אפשר לקרוא את הגיבוי המוצפן. השלב הבא: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "מוסיף את החיבור המוצפן ב-Mac ובמכונה (מבקש את הסיסמה). לא מעביר נתונים.", + "Again: ": + "שוב: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "מעתיק את הגיבוי הלא מוצפן לתיקייה המוצפנת ברקע; הגיבויים ממשיכים.", + "Copy: %@": + "העתקה: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ ללא CLOUDMACHINE_VM_REMOTE תשתמש בתיקיית הגיבוי הפעיל - הגדר את שניהם.", + "Could not add the remote to %@: %@": + "לא ניתן היה להוסיף את החיבור ל-%@: %@", + "Could not add the remote to the machine's rclone.conf.": + "לא ניתן היה להוסיף את החיבור ל-rclone.conf של המכונה.", + "Could not read %@.": + "לא ניתן היה לקרוא את %@.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "לא ניתן היה לקרוא מתי Time Machine סיים לאחרונה גיבוי (גישה מלאה לדיסק?) - שום דבר לא נמחק.", + "Could not start the copy: %@": + "לא ניתן היה להתחיל את ההעתקה: %@", + "Could not write %@.": + "לא ניתן היה לכתוב את %@.", + "Deleting the plain folder failed: %@": + "מחיקת התיקייה הלא מוצפנת נכשלה: %@", + "Encrypted folder: %@": + "תיקייה מוצפנת: %@", + "Encryption password: ": + "סיסמת הצפנה: ", + "Machine uses: %@": + "בשימוש המכונה: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "מעביר את הגיבוי ב-Google Drive להצפנה של rclone: setup, copy, switch ואז delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "מעביר את התיקייה הלא מוצפנת לפח של Google Drive - רק אחרי שגיבוי הסתיים בתיקייה המוצפנת.", + "Moving the machine failed; it went back to %@: %@": + "העברת המכונה נכשלה; היא חזרה אל %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "אין תשובה מ-rclone במכונה - מצב תור ההעלאה לא ידוע.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "עדיין אף גיבוי לא הסתיים בתיקייה המוצפנת (האחרון %@, ההעברה %@) - שום דבר לא נמחק.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "עדיין לא הכול ב-Google Drive (%@ MB לא נשלחו, %@ בהעלאה, %@ נכשלו). המתן עד שההעלאה תסתיים: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "שחזר קודם קובץ מהגיבוי המוצפן (Time Machine, עיון), ואז אשר עם --restore-checked.", + "Shows which folder the machine uses and the copy.": + "מציג באיזו תיקייה המכונה משתמשת ואת מצב ההעתקה.", + "Switched: %@": + "הועבר: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "ההעתקה כבר פועלת - ראה: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "ההעתקה פועלת במכונה (%@, יומן %@). בינתיים הגיבויים ממשיכים. בדיקה: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "ההעתקה לתיקייה המוצפנת לא הסתיימה כראוי (%@) - הרץ: cloudmachine-agent vm-encrypt copy, והמתן לסיומה.", + "The encryption password and salt are kept outside this Mac.": + "סיסמת ההצפנה והמלח נשמרים מחוץ ל-Mac הזה.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "השומר מחזיק את Time Machine במצב מושהה ועלול לחדש אותו במהלך ההעברה. המתן עד שהגיבוי יחודש וההעלאה תסתיים.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "המטא-נתונים של התמונה לא הועתקו לתיקייה המוצפנת מאז הגיבוי האחרון - שום דבר לא נמחק.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "הסנכרון האחרון או cryptcheck נכשל - המכונה לא הועברה: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "המכונה כבר משתמשת בתיקייה המוצפנת - אין מה להעביר.", + "The machine already uses the encrypted folder.": + "המכונה כבר משתמשת בתיקייה המוצפנת.", + "The machine does not use the encrypted folder - nothing to roll back.": + "המכונה לא משתמשת בתיקייה המוצפנת - אין מה לבטל.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "המכונה לא משתמשת בתיקייה המוצפנת - התיקייה הלא מוצפנת היא הגיבוי. היא לא נמחקת.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "למכונה אין חיבור '%@' ב-rclone.conf שלה - הרץ: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "המכונה לא פועלת עם התבנית של האפליקציה הזו או לא משתמשת ב-%@ (קודם cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "המכונה משתמשת עכשיו ב-%@, אבל לא ניתן היה לעדכן את %@ (%@) - תקן זאת לפני שמשהו אחר מריץ vm-setup.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "המכונה משתמשת עכשיו ב-%@. %@ נשמרת. הפעל מחדש גיבויים אוטומטיים: sudo tmutil enable", + "The password must have at least 12 characters.": + "הסיסמה חייבת להכיל לפחות 12 תווים.", + "The passwords differ - nothing was changed.": + "הסיסמאות שונות - שום דבר לא שונה.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "ההעברה בכיוון ההפוך: מפענח את מה שהשתנה בחזרה לתיקייה הלא מוצפנת ומשתמש בה שוב.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "ההעלאה לתיקייה המוצפנת לא הסתיימה (או ש-rclone לא ענה) - שום דבר לא נמחק.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine מגבה כרגע. כבה גיבויים אוטומטיים (sudo tmutil disable), עצור אותו (tmutil stopbackup), ואז הרץ זאת שוב.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "כש-Time Machine עצור: סנכרון אחרון, cryptcheck, ואז המכונה משתמשת בתיקייה המוצפנת. הלא מוצפנת נשארת.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "בלי סיסמת ההצפנה והמלח אי אפשר לקרוא את הגיבוי המוצפן. שמור את שניהם מחוץ ל-Mac הזה (מנהל סיסמאות, נייר), ואז אשר עם --secrets-saved-elsewhere.", + "rclone obscure failed.": + "rclone obscure נכשל.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil לא ענה - אין העברה כל עוד המצב של Time Machine לא ידוע.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-it.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-it.swift new file mode 100644 index 0000000..e7a3db9 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-it.swift @@ -0,0 +1,121 @@ +// Italian translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let itEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ e i suoi metadati sono nel cestino di Google Drive (conservati 30 giorni, ancora conteggiati nella quota).", + "'%@' is already in %@ - not replacing its password.": + "'%@' è già in %@ – la sua password non viene sostituita.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' è già nel rclone.conf della macchina – la sua password non viene sostituita.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ contiene cartelle che questa chiave non può leggere: un'altra password o sale nella macchina, oppure resti di un tentativo precedente. Confronta il rclone.conf della macchina con quello del Mac; i resti vanno rimossi a mano da %@.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' è incompleto o manca nel rclone.conf del Mac – esegui: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ ha bisogno di un proprio rclone.conf – imposta RCLONE_CONFIG su una copia, non su %@. La sua voce nel portachiavi è il servizio %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "Impossibile leggere la sezione '%@' sul Mac o nella macchina.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "'%@' della macchina (password, sale o cartella) è diverso da quello del Mac – la copia sarebbe leggibile solo con la chiave della macchina. Non viene spostato nulla.", + "A file was restored from the encrypted backup.": + "Un file è stato ripristinato dal backup cifrato.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "Aggiunto '%@' (%@) sul Mac e nella macchina; password e sale sono nel portachiavi (servizio %@).\nSALE: %@\nConserva ora la password E questo sale fuori da questo Mac – senza entrambi il backup cifrato non si può leggere. Poi: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Aggiunge il remoto cifrato sul Mac e nella macchina (chiede la password). Non sposta dati.", + "Again: ": + "Ancora: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Copia il backup non cifrato nella cartella cifrata, in background; i backup continuano.", + "Copy: %@": + "Copia: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ senza CLOUDMACHINE_VM_REMOTE userebbe la cartella del backup in uso – impostale entrambe.", + "Could not add the remote to %@: %@": + "Impossibile aggiungere il remoto a %@: %@", + "Could not add the remote to the machine's rclone.conf.": + "Impossibile aggiungere il remoto al rclone.conf della macchina.", + "Could not read %@.": + "Impossibile leggere %@.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Impossibile sapere quando Time Machine ha completato l'ultimo backup (Accesso completo al disco?) – nessuna eliminazione.", + "Could not start the copy: %@": + "Impossibile avviare la copia: %@", + "Could not write %@.": + "Impossibile scrivere %@.", + "Deleting the plain folder failed: %@": + "Eliminazione della cartella non cifrata non riuscita: %@", + "Encrypted folder: %@": + "Cartella cifrata: %@", + "Encryption password: ": + "Password di cifratura: ", + "Machine uses: %@": + "La macchina usa: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Porta il backup su Google Drive sotto la cifratura di rclone: setup, copy, switch, poi delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Sposta la cartella non cifrata nel cestino di Google Drive – solo dopo che un backup è terminato in quella cifrata.", + "Moving the machine failed; it went back to %@: %@": + "Il passaggio della macchina non è riuscito; è tornata a %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Nessuna risposta da rclone nella macchina – la coda di caricamento è sconosciuta.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Nessun backup è ancora terminato nella cartella cifrata (l'ultimo %@, passaggio %@) – nessuna eliminazione.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Non è ancora tutto su Google Drive (%@ MB non inviati, %@ in caricamento, %@ non riusciti). Attendi la fine del caricamento: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Ripristina prima un file dal backup cifrato (Time Machine, Sfoglia), poi conferma con --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Mostra quale cartella usa la macchina e lo stato della copia.", + "Switched: %@": + "Passaggio: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "La copia è già in corso – vedi: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "La copia è in esecuzione nella macchina (%@, log %@). Nel frattempo i backup continuano. Controlla: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "La copia nella cartella cifrata non è terminata correttamente (%@) – esegui: cloudmachine-agent vm-encrypt copy, e attendila.", + "The encryption password and salt are kept outside this Mac.": + "La password di cifratura e il sale sono conservati fuori da questo Mac.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "Il guardiano tiene Time Machine in pausa e la riprenderebbe durante il passaggio. Attendi che riprenda e che il caricamento sia terminato.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "I metadati dell'immagine non sono stati copiati nella cartella cifrata dall'ultimo backup – nessuna eliminazione.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "L'ultima sincronizzazione o cryptcheck non è riuscita – la macchina NON è stata spostata: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "La macchina usa già la cartella cifrata – nulla da spostare.", + "The machine already uses the encrypted folder.": + "La macchina usa già la cartella cifrata.", + "The machine does not use the encrypted folder - nothing to roll back.": + "La macchina non usa la cartella cifrata – nulla da annullare.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "La macchina non usa la cartella cifrata – la cartella non cifrata è il backup. Non viene eliminata.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "La macchina non ha il remoto '%@' nel suo rclone.conf – esegui: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "La macchina non usa il modello di questa app o non usa %@ (prima cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "La macchina ora usa %@, ma non è stato possibile aggiornare %@ (%@) – correggilo prima che qualcosa esegua vm-setup.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "La macchina ora usa %@. %@ viene conservata. Riattiva i backup automatici: sudo tmutil enable", + "The password must have at least 12 characters.": + "La password deve avere almeno 12 caratteri.", + "The passwords differ - nothing was changed.": + "Le password non coincidono – non è stato modificato nulla.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "Il passaggio nell'altro senso: decifra ciò che è cambiato nella cartella non cifrata e torna a usarla.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "Il caricamento nella cartella cifrata non è terminato (o rclone non ha risposto) – nessuna eliminazione.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine sta eseguendo un backup. Disattiva i backup automatici (sudo tmutil disable), fermala (tmutil stopbackup), poi esegui di nuovo questo comando.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Con Time Machine ferma: ultima sincronizzazione, cryptcheck, poi la macchina usa la cartella cifrata. Quella non cifrata resta.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Senza password di cifratura e sale il backup cifrato non si può leggere. Conservali entrambi fuori da questo Mac (gestore di password, carta), poi conferma con --secrets-saved-elsewhere.", + "rclone obscure failed.": + "rclone obscure non è riuscito.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil non ha risposto – nessun passaggio finché lo stato di Time Machine è sconosciuto.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ja.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ja.swift new file mode 100644 index 0000000..11aa1ce --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ja.swift @@ -0,0 +1,121 @@ +// Japanese translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let jaEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ とそのメタデータは Google Drive のゴミ箱にあります(30日間保持され、引き続き容量に含まれます)。", + "'%@' is already in %@ - not replacing its password.": + "'%@' はすでに %@ にあります - パスワードは置き換えません。", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' はすでにマシンの rclone.conf にあります - パスワードは置き換えません。", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ には、このキーで読めないフォルダがあります: マシン内のパスワードまたはソルトが異なるか、以前の試行の残りです。マシンの rclone.conf と Mac のものを比較してください。残りは %@ から手動で削除する必要があります。", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' が Mac の rclone.conf にないか不完全です - 実行: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ には専用の rclone.conf が必要です - RCLONE_CONFIG を %@ ではなくコピーに設定してください。キーチェーンの項目はサービス %@ です。", + "Could not read the '%@' section on the Mac or in the machine.": + "Mac またはマシンで '%@' セクションを読み取れませんでした。", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "マシンの '%@'(パスワード、ソルト、フォルダ)が Mac のものと異なります - コピーはマシンのキーでしか読めなくなります。何も移動しません。", + "A file was restored from the encrypted backup.": + "暗号化されたバックアップからファイルを復元しました。", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@'(%@)を Mac とマシンに追加しました。パスワードとソルトはキーチェーンにあります(サービス %@)。\nソルト: %@\n今すぐパスワードとこのソルトの両方をこの Mac の外に保管してください - 両方がないと暗号化されたバックアップは読めません。次: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "暗号化されたリモートを Mac とマシンに追加します(パスワードを尋ねます)。データは移動しません。", + "Again: ": + "もう一度: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "暗号化されていないバックアップを暗号化フォルダへバックグラウンドでコピーします。バックアップは続行します。", + "Copy: %@": + "コピー: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ を CLOUDMACHINE_VM_REMOTE なしで使うと、稼働中のバックアップのフォルダを使ってしまいます - 両方を設定してください。", + "Could not add the remote to %@: %@": + "%@ にリモートを追加できませんでした: %@", + "Could not add the remote to the machine's rclone.conf.": + "マシンの rclone.conf にリモートを追加できませんでした。", + "Could not read %@.": + "%@ を読み取れませんでした。", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Time Machine が最後にバックアップを完了した日時を読み取れませんでした(フルディスクアクセス?) - 削除しません。", + "Could not start the copy: %@": + "コピーを開始できませんでした: %@", + "Could not write %@.": + "%@ に書き込めませんでした。", + "Deleting the plain folder failed: %@": + "暗号化されていないフォルダの削除に失敗しました: %@", + "Encrypted folder: %@": + "暗号化フォルダ: %@", + "Encryption password: ": + "暗号化パスワード: ", + "Machine uses: %@": + "マシンの使用先: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Google Drive 上のバックアップを rclone の暗号化の下に移します: setup、copy、switch、その後 delete-plain。", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "暗号化されていないフォルダを Google Drive のゴミ箱に移します - 暗号化フォルダへのバックアップが完了した後のみ。", + "Moving the machine failed; it went back to %@: %@": + "マシンの切り替えに失敗しました。%@ に戻りました: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "マシン内の rclone から応答がありません - アップロードキューの状態は不明です。", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "暗号化フォルダへのバックアップはまだ1回も完了していません(最後 %@、切り替え %@) - 削除しません。", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "まだすべてが Google Drive にあるわけではありません(未送信 %@ MB、アップロード中 %@、失敗 %@)。アップロードが終わるまで待ってください: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "まず暗号化されたバックアップからファイルを1つ復元し(Time Machine、ブラウズ)、その後 --restore-checked で確認してください。", + "Shows which folder the machine uses and the copy.": + "マシンが使っているフォルダとコピーの状態を表示します。", + "Switched: %@": + "切り替え: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "コピーはすでに実行中です - 確認: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "コピーはマシン内で実行中です(%@、ログ %@)。その間もバックアップは続行します。確認: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "暗号化フォルダへのコピーが正常に終わっていません(%@) - 実行: cloudmachine-agent vm-encrypt copy、そして完了を待ってください。", + "The encryption password and salt are kept outside this Mac.": + "暗号化パスワードとソルトはこの Mac の外に保管されています。", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "バッファガードが Time Machine を一時停止しており、切り替え中に再開してしまいます。再開してアップロードが終わるまで待ってください。", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "前回のバックアップ以降、イメージのメタデータが暗号化フォルダにコピーされていません - 削除しません。", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "最後の同期または cryptcheck に失敗しました - マシンは切り替えられていません: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "マシンはすでに暗号化フォルダを使っています - 切り替えるものはありません。", + "The machine already uses the encrypted folder.": + "マシンはすでに暗号化フォルダを使っています。", + "The machine does not use the encrypted folder - nothing to roll back.": + "マシンは暗号化フォルダを使っていません - 戻すものはありません。", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "マシンは暗号化フォルダを使っていません - 暗号化されていないフォルダがバックアップです。削除しません。", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "マシンの rclone.conf にリモート '%@' がありません - 実行: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "マシンがこのアプリのテンプレートではないか、%@ を使っていません(先に cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "マシンは現在 %@ を使っていますが、%@ を更新できませんでした(%@) - 他の何かが vm-setup を実行する前に修正してください。", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "マシンは現在 %@ を使っています。%@ は残してあります。自動バックアップを再びオンにしてください: sudo tmutil enable", + "The password must have at least 12 characters.": + "パスワードは12文字以上にしてください。", + "The passwords differ - nothing was changed.": + "パスワードが一致しません - 何も変更していません。", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "逆方向の切り替え: 変更された分を暗号化されていないフォルダへ復号し、再びそれを使います。", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "暗号化フォルダへのアップロードが終わっていません(または rclone が応答しませんでした) - 削除しません。", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine がバックアップ中です。自動バックアップをオフにし(sudo tmutil disable)、停止して(tmutil stopbackup)から、もう一度実行してください。", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Time Machine を停止した状態で: 最後の同期、cryptcheck、その後マシンが暗号化フォルダを使います。暗号化されていないフォルダは残ります。", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "暗号化パスワードとソルトがないと、暗号化されたバックアップは読めません。両方をこの Mac の外(パスワードマネージャ、紙)に保管し、その後 --secrets-saved-elsewhere で確認してください。", + "rclone obscure failed.": + "rclone obscure に失敗しました。", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil から応答がありません - Time Machine の状態が不明な間は切り替えません。", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ko.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ko.swift new file mode 100644 index 0000000..960bef8 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ko.swift @@ -0,0 +1,121 @@ +// Korean translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let koEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ 및 메타데이터는 Google Drive 휴지통에 있습니다(30일 동안 보관되며 계속 용량에 포함됩니다).", + "'%@' is already in %@ - not replacing its password.": + "'%@'이(가) 이미 %@에 있습니다 - 암호를 바꾸지 않습니다.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@'이(가) 이미 머신의 rclone.conf에 있습니다 - 암호를 바꾸지 않습니다.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@에 이 키로 읽을 수 없는 폴더가 있습니다: 머신의 암호나 솔트가 다르거나 이전 시도의 잔여물입니다. 머신의 rclone.conf를 Mac의 것과 비교하십시오. 잔여물은 %@에서 직접 삭제해야 합니다.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "Mac의 rclone.conf에 '%@'이(가) 없거나 불완전합니다 - 실행: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@에는 자체 rclone.conf가 필요합니다 - RCLONE_CONFIG를 %@이(가) 아닌 사본으로 설정하십시오. 키체인 항목은 서비스 %@입니다.", + "Could not read the '%@' section on the Mac or in the machine.": + "Mac 또는 머신에서 '%@' 섹션을 읽을 수 없습니다.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "머신의 '%@'(암호, 솔트 또는 폴더)이(가) Mac의 것과 다릅니다 - 사본은 머신의 키로만 읽을 수 있게 됩니다. 아무것도 옮기지 않습니다.", + "A file was restored from the encrypted backup.": + "암호화된 백업에서 파일을 복원했습니다.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@'(%@)을(를) Mac과 머신에 추가했습니다. 암호와 솔트는 키체인에 있습니다(서비스 %@).\n솔트: %@\n지금 암호와 이 솔트를 모두 이 Mac 밖에 보관하십시오 - 둘 다 없으면 암호화된 백업을 읽을 수 없습니다. 다음: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "암호화된 원격 저장소를 Mac과 머신에 추가합니다(암호를 묻습니다). 데이터는 옮기지 않습니다.", + "Again: ": + "다시 한 번: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "암호화되지 않은 백업을 백그라운드에서 암호화된 폴더로 복사합니다. 백업은 계속됩니다.", + "Copy: %@": + "복사: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@을(를) CLOUDMACHINE_VM_REMOTE 없이 쓰면 사용 중인 백업의 폴더를 쓰게 됩니다 - 둘 다 설정하십시오.", + "Could not add the remote to %@: %@": + "%@에 원격 저장소를 추가할 수 없습니다: %@", + "Could not add the remote to the machine's rclone.conf.": + "머신의 rclone.conf에 원격 저장소를 추가할 수 없습니다.", + "Could not read %@.": + "%@을(를) 읽을 수 없습니다.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Time Machine이 마지막으로 백업을 완료한 시각을 읽을 수 없습니다(전체 디스크 접근 권한?) - 삭제하지 않습니다.", + "Could not start the copy: %@": + "복사를 시작할 수 없습니다: %@", + "Could not write %@.": + "%@에 쓸 수 없습니다.", + "Deleting the plain folder failed: %@": + "암호화되지 않은 폴더 삭제에 실패했습니다: %@", + "Encrypted folder: %@": + "암호화된 폴더: %@", + "Encryption password: ": + "암호화 암호: ", + "Machine uses: %@": + "머신 사용 폴더: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Google Drive의 백업을 rclone 암호화 아래로 옮깁니다: setup, copy, switch, 그다음 delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "암호화되지 않은 폴더를 Google Drive 휴지통으로 옮깁니다 - 암호화된 폴더로의 백업이 완료된 후에만.", + "Moving the machine failed; it went back to %@: %@": + "머신 전환에 실패했습니다. %@(으)로 돌아갔습니다: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "머신의 rclone에서 응답이 없습니다 - 업로드 대기열 상태를 알 수 없습니다.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "암호화된 폴더로 완료된 백업이 아직 없습니다(마지막 %@, 전환 %@) - 삭제하지 않습니다.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "아직 모든 것이 Google Drive에 있지는 않습니다(%@ MB 미전송, %@개 업로드 중, %@개 실패). 업로드가 끝날 때까지 기다리십시오: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "먼저 암호화된 백업에서 파일을 하나 복원한 다음(Time Machine, 둘러보기) --restore-checked로 확인하십시오.", + "Shows which folder the machine uses and the copy.": + "머신이 사용하는 폴더와 복사 상태를 표시합니다.", + "Switched: %@": + "전환: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "복사가 이미 실행 중입니다 - 확인: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "복사가 머신에서 실행 중입니다(%@, 로그 %@). 그동안 백업은 계속됩니다. 확인: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "암호화된 폴더로의 복사가 제대로 끝나지 않았습니다(%@) - 실행: cloudmachine-agent vm-encrypt copy 후 끝날 때까지 기다리십시오.", + "The encryption password and salt are kept outside this Mac.": + "암호화 암호와 솔트는 이 Mac 밖에 보관되어 있습니다.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "버퍼 가드가 Time Machine을 일시 정지 상태로 두고 있으며 전환 중에 재개하게 됩니다. 재개되고 업로드가 끝날 때까지 기다리십시오.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "마지막 백업 이후 이미지 메타데이터가 암호화된 폴더로 복사되지 않았습니다 - 삭제하지 않습니다.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "마지막 동기화 또는 cryptcheck에 실패했습니다 - 머신을 전환하지 않았습니다: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "머신이 이미 암호화된 폴더를 사용합니다 - 전환할 것이 없습니다.", + "The machine already uses the encrypted folder.": + "머신이 이미 암호화된 폴더를 사용합니다.", + "The machine does not use the encrypted folder - nothing to roll back.": + "머신이 암호화된 폴더를 사용하지 않습니다 - 되돌릴 것이 없습니다.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "머신이 암호화된 폴더를 사용하지 않습니다 - 암호화되지 않은 폴더가 백업입니다. 삭제하지 않습니다.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "머신의 rclone.conf에 원격 저장소 '%@'이(가) 없습니다 - 실행: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "머신이 이 앱의 템플릿이 아니거나 %@을(를) 사용하지 않습니다(먼저 cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "머신이 이제 %@을(를) 사용하지만 %@을(를) 업데이트할 수 없습니다(%@) - 다른 무엇이 vm-setup을 실행하기 전에 고치십시오.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "머신이 이제 %@을(를) 사용합니다. %@은(는) 남겨 둡니다. 자동 백업을 다시 켜십시오: sudo tmutil enable", + "The password must have at least 12 characters.": + "암호는 12자 이상이어야 합니다.", + "The passwords differ - nothing was changed.": + "암호가 서로 다릅니다 - 아무것도 바꾸지 않았습니다.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "반대 방향 전환: 변경된 부분을 암호화되지 않은 폴더로 복호화하고 다시 그 폴더를 사용합니다.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "암호화된 폴더로의 업로드가 끝나지 않았습니다(또는 rclone이 응답하지 않았습니다) - 삭제하지 않습니다.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine이 백업 중입니다. 자동 백업을 끄고(sudo tmutil disable), 중지한 다음(tmutil stopbackup) 이 명령을 다시 실행하십시오.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Time Machine을 중지한 상태에서: 마지막 동기화, cryptcheck, 그다음 머신이 암호화된 폴더를 사용합니다. 암호화되지 않은 폴더는 남습니다.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "암호화 암호와 솔트가 없으면 암호화된 백업을 읽을 수 없습니다. 둘 다 이 Mac 밖(암호 관리자, 종이)에 보관한 다음 --secrets-saved-elsewhere로 확인하십시오.", + "rclone obscure failed.": + "rclone obscure에 실패했습니다.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil이 응답하지 않습니다 - Time Machine 상태를 알 수 없는 동안에는 전환하지 않습니다.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ms.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ms.swift new file mode 100644 index 0000000..9eae8af --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ms.swift @@ -0,0 +1,121 @@ +// Malay translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let msEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ dan metadatanya berada dalam tong sampah Google Drive (disimpan 30 hari, masih dikira dalam kuota).", + "'%@' is already in %@ - not replacing its password.": + "'%@' sudah ada dalam %@ - kata laluannya tidak diganti.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' sudah ada dalam rclone.conf mesin - kata laluannya tidak diganti.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ mengandungi folder yang tidak dapat dibaca oleh kunci ini: kata laluan atau garam lain dalam mesin, atau sisa cubaan terdahulu. Bandingkan rclone.conf mesin dengan rclone.conf Mac; sisa perlu dibuang daripada %@ secara manual.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' tidak lengkap atau tiada dalam rclone.conf Mac - jalankan: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ memerlukan rclone.conf sendiri - tetapkan RCLONE_CONFIG kepada salinan, bukan %@. Entri Rantai Kuncinya ialah perkhidmatan %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "Bahagian '%@' tidak dapat dibaca pada Mac atau dalam mesin.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "'%@' mesin (kata laluan, garam atau folder) berbeza daripada Mac - salinan hanya dapat dibaca dengan kunci mesin. Tiada apa-apa dipindahkan.", + "A file was restored from the encrypted backup.": + "Satu fail telah dipulihkan daripada sandaran tersulit.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@' (%@) ditambah pada Mac dan dalam mesin; kata laluan dan garam berada dalam Rantai Kunci (perkhidmatan %@).\nGARAM: %@\nSimpan kata laluan DAN garam ini di luar Mac ini sekarang - tanpa kedua-duanya sandaran tersulit tidak dapat dibaca. Seterusnya: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Menambah jauh tersulit pada Mac dan dalam mesin (meminta kata laluan). Tidak memindahkan data.", + "Again: ": + "Sekali lagi: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Menyalin sandaran tidak tersulit ke folder tersulit di latar belakang; sandaran diteruskan.", + "Copy: %@": + "Salinan: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ tanpa CLOUDMACHINE_VM_REMOTE akan menggunakan folder sandaran yang sedang digunakan - tetapkan kedua-duanya.", + "Could not add the remote to %@: %@": + "Tidak dapat menambah jauh ke %@: %@", + "Could not add the remote to the machine's rclone.conf.": + "Tidak dapat menambah jauh ke rclone.conf mesin.", + "Could not read %@.": + "Tidak dapat membaca %@.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Tidak dapat membaca bila Time Machine terakhir menyelesaikan sandaran (Capaian Cakera Penuh?) - tiada apa-apa dipadam.", + "Could not start the copy: %@": + "Tidak dapat memulakan salinan: %@", + "Could not write %@.": + "Tidak dapat menulis %@.", + "Deleting the plain folder failed: %@": + "Pemadaman folder tidak tersulit gagal: %@", + "Encrypted folder: %@": + "Folder tersulit: %@", + "Encryption password: ": + "Kata laluan penyulitan: ", + "Machine uses: %@": + "Mesin guna: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Memindahkan sandaran di Google Drive ke bawah penyulitan rclone: setup, copy, switch, kemudian delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Memindahkan folder tidak tersulit ke tong sampah Google Drive - hanya selepas satu sandaran selesai ke folder tersulit.", + "Moving the machine failed; it went back to %@: %@": + "Pertukaran mesin gagal; ia kembali ke %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Tiada jawapan daripada rclone dalam mesin - baris gilir muat naik tidak diketahui.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Belum ada sandaran yang selesai ke folder tersulit (terakhir %@, ditukar %@) - tiada apa-apa dipadam.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Belum semuanya berada di Google Drive (%@ MB belum dihantar, %@ sedang dimuat naik, %@ gagal). Tunggu sehingga muat naik selesai: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Pulihkan satu fail daripada sandaran tersulit dahulu (Time Machine, Layari), kemudian sahkan dengan --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Menunjukkan folder yang digunakan mesin dan keadaan salinan.", + "Switched: %@": + "Ditukar: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "Salinan sudah berjalan - lihat: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "Salinan berjalan dalam mesin (%@, log %@). Sandaran diteruskan sementara itu. Semak: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "Salinan ke folder tersulit tidak selesai dengan baik (%@) - jalankan: cloudmachine-agent vm-encrypt copy, dan tunggu sehingga selesai.", + "The encryption password and salt are kept outside this Mac.": + "Kata laluan penyulitan dan garam disimpan di luar Mac ini.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "Pengawal menahan Time Machine dalam jeda dan akan menyambungnya semasa pertukaran. Tunggu sehingga ia disambung semula dan muat naik selesai.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "Metadata imej belum disalin ke folder tersulit sejak sandaran terakhir - tiada apa-apa dipadam.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "Penyegerakan terakhir atau cryptcheck gagal - mesin TIDAK ditukar: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "Mesin sudah menggunakan folder tersulit - tiada apa-apa untuk ditukar.", + "The machine already uses the encrypted folder.": + "Mesin sudah menggunakan folder tersulit.", + "The machine does not use the encrypted folder - nothing to roll back.": + "Mesin tidak menggunakan folder tersulit - tiada apa-apa untuk dikembalikan.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "Mesin tidak menggunakan folder tersulit - folder tidak tersulit ialah sandarannya. Ia tidak dipadam.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "Mesin tiada jauh '%@' dalam rclone.conf nya - jalankan: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "Mesin tidak menggunakan templat apl ini atau tidak menggunakan %@ (cloudmachine-agent vm-update dahulu): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "Mesin kini menggunakan %@, tetapi %@ tidak dapat dikemas kini (%@) - betulkan sebelum apa-apa lagi menjalankan vm-setup.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "Mesin kini menggunakan %@. %@ dikekalkan. Hidupkan semula sandaran automatik: sudo tmutil enable", + "The password must have at least 12 characters.": + "Kata laluan mesti sekurang-kurangnya 12 aksara.", + "The passwords differ - nothing was changed.": + "Kata laluan berbeza - tiada apa-apa diubah.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "Pertukaran ke arah sebaliknya: menyahsulit perubahan kembali ke folder tidak tersulit dan menggunakannya semula.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "Muat naik ke folder tersulit belum selesai (atau rclone tidak menjawab) - tiada apa-apa dipadam.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine sedang membuat sandaran. Matikan sandaran automatik (sudo tmutil disable), hentikannya (tmutil stopbackup), kemudian jalankan ini semula.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Dengan Time Machine dihentikan: penyegerakan terakhir, cryptcheck, kemudian mesin menggunakan folder tersulit. Folder tidak tersulit kekal.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Tanpa kata laluan penyulitan dan garam sandaran tersulit tidak dapat dibaca. Simpan kedua-duanya di luar Mac ini (pengurus kata laluan, kertas), kemudian sahkan dengan --secrets-saved-elsewhere.", + "rclone obscure failed.": + "rclone obscure gagal.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil tidak menjawab - tiada pertukaran selagi keadaan Time Machine tidak diketahui.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-nb.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-nb.swift new file mode 100644 index 0000000..de8d647 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-nb.swift @@ -0,0 +1,121 @@ +// Norwegian Bokmal translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let nbEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ og metadataene ligger i papirkurven i Google Drive (beholdes i 30 dager og teller fortsatt med i kvoten).", + "'%@' is already in %@ - not replacing its password.": + "'%@' finnes allerede i %@ – passordet erstattes ikke.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' finnes allerede i maskinens rclone.conf – passordet erstattes ikke.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ inneholder mapper denne nøkkelen ikke kan lese: et annet passord eller salt i maskinen, eller rester fra et tidligere forsøk. Sammenlign maskinens rclone.conf med Mac-ens; rester må fjernes fra %@ for hånd.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' er ufullstendig eller mangler i Mac-ens rclone.conf – kjør: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ trenger sin egen rclone.conf – sett RCLONE_CONFIG til en kopi, ikke %@. Oppføringen i nøkkelringen er tjenesten %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "Kunne ikke lese delen '%@' på Mac-en eller i maskinen.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "Maskinens '%@' (passord, salt eller mappe) avviker fra Mac-ens – kopien ville bare kunne leses med maskinens nøkkel. Ingenting flyttes.", + "A file was restored from the encrypted backup.": + "En fil ble gjenopprettet fra den krypterte sikkerhetskopien.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "La til '%@' (%@) på Mac-en og i maskinen; passord og salt ligger i nøkkelringen (tjeneste %@).\nSALT: %@\nOppbevar passordet OG dette saltet utenfor denne Mac-en nå – uten begge kan den krypterte sikkerhetskopien ikke leses. Neste: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Legger til det krypterte fjernlageret på Mac-en og i maskinen (spør om passordet). Flytter ingen data.", + "Again: ": + "En gang til: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Kopierer den ukrypterte sikkerhetskopien til den krypterte mappen i bakgrunnen; sikkerhetskopieringen fortsetter.", + "Copy: %@": + "Kopi: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ uten CLOUDMACHINE_VM_REMOTE ville brukt mappen til den aktive sikkerhetskopien – sett begge.", + "Could not add the remote to %@: %@": + "Kunne ikke legge til fjernlageret i %@: %@", + "Could not add the remote to the machine's rclone.conf.": + "Kunne ikke legge til fjernlageret i maskinens rclone.conf.", + "Could not read %@.": + "Kunne ikke lese %@.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Kunne ikke lese når Time Machine sist fullførte en sikkerhetskopi (Full disktilgang?) – ingenting slettes.", + "Could not start the copy: %@": + "Kunne ikke starte kopieringen: %@", + "Could not write %@.": + "Kunne ikke skrive %@.", + "Deleting the plain folder failed: %@": + "Sletting av den ukrypterte mappen mislyktes: %@", + "Encrypted folder: %@": + "Kryptert mappe: %@", + "Encryption password: ": + "Krypteringspassord: ", + "Machine uses: %@": + "Maskinen bruker: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Flytter sikkerhetskopien på Google Drive inn under rclones kryptering: setup, copy, switch og deretter delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Flytter den ukrypterte mappen til papirkurven i Google Drive – først etter at en sikkerhetskopi er fullført i den krypterte.", + "Moving the machine failed; it went back to %@: %@": + "Omleggingen av maskinen mislyktes; den er tilbake på %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Ingen svar fra rclone i maskinen – opplastingskøen er ukjent.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Ingen sikkerhetskopi er fullført i den krypterte mappen ennå (siste %@, lagt om %@) – ingenting slettes.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Ikke alt er på Google Drive ennå (%@ MB ikke sendt, %@ lastes opp, %@ mislyktes). Vent til opplastingen er ferdig: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Gjenopprett først en fil fra den krypterte sikkerhetskopien (Time Machine, Bla gjennom), og bekreft deretter med --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Viser hvilken mappe maskinen bruker, og hvordan kopieringen ligger an.", + "Switched: %@": + "Lagt om: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "Kopieringen kjører allerede – se: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "Kopieringen kjører i maskinen (%@, logg %@). Sikkerhetskopieringen fortsetter imens. Sjekk: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "Kopieringen til den krypterte mappen ble ikke fullført riktig (%@) – kjør: cloudmachine-agent vm-encrypt copy, og vent på den.", + "The encryption password and salt are kept outside this Mac.": + "Krypteringspassordet og saltet oppbevares utenfor denne Mac-en.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "Vakten holder Time Machine på pause og ville gjenopptatt den under omleggingen. Vent til den er gjenopptatt og opplastingen er ferdig.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "Bildets metadata er ikke kopiert til den krypterte mappen siden siste sikkerhetskopi – ingenting slettes.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "Den siste synkroniseringen eller cryptcheck mislyktes – maskinen ble IKKE flyttet: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "Maskinen bruker allerede den krypterte mappen – ingenting å legge om.", + "The machine already uses the encrypted folder.": + "Maskinen bruker allerede den krypterte mappen.", + "The machine does not use the encrypted folder - nothing to roll back.": + "Maskinen bruker ikke den krypterte mappen – ingenting å rulle tilbake.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "Maskinen bruker ikke den krypterte mappen – den ukrypterte mappen er sikkerhetskopien. Den slettes ikke.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "Maskinen har ikke fjernlageret '%@' i sin rclone.conf – kjør: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "Maskinen bruker ikke denne appens mal eller bruker ikke %@ (først cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "Maskinen bruker nå %@, men %@ kunne ikke oppdateres (%@) – rett det før noe annet kjører vm-setup.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "Maskinen bruker nå %@. %@ beholdes. Slå på automatisk sikkerhetskopiering igjen: sudo tmutil enable", + "The password must have at least 12 characters.": + "Passordet må ha minst 12 tegn.", + "The passwords differ - nothing was changed.": + "Passordene er forskjellige – ingenting ble endret.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "Omleggingen den andre veien: dekrypterer det som er endret tilbake til den ukrypterte mappen og bruker den igjen.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "Opplastingen til den krypterte mappen er ikke ferdig (eller rclone svarte ikke) – ingenting slettes.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine sikkerhetskopierer nå. Slå av automatisk sikkerhetskopiering (sudo tmutil disable), stopp den (tmutil stopbackup), og kjør dette igjen.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Med Time Machine stoppet: siste synkronisering, cryptcheck, så bruker maskinen den krypterte mappen. Den ukrypterte blir værende.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Uten krypteringspassordet og saltet kan den krypterte sikkerhetskopien ikke leses. Oppbevar begge utenfor denne Mac-en (en passordbehandler, papir), og bekreft deretter med --secrets-saved-elsewhere.", + "rclone obscure failed.": + "rclone obscure mislyktes.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil svarte ikke – ingen omlegging så lenge tilstanden til Time Machine er ukjent.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-nl.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-nl.swift new file mode 100644 index 0000000..20f85bf --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-nl.swift @@ -0,0 +1,121 @@ +// Dutch translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let nlEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ en de metadata staan in de prullenbak van Google Drive (30 dagen bewaard, tellen nog mee voor de quota).", + "'%@' is already in %@ - not replacing its password.": + "'%@' staat al in %@ – het wachtwoord wordt niet vervangen.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' staat al in de rclone.conf van de machine – het wachtwoord wordt niet vervangen.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ bevat mappen die deze sleutel niet kan lezen: een ander wachtwoord of andere salt in de machine, of resten van een eerdere poging. Vergelijk de rclone.conf van de machine met die van de Mac; resten moeten met de hand uit %@ worden verwijderd.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' is onvolledig of ontbreekt in de rclone.conf van de Mac – voer uit: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ heeft een eigen rclone.conf nodig – stel RCLONE_CONFIG in op een kopie, niet op %@. Het sleutelhangeronderdeel is de dienst %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "Het gedeelte '%@' kon niet worden gelezen op de Mac of in de machine.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "'%@' van de machine (wachtwoord, salt of map) wijkt af van dat van de Mac – de kopie zou alleen met de sleutel van de machine leesbaar zijn. Er wordt niets verplaatst.", + "A file was restored from the encrypted backup.": + "Er is een bestand hersteld uit de versleutelde reservekopie.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@' (%@) toegevoegd op de Mac en in de machine; wachtwoord en salt staan in de sleutelhanger (dienst %@).\nSALT: %@\nBewaar nu het wachtwoord EN deze salt buiten deze Mac – zonder beide is de versleutelde reservekopie niet leesbaar. Volgende: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Voegt de versleutelde remote toe op de Mac en in de machine (vraagt om het wachtwoord). Verplaatst geen gegevens.", + "Again: ": + "Nogmaals: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Kopieert de onversleutelde reservekopie op de achtergrond naar de versleutelde map; reservekopieën gaan door.", + "Copy: %@": + "Kopie: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ zonder CLOUDMACHINE_VM_REMOTE zou de map van de actieve reservekopie gebruiken – stel beide in.", + "Could not add the remote to %@: %@": + "Kon de remote niet toevoegen aan %@: %@", + "Could not add the remote to the machine's rclone.conf.": + "Kon de remote niet toevoegen aan de rclone.conf van de machine.", + "Could not read %@.": + "Kon %@ niet lezen.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Kon niet lezen wanneer Time Machine voor het laatst een reservekopie heeft voltooid (Volledige schijftoegang?) – er wordt niets verwijderd.", + "Could not start the copy: %@": + "Kon de kopie niet starten: %@", + "Could not write %@.": + "Kon %@ niet schrijven.", + "Deleting the plain folder failed: %@": + "Verwijderen van de onversleutelde map mislukt: %@", + "Encrypted folder: %@": + "Versleutelde map: %@", + "Encryption password: ": + "Versleutelingswachtwoord: ", + "Machine uses: %@": + "Machine gebruikt: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Brengt de reservekopie op Google Drive onder de versleuteling van rclone: setup, copy, switch en daarna delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Verplaatst de onversleutelde map naar de prullenbak van Google Drive – pas nadat een reservekopie in de versleutelde is voltooid.", + "Moving the machine failed; it went back to %@: %@": + "Het omzetten van de machine is mislukt; ze gebruikt weer %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Geen antwoord van rclone in de machine – de uploadwachtrij is onbekend.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Er is nog geen reservekopie in de versleutelde map voltooid (laatste %@, omgezet %@) – er wordt niets verwijderd.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Nog niet alles staat op Google Drive (%@ MB niet verzonden, %@ bezig met uploaden, %@ mislukt). Wacht tot de upload klaar is: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Herstel eerst een bestand uit de versleutelde reservekopie (Time Machine, Blader) en bevestig daarna met --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Toont welke map de machine gebruikt en hoe de kopie ervoor staat.", + "Switched: %@": + "Omgezet: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "De kopie loopt al – zie: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "De kopie loopt in de machine (%@, log %@). Reservekopieën gaan ondertussen door. Controleren: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "De kopie naar de versleutelde map is niet goed afgerond (%@) – voer uit: cloudmachine-agent vm-encrypt copy, en wacht erop.", + "The encryption password and salt are kept outside this Mac.": + "Het versleutelingswachtwoord en de salt worden buiten deze Mac bewaard.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "De bewaker houdt Time Machine gepauzeerd en zou die tijdens het omzetten hervatten. Wacht tot die is hervat en de upload klaar is.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "De metadata van de schijfkopie zijn sinds de laatste reservekopie niet naar de versleutelde map gekopieerd – er wordt niets verwijderd.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "De laatste synchronisatie of cryptcheck is mislukt – de machine is NIET verplaatst: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "De machine gebruikt de versleutelde map al – niets om te zetten.", + "The machine already uses the encrypted folder.": + "De machine gebruikt de versleutelde map al.", + "The machine does not use the encrypted folder - nothing to roll back.": + "De machine gebruikt de versleutelde map niet – niets terug te draaien.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "De machine gebruikt de versleutelde map niet – de onversleutelde map is de reservekopie. Die wordt niet verwijderd.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "De machine heeft geen remote '%@' in haar rclone.conf – voer uit: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "De machine draait niet op het sjabloon van deze app of gebruikt %@ niet (eerst cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "De machine gebruikt nu %@, maar %@ kon niet worden bijgewerkt (%@) – herstel dat voordat iets anders vm-setup uitvoert.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "De machine gebruikt nu %@. %@ blijft behouden. Zet automatische reservekopieën weer aan: sudo tmutil enable", + "The password must have at least 12 characters.": + "Het wachtwoord moet minstens 12 tekens hebben.", + "The passwords differ - nothing was changed.": + "De wachtwoorden verschillen – er is niets gewijzigd.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "Het omzetten de andere kant op: ontsleutelt wat er veranderd is terug naar de onversleutelde map en gebruikt die weer.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "De upload naar de versleutelde map is niet klaar (of rclone antwoordde niet) – er wordt niets verwijderd.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine maakt een reservekopie. Zet automatische reservekopieën uit (sudo tmutil disable), stop die (tmutil stopbackup) en voer dit daarna opnieuw uit.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Met Time Machine gestopt: laatste synchronisatie, cryptcheck, daarna gebruikt de machine de versleutelde map. De onversleutelde blijft.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Zonder het versleutelingswachtwoord en de salt is de versleutelde reservekopie niet leesbaar. Bewaar beide buiten deze Mac (een wachtwoordbeheerder, papier) en bevestig daarna met --secrets-saved-elsewhere.", + "rclone obscure failed.": + "rclone obscure is mislukt.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil antwoordde niet – geen omzetting zolang de status van Time Machine onbekend is.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-pt-BR.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-pt-BR.swift new file mode 100644 index 0000000..0dd7c42 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-pt-BR.swift @@ -0,0 +1,121 @@ +// Brazilian Portuguese translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let ptBREncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ e seus metadados estão na lixeira do Google Drive (mantidos por 30 dias, ainda contam na cota).", + "'%@' is already in %@ - not replacing its password.": + "'%@' já está em %@ – a senha não será substituída.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' já está no rclone.conf da máquina – a senha não será substituída.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ contém pastas que esta chave não consegue ler: outra senha ou sal na máquina, ou restos de uma tentativa anterior. Compare o rclone.conf da máquina com o do Mac; os restos precisam ser removidos de %@ manualmente.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' está incompleto ou ausente no rclone.conf do Mac – execute: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ precisa do próprio rclone.conf – defina RCLONE_CONFIG como uma cópia, não %@. A entrada dela nas Chaves é o serviço %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "Não foi possível ler a seção '%@' no Mac ou na máquina.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "'%@' da máquina (senha, sal ou pasta) é diferente do Mac – a cópia só poderia ser lida com a chave da máquina. Nada será movido.", + "A file was restored from the encrypted backup.": + "Um arquivo foi restaurado do backup criptografado.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@' (%@) adicionado no Mac e na máquina; a senha e o sal estão nas Chaves (serviço %@).\nSAL: %@\nGuarde agora a senha E este sal fora deste Mac – sem os dois o backup criptografado não pode ser lido. Próximo: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Adiciona o remoto criptografado no Mac e na máquina (pede a senha). Não move dados.", + "Again: ": + "De novo: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Copia o backup não criptografado para a pasta criptografada, em segundo plano; os backups continuam.", + "Copy: %@": + "Cópia: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ sem CLOUDMACHINE_VM_REMOTE usaria a pasta do backup em uso – defina as duas.", + "Could not add the remote to %@: %@": + "Não foi possível adicionar o remoto a %@: %@", + "Could not add the remote to the machine's rclone.conf.": + "Não foi possível adicionar o remoto ao rclone.conf da máquina.", + "Could not read %@.": + "Não foi possível ler %@.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Não foi possível saber quando o Time Machine concluiu o último backup (Acesso Total ao Disco?) – nada será apagado.", + "Could not start the copy: %@": + "Não foi possível iniciar a cópia: %@", + "Could not write %@.": + "Não foi possível gravar %@.", + "Deleting the plain folder failed: %@": + "Falha ao apagar a pasta não criptografada: %@", + "Encrypted folder: %@": + "Pasta criptografada: %@", + "Encryption password: ": + "Senha de criptografia: ", + "Machine uses: %@": + "A máquina usa: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Coloca o backup no Google Drive sob a criptografia do rclone: setup, copy, switch e depois delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Move a pasta não criptografada para a lixeira do Google Drive – só depois que um backup terminar na criptografada.", + "Moving the machine failed; it went back to %@: %@": + "Falha ao mudar a máquina; ela voltou para %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Sem resposta do rclone na máquina – a fila de envio é desconhecida.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Nenhum backup terminou ainda na pasta criptografada (último %@, mudança %@) – nada será apagado.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Ainda não está tudo no Google Drive (%@ MB não enviados, %@ enviando, %@ com falha). Aguarde o fim do envio: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Restaure primeiro um arquivo do backup criptografado (Time Machine, Navegar) e depois confirme com --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Mostra qual pasta a máquina usa e o estado da cópia.", + "Switched: %@": + "Mudança: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "A cópia já está em andamento – veja: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "A cópia está sendo executada na máquina (%@, log %@). Enquanto isso, os backups continuam. Verificar: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "A cópia para a pasta criptografada não terminou bem (%@) – execute: cloudmachine-agent vm-encrypt copy e aguarde.", + "The encryption password and salt are kept outside this Mac.": + "A senha de criptografia e o sal estão guardados fora deste Mac.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "O vigia mantém o Time Machine em pausa e o retomaria durante a mudança. Aguarde até que ele seja retomado e o envio termine.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "Os metadados da imagem não foram copiados para a pasta criptografada desde o último backup – nada será apagado.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "A última sincronização ou o cryptcheck falhou – a máquina NÃO foi mudada: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "A máquina já usa a pasta criptografada – nada para mudar.", + "The machine already uses the encrypted folder.": + "A máquina já usa a pasta criptografada.", + "The machine does not use the encrypted folder - nothing to roll back.": + "A máquina não usa a pasta criptografada – nada para reverter.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "A máquina não usa a pasta criptografada – a pasta não criptografada é o backup. Ela não será apagada.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "A máquina não tem o remoto '%@' no seu rclone.conf – execute: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "A máquina não está no modelo deste app ou não usa %@ (primeiro cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "A máquina agora usa %@, mas não foi possível atualizar %@ (%@) – corrija isso antes que algo execute vm-setup.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "A máquina agora usa %@. %@ é mantida. Reative os backups automáticos: sudo tmutil enable", + "The password must have at least 12 characters.": + "A senha deve ter pelo menos 12 caracteres.", + "The passwords differ - nothing was changed.": + "As senhas são diferentes – nada foi alterado.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "A mudança no sentido inverso: descriptografa o que mudou de volta para a pasta não criptografada e passa a usá-la de novo.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "O envio para a pasta criptografada não terminou (ou o rclone não respondeu) – nada será apagado.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "O Time Machine está fazendo backup. Desative os backups automáticos (sudo tmutil disable), pare-o (tmutil stopbackup) e execute isto de novo.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Com o Time Machine parado: última sincronização, cryptcheck e depois a máquina usa a pasta criptografada. A não criptografada fica.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Sem a senha de criptografia e o sal o backup criptografado não pode ser lido. Guarde os dois fora deste Mac (um gerenciador de senhas, papel) e depois confirme com --secrets-saved-elsewhere.", + "rclone obscure failed.": + "Falha no rclone obscure.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "O tmutil não respondeu – sem mudança enquanto o estado do Time Machine for desconhecido.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ru.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ru.swift new file mode 100644 index 0000000..b8fb56e --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-ru.swift @@ -0,0 +1,121 @@ +// Russian translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let ruEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ вместе с метаданными находится в корзине Google Drive (хранится 30 дней и по-прежнему учитывается в квоте).", + "'%@' is already in %@ - not replacing its password.": + "'%@' уже есть в %@ — его пароль не заменяется.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' уже есть в rclone.conf машины — его пароль не заменяется.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "В %@ есть папки, которые этот ключ не может прочитать: другой пароль или соль в машине либо остатки прежней попытки. Сравните rclone.conf машины с файлом на Mac; остатки нужно удалить из %@ вручную.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' неполон или отсутствует в rclone.conf на Mac — выполните: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ нужен собственный rclone.conf — укажите в RCLONE_CONFIG копию, а не %@. Её запись в связке ключей — служба %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "Не удалось прочитать раздел '%@' на Mac или в машине.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "'%@' в машине (пароль, соль или папка) отличается от Mac — копию можно было бы прочитать только ключом машины. Ничего не переносится.", + "A file was restored from the encrypted backup.": + "Файл восстановлен из зашифрованной резервной копии.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@' (%@) добавлен на Mac и в машину; пароль и соль в связке ключей (служба %@).\nСОЛЬ: %@\nСохраните сейчас пароль И эту соль вне этого Mac — без обоих зашифрованную копию не прочитать. Далее: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Добавляет зашифрованное удалённое хранилище на Mac и в машину (запрашивает пароль). Данные не переносит.", + "Again: ": + "Ещё раз: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Копирует незашифрованную резервную копию в зашифрованную папку в фоне; резервное копирование продолжается.", + "Copy: %@": + "Копия: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ без CLOUDMACHINE_VM_REMOTE использовал бы папку действующей резервной копии — задайте обе.", + "Could not add the remote to %@: %@": + "Не удалось добавить удалённое хранилище в %@: %@", + "Could not add the remote to the machine's rclone.conf.": + "Не удалось добавить удалённое хранилище в rclone.conf машины.", + "Could not read %@.": + "Не удалось прочитать %@.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Не удалось узнать, когда Time Machine в последний раз завершил резервное копирование (Полный доступ к диску?) — ничего не удаляется.", + "Could not start the copy: %@": + "Не удалось запустить копирование: %@", + "Could not write %@.": + "Не удалось записать %@.", + "Deleting the plain folder failed: %@": + "Не удалось удалить незашифрованную папку: %@", + "Encrypted folder: %@": + "Зашифр. папка: %@", + "Encryption password: ": + "Пароль шифрования: ", + "Machine uses: %@": + "Машина использует: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Переносит резервную копию на Google Drive под шифрование rclone: setup, copy, switch, затем delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Перемещает незашифрованную папку в корзину Google Drive — только после того, как резервная копия завершилась в зашифрованную.", + "Moving the machine failed; it went back to %@: %@": + "Не удалось переключить машину; она вернулась к %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Нет ответа от rclone в машине — очередь отправки неизвестна.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "В зашифрованную папку ещё не завершилась ни одна резервная копия (последняя %@, переключение %@) — ничего не удаляется.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Ещё не всё на Google Drive (%@ МБ не отправлено, %@ отправляется, %@ с ошибкой). Дождитесь окончания отправки: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Сначала восстановите файл из зашифрованной копии (Time Machine, Просмотреть), затем подтвердите с --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Показывает, какую папку использует машина, и состояние копирования.", + "Switched: %@": + "Переключено: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "Копирование уже идёт — см.: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "Копирование идёт в машине (%@, журнал %@). Резервное копирование тем временем продолжается. Проверить: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "Копирование в зашифрованную папку не завершилось успешно (%@) — выполните: cloudmachine-agent vm-encrypt copy и дождитесь его.", + "The encryption password and salt are kept outside this Mac.": + "Пароль шифрования и соль хранятся вне этого Mac.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "Страж держит Time Machine на паузе и возобновил бы его во время переключения. Дождитесь возобновления и окончания отправки.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "Метаданные образа не копировались в зашифрованную папку после последней резервной копии — ничего не удаляется.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "Последняя синхронизация или cryptcheck не удалась — машина НЕ переключена: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "Машина уже использует зашифрованную папку — переключать нечего.", + "The machine already uses the encrypted folder.": + "Машина уже использует зашифрованную папку.", + "The machine does not use the encrypted folder - nothing to roll back.": + "Машина не использует зашифрованную папку — откатывать нечего.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "Машина не использует зашифрованную папку — резервная копия в незашифрованной папке. Она не удаляется.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "В rclone.conf машины нет удалённого хранилища '%@' — выполните: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "Машина не на шаблоне этого приложения или не использует %@ (сначала cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "Машина теперь использует %@, но %@ не удалось обновить (%@) — исправьте это, прежде чем что-либо запустит vm-setup.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "Машина теперь использует %@. %@ сохранена. Снова включите автоматическое резервное копирование: sudo tmutil enable", + "The password must have at least 12 characters.": + "Пароль должен содержать не менее 12 символов.", + "The passwords differ - nothing was changed.": + "Пароли различаются — ничего не изменено.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "Переключение в обратную сторону: расшифровывает изменённое обратно в незашифрованную папку и снова использует её.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "Отправка в зашифрованную папку не завершена (или rclone не ответил) — ничего не удаляется.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine выполняет резервное копирование. Отключите автоматическое копирование (sudo tmutil disable), остановите его (tmutil stopbackup) и запустите это снова.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "При остановленном Time Machine: последняя синхронизация, cryptcheck, затем машина использует зашифрованную папку. Незашифрованная остаётся.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Без пароля шифрования и соли зашифрованную копию не прочитать. Храните оба вне этого Mac (менеджер паролей, бумага), затем подтвердите с --secrets-saved-elsewhere.", + "rclone obscure failed.": + "Ошибка rclone obscure.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil не ответил — переключение не выполняется, пока состояние Time Machine неизвестно.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-sv.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-sv.swift new file mode 100644 index 0000000..72bbf5f --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-sv.swift @@ -0,0 +1,121 @@ +// Swedish translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let svEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ och dess metadata ligger i papperskorgen i Google Drive (sparas i 30 dagar och räknas fortfarande in i kvoten).", + "'%@' is already in %@ - not replacing its password.": + "'%@' finns redan i %@ – lösenordet ersätts inte.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' finns redan i maskinens rclone.conf – lösenordet ersätts inte.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ innehåller mappar som den här nyckeln inte kan läsa: ett annat lösenord eller salt i maskinen, eller rester från ett tidigare försök. Jämför maskinens rclone.conf med Macens; rester måste tas bort från %@ för hand.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' är ofullständig eller saknas i Macens rclone.conf – kör: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ behöver en egen rclone.conf – sätt RCLONE_CONFIG till en kopia, inte %@. Dess post i nyckelringen är tjänsten %@.", + "Could not read the '%@' section on the Mac or in the machine.": + "Avsnittet '%@' kunde inte läsas på Macen eller i maskinen.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "Maskinens '%@' (lösenord, salt eller mapp) skiljer sig från Macens – kopian skulle bara kunna läsas med maskinens nyckel. Inget flyttas.", + "A file was restored from the encrypted backup.": + "En fil återställdes från den krypterade säkerhetskopian.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@' (%@) har lagts till på Macen och i maskinen; lösenord och salt finns i nyckelringen (tjänst %@).\nSALT: %@\nSpara nu lösenordet OCH detta salt utanför den här Macen – utan båda går den krypterade säkerhetskopian inte att läsa. Nästa: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Lägger till den krypterade fjärrplatsen på Macen och i maskinen (frågar efter lösenordet). Flyttar inga data.", + "Again: ": + "Igen: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Kopierar den okrypterade säkerhetskopian till den krypterade mappen i bakgrunden; säkerhetskopieringen fortsätter.", + "Copy: %@": + "Kopia: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ utan CLOUDMACHINE_VM_REMOTE skulle använda mappen för den aktiva säkerhetskopian – sätt båda.", + "Could not add the remote to %@: %@": + "Kunde inte lägga till fjärrplatsen i %@: %@", + "Could not add the remote to the machine's rclone.conf.": + "Kunde inte lägga till fjärrplatsen i maskinens rclone.conf.", + "Could not read %@.": + "Kunde inte läsa %@.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Kunde inte läsa när Time Machine senast slutförde en säkerhetskopia (Full åtkomst till disken?) – inget raderas.", + "Could not start the copy: %@": + "Kunde inte starta kopieringen: %@", + "Could not write %@.": + "Kunde inte skriva %@.", + "Deleting the plain folder failed: %@": + "Radering av den okrypterade mappen misslyckades: %@", + "Encrypted folder: %@": + "Krypterad mapp: %@", + "Encryption password: ": + "Krypteringslösenord: ", + "Machine uses: %@": + "Maskinen använder: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Flyttar säkerhetskopian på Google Drive till rclones kryptering: setup, copy, switch och sedan delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Flyttar den okrypterade mappen till papperskorgen i Google Drive – först när en säkerhetskopia har slutförts i den krypterade.", + "Moving the machine failed; it went back to %@: %@": + "Omläggningen av maskinen misslyckades; den är tillbaka på %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Inget svar från rclone i maskinen – uppladdningskön är okänd.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Ingen säkerhetskopia har slutförts i den krypterade mappen än (senaste %@, omlagd %@) – inget raderas.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Allt finns inte på Google Drive än (%@ MB inte skickade, %@ laddas upp, %@ misslyckade). Vänta tills uppladdningen är klar: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Återställ först en fil från den krypterade säkerhetskopian (Time Machine, Bläddra) och bekräfta sedan med --restore-checked.", + "Shows which folder the machine uses and the copy.": + "Visar vilken mapp maskinen använder och hur kopieringen går.", + "Switched: %@": + "Omlagd: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "Kopieringen körs redan – se: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "Kopieringen körs i maskinen (%@, logg %@). Säkerhetskopieringen fortsätter under tiden. Kontrollera: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "Kopieringen till den krypterade mappen avslutades inte korrekt (%@) – kör: cloudmachine-agent vm-encrypt copy, och vänta på den.", + "The encryption password and salt are kept outside this Mac.": + "Krypteringslösenordet och saltet förvaras utanför den här Macen.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "Vakten håller Time Machine pausad och skulle återuppta den under omläggningen. Vänta tills den har återupptagits och uppladdningen är klar.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "Avbildningens metadata har inte kopierats till den krypterade mappen sedan senaste säkerhetskopian – inget raderas.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "Den sista synkroniseringen eller cryptcheck misslyckades – maskinen flyttades INTE: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "Maskinen använder redan den krypterade mappen – inget att lägga om.", + "The machine already uses the encrypted folder.": + "Maskinen använder redan den krypterade mappen.", + "The machine does not use the encrypted folder - nothing to roll back.": + "Maskinen använder inte den krypterade mappen – inget att rulla tillbaka.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "Maskinen använder inte den krypterade mappen – den okrypterade mappen är säkerhetskopian. Den raderas inte.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "Maskinen har ingen fjärrplats '%@' i sin rclone.conf – kör: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "Maskinen använder inte appens mall eller använder inte %@ (först cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "Maskinen använder nu %@, men %@ kunde inte uppdateras (%@) – åtgärda det innan något annat kör vm-setup.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "Maskinen använder nu %@. %@ behålls. Slå på automatisk säkerhetskopiering igen: sudo tmutil enable", + "The password must have at least 12 characters.": + "Lösenordet måste ha minst 12 tecken.", + "The passwords differ - nothing was changed.": + "Lösenorden skiljer sig åt – inget har ändrats.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "Omläggningen åt andra hållet: dekrypterar det som ändrats tillbaka till den okrypterade mappen och använder den igen.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "Uppladdningen till den krypterade mappen är inte klar (eller rclone svarade inte) – inget raderas.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine säkerhetskopierar just nu. Stäng av automatisk säkerhetskopiering (sudo tmutil disable), stoppa den (tmutil stopbackup) och kör sedan detta igen.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Med Time Machine stoppad: sista synkronisering, cryptcheck, sedan använder maskinen den krypterade mappen. Den okrypterade finns kvar.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Utan krypteringslösenordet och saltet går den krypterade säkerhetskopian inte att läsa. Förvara båda utanför den här Macen (en lösenordshanterare, papper) och bekräfta sedan med --secrets-saved-elsewhere.", + "rclone obscure failed.": + "rclone obscure misslyckades.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil svarade inte – ingen omläggning så länge Time Machines tillstånd är okänt.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-th.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-th.swift new file mode 100644 index 0000000..4d063c7 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-th.swift @@ -0,0 +1,121 @@ +// Thai translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let thEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ และข้อมูลเมตาอยู่ในถังขยะของ Google Drive (เก็บไว้ 30 วัน และยังนับรวมในโควตา)", + "'%@' is already in %@ - not replacing its password.": + "'%@' มีอยู่แล้วใน %@ - จะไม่แทนที่รหัสผ่าน", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' มีอยู่แล้วใน rclone.conf ของเครื่องเสมือน - จะไม่แทนที่รหัสผ่าน", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ มีโฟลเดอร์ที่คีย์นี้อ่านไม่ได้: รหัสผ่านหรือซอลต์ในเครื่องเสมือนต่างกัน หรือเป็นเศษจากการลองครั้งก่อน ให้เปรียบเทียบ rclone.conf ของเครื่องเสมือนกับของ Mac ส่วนเศษที่เหลือต้องลบออกจาก %@ ด้วยตนเอง", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@' ไม่สมบูรณ์หรือไม่มีใน rclone.conf ของ Mac - เรียกใช้: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ ต้องมี rclone.conf ของตัวเอง - ตั้ง RCLONE_CONFIG เป็นสำเนา ไม่ใช่ %@ รายการในพวงกุญแจของอินสแตนซ์นี้คือบริการ %@", + "Could not read the '%@' section on the Mac or in the machine.": + "อ่านส่วน '%@' บน Mac หรือในเครื่องเสมือนไม่ได้", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "'%@' ของเครื่องเสมือน (รหัสผ่าน ซอลต์ หรือโฟลเดอร์) ต่างจากของ Mac - สำเนาจะอ่านได้ด้วยคีย์ของเครื่องเสมือนเท่านั้น จะไม่ย้ายอะไร", + "A file was restored from the encrypted backup.": + "กู้คืนไฟล์จากข้อมูลสำรองที่เข้ารหัสแล้ว", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "เพิ่ม '%@' (%@) บน Mac และในเครื่องเสมือนแล้ว รหัสผ่านและซอลต์อยู่ในพวงกุญแจ (บริการ %@)\nซอลต์: %@\nเก็บรหัสผ่านและซอลต์นี้ไว้นอก Mac เครื่องนี้ตอนนี้เลย - หากไม่มีทั้งสองอย่างจะอ่านข้อมูลสำรองที่เข้ารหัสไม่ได้ ถัดไป: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "เพิ่ม remote ที่เข้ารหัสบน Mac และในเครื่องเสมือน (ถามรหัสผ่าน) ไม่ย้ายข้อมูล", + "Again: ": + "อีกครั้ง: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "คัดลอกข้อมูลสำรองที่ไม่เข้ารหัสไปยังโฟลเดอร์ที่เข้ารหัสในเบื้องหลัง การสำรองข้อมูลยังดำเนินต่อไป", + "Copy: %@": + "การคัดลอก: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@ ที่ไม่มี CLOUDMACHINE_VM_REMOTE จะใช้โฟลเดอร์ของข้อมูลสำรองที่ใช้งานอยู่ - ตั้งค่าทั้งสองตัว", + "Could not add the remote to %@: %@": + "เพิ่ม remote ลงใน %@ ไม่ได้: %@", + "Could not add the remote to the machine's rclone.conf.": + "เพิ่ม remote ลงใน rclone.conf ของเครื่องเสมือนไม่ได้", + "Could not read %@.": + "อ่าน %@ ไม่ได้", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "อ่านไม่ได้ว่า Time Machine สำรองข้อมูลเสร็จครั้งล่าสุดเมื่อใด (การเข้าถึงดิสก์แบบเต็ม?) - จะไม่ลบอะไร", + "Could not start the copy: %@": + "เริ่มการคัดลอกไม่ได้: %@", + "Could not write %@.": + "เขียน %@ ไม่ได้", + "Deleting the plain folder failed: %@": + "ลบโฟลเดอร์ที่ไม่เข้ารหัสไม่สำเร็จ: %@", + "Encrypted folder: %@": + "โฟลเดอร์เข้ารหัส: %@", + "Encryption password: ": + "รหัสผ่านการเข้ารหัส: ", + "Machine uses: %@": + "เครื่องเสมือนใช้: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "ย้ายข้อมูลสำรองบน Google Drive ไปอยู่ใต้การเข้ารหัสของ rclone: setup, copy, switch แล้วจึง delete-plain", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "ย้ายโฟลเดอร์ที่ไม่เข้ารหัสไปยังถังขยะของ Google Drive - หลังจากสำรองข้อมูลลงโฟลเดอร์ที่เข้ารหัสเสร็จแล้วเท่านั้น", + "Moving the machine failed; it went back to %@: %@": + "การย้ายเครื่องเสมือนไปยังโฟลเดอร์ใหม่ไม่สำเร็จ จึงกลับไปใช้ %@: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "ไม่มีคำตอบจาก rclone ในเครื่องเสมือน - ไม่ทราบสถานะคิวอัปโหลด", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "ยังไม่มีการสำรองข้อมูลใดเสร็จในโฟลเดอร์ที่เข้ารหัส (ล่าสุด %@ สลับเมื่อ %@) - จะไม่ลบอะไร", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "ยังไม่ได้อยู่บน Google Drive ทั้งหมด (%@ MB ยังไม่ส่ง, %@ กำลังอัปโหลด, %@ ล้มเหลว) รอจนอัปโหลดเสร็จ: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "กู้คืนไฟล์จากข้อมูลสำรองที่เข้ารหัสก่อน (Time Machine, เลือกดู) แล้วยืนยันด้วย --restore-checked", + "Shows which folder the machine uses and the copy.": + "แสดงว่าเครื่องเสมือนใช้โฟลเดอร์ใดและสถานะการคัดลอก", + "Switched: %@": + "สลับเมื่อ: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "การคัดลอกกำลังทำงานอยู่แล้ว - ดู: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "การคัดลอกทำงานในเครื่องเสมือน (%@, บันทึก %@) ระหว่างนี้การสำรองข้อมูลยังดำเนินต่อไป ตรวจสอบ: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "การคัดลอกไปยังโฟลเดอร์ที่เข้ารหัสไม่เสร็จสมบูรณ์ (%@) - เรียกใช้: cloudmachine-agent vm-encrypt copy แล้วรอให้เสร็จ", + "The encryption password and salt are kept outside this Mac.": + "รหัสผ่านการเข้ารหัสและซอลต์เก็บไว้นอก Mac เครื่องนี้", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "ตัวป้องกันบัฟเฟอร์กำลังหยุด Time Machine ไว้ชั่วคราว และจะให้ทำงานต่อระหว่างการสลับ รอจนกว่า Time Machine จะกลับมาทำงานและอัปโหลดเสร็จ", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "ข้อมูลเมตาของอิมเมจยังไม่ถูกคัดลอกไปยังโฟลเดอร์ที่เข้ารหัสตั้งแต่การสำรองข้อมูลครั้งล่าสุด - จะไม่ลบอะไร", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "การซิงค์ครั้งสุดท้ายหรือ cryptcheck ล้มเหลว - ไม่ได้ย้ายเครื่องเสมือนไปยังโฟลเดอร์ที่เข้ารหัส: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "เครื่องเสมือนใช้โฟลเดอร์ที่เข้ารหัสอยู่แล้ว - ไม่มีอะไรต้องสลับ", + "The machine already uses the encrypted folder.": + "เครื่องเสมือนใช้โฟลเดอร์ที่เข้ารหัสอยู่แล้ว", + "The machine does not use the encrypted folder - nothing to roll back.": + "เครื่องเสมือนไม่ได้ใช้โฟลเดอร์ที่เข้ารหัส - ไม่มีอะไรต้องย้อนกลับ", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "เครื่องเสมือนไม่ได้ใช้โฟลเดอร์ที่เข้ารหัส - โฟลเดอร์ที่ไม่เข้ารหัสคือข้อมูลสำรอง จะไม่ลบ", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "เครื่องเสมือนไม่มี remote '%@' ใน rclone.conf - เรียกใช้: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "เครื่องเสมือนไม่ได้ใช้เทมเพลตของแอปนี้ หรือไม่ได้ใช้ %@ (ทำ cloudmachine-agent vm-update ก่อน): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "ตอนนี้เครื่องเสมือนใช้ %@ แต่อัปเดต %@ ไม่ได้ (%@) - แก้ไขก่อนที่จะมีการเรียกใช้ vm-setup อีก", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "ตอนนี้เครื่องเสมือนใช้ %@ และเก็บ %@ ไว้ เปิดการสำรองข้อมูลอัตโนมัติอีกครั้ง: sudo tmutil enable", + "The password must have at least 12 characters.": + "รหัสผ่านต้องมีอย่างน้อย 12 ตัวอักษร", + "The passwords differ - nothing was changed.": + "รหัสผ่านไม่ตรงกัน - ไม่ได้เปลี่ยนแปลงอะไร", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "การสลับในทิศทางกลับ: ถอดรหัสส่วนที่เปลี่ยนกลับไปยังโฟลเดอร์ที่ไม่เข้ารหัสแล้วใช้โฟลเดอร์นั้นอีกครั้ง", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "การอัปโหลดไปยังโฟลเดอร์ที่เข้ารหัสยังไม่เสร็จ (หรือ rclone ไม่ตอบ) - จะไม่ลบอะไร", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine กำลังสำรองข้อมูล ปิดการสำรองข้อมูลอัตโนมัติ (sudo tmutil disable) หยุดการทำงาน (tmutil stopbackup) แล้วเรียกใช้คำสั่งนี้อีกครั้ง", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "เมื่อหยุด Time Machine แล้ว: ซิงค์ครั้งสุดท้าย, cryptcheck แล้วเครื่องเสมือนจะใช้โฟลเดอร์ที่เข้ารหัส โฟลเดอร์ที่ไม่เข้ารหัสยังอยู่", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "หากไม่มีรหัสผ่านการเข้ารหัสและซอลต์จะอ่านข้อมูลสำรองที่เข้ารหัสไม่ได้ เก็บทั้งสองไว้นอก Mac เครื่องนี้ (ตัวจัดการรหัสผ่าน, กระดาษ) แล้วยืนยันด้วย --secrets-saved-elsewhere", + "rclone obscure failed.": + "rclone obscure ล้มเหลว", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil ไม่ตอบ - จะไม่สลับขณะที่ไม่ทราบสถานะของ Time Machine", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-tr.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-tr.swift new file mode 100644 index 0000000..4399413 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-tr.swift @@ -0,0 +1,121 @@ +// Turkish translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let trEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ ve meta verileri Google Drive çöp kutusunda (30 gün saklanır, kotaya sayılmaya devam eder).", + "'%@' is already in %@ - not replacing its password.": + "'%@' zaten %@ içinde – parolası değiştirilmiyor.", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' zaten makinenin rclone.conf dosyasında – parolası değiştirilmiyor.", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ bu anahtarın okuyamadığı klasörler içeriyor: makinede başka bir parola veya tuz ya da önceki bir denemeden kalıntılar. Makinenin rclone.conf dosyasını Mac'tekiyle karşılaştırın; kalıntılar %@ içinden elle silinmelidir.", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "'%@', Mac'in rclone.conf dosyasında eksik veya yok – çalıştırın: cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ kendi rclone.conf dosyasına ihtiyaç duyar – RCLONE_CONFIG'i %@ yerine bir kopyaya ayarlayın. Anahtar Zinciri kaydı %@ hizmetidir.", + "Could not read the '%@' section on the Mac or in the machine.": + "'%@' bölümü Mac'te veya makinede okunamadı.", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "Makinedeki '%@' (parola, tuz veya klasör) Mac'tekinden farklı – kopya yalnızca makinenin anahtarıyla okunabilirdi. Hiçbir şey taşınmıyor.", + "A file was restored from the encrypted backup.": + "Şifreli yedekten bir dosya geri yüklendi.", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "'%@' (%@) Mac'e ve makineye eklendi; parola ve tuz Anahtar Zinciri'nde (hizmet %@).\nTUZ: %@\nParolayı VE bu tuzu şimdi bu Mac'in dışında saklayın – ikisi olmadan şifreli yedek okunamaz. Sonraki: cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "Şifreli uzak depoyu Mac'e ve makineye ekler (parolayı sorar). Veri taşımaz.", + "Again: ": + "Tekrar: ", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "Şifrelenmemiş yedeği arka planda şifreli klasöre kopyalar; yedeklemeler sürer.", + "Copy: %@": + "Kopya: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "CLOUDMACHINE_VM_INSTANCE=%@, CLOUDMACHINE_VM_REMOTE olmadan canlı yedeğin klasörünü kullanırdı – ikisini de ayarlayın.", + "Could not add the remote to %@: %@": + "Uzak depo %@ dosyasına eklenemedi: %@", + "Could not add the remote to the machine's rclone.conf.": + "Uzak depo makinenin rclone.conf dosyasına eklenemedi.", + "Could not read %@.": + "%@ okunamadı.", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "Time Machine'in son yedeği ne zaman bitirdiği okunamadı (Tam Disk Erişimi?) – hiçbir şey silinmiyor.", + "Could not start the copy: %@": + "Kopyalama başlatılamadı: %@", + "Could not write %@.": + "%@ yazılamadı.", + "Deleting the plain folder failed: %@": + "Şifrelenmemiş klasör silinemedi: %@", + "Encrypted folder: %@": + "Şifreli klasör: %@", + "Encryption password: ": + "Şifreleme parolası: ", + "Machine uses: %@": + "Makine kullanıyor: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "Google Drive'daki yedeği rclone şifrelemesinin altına taşır: setup, copy, switch, ardından delete-plain.", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "Şifrelenmemiş klasörü Google Drive çöp kutusuna taşır – yalnızca şifreli klasöre bir yedek tamamlandıktan sonra.", + "Moving the machine failed; it went back to %@: %@": + "Makine geçirilemedi; %@ klasörüne geri döndü: %@", + "No answer from rclone in the machine - the upload queue is unknown.": + "Makinedeki rclone yanıt vermedi – yükleme kuyruğu bilinmiyor.", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "Şifreli klasöre henüz hiçbir yedek tamamlanmadı (sonuncusu %@, geçiş %@) – hiçbir şey silinmiyor.", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "Henüz her şey Google Drive'da değil (%@ MB gönderilmedi, %@ yükleniyor, %@ başarısız). Yükleme bitene kadar bekleyin: cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "Önce şifreli yedekten bir dosya geri yükleyin (Time Machine, Göz At), ardından --restore-checked ile onaylayın.", + "Shows which folder the machine uses and the copy.": + "Makinenin hangi klasörü kullandığını ve kopyanın durumunu gösterir.", + "Switched: %@": + "Geçiş: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "Kopyalama zaten çalışıyor – bakın: cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "Kopyalama makinede çalışıyor (%@, günlük %@). Bu sırada yedeklemeler sürer. Denetleyin: cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "Şifreli klasöre kopyalama düzgün bitmedi (%@) – çalıştırın: cloudmachine-agent vm-encrypt copy ve bitmesini bekleyin.", + "The encryption password and salt are kept outside this Mac.": + "Şifreleme parolası ve tuz bu Mac'in dışında saklanıyor.", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "Bekçi Time Machine'i duraklatılmış tutuyor ve geçiş sırasında sürdürürdü. Sürdürülene ve yükleme bitene kadar bekleyin.", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "Görüntünün meta verileri son yedekten beri şifreli klasöre kopyalanmadı – hiçbir şey silinmiyor.", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "Son eşitleme veya cryptcheck başarısız oldu – makine TAŞINMADI: %@", + "The machine already uses the encrypted folder - nothing to switch.": + "Makine zaten şifreli klasörü kullanıyor – geçirilecek bir şey yok.", + "The machine already uses the encrypted folder.": + "Makine zaten şifreli klasörü kullanıyor.", + "The machine does not use the encrypted folder - nothing to roll back.": + "Makine şifreli klasörü kullanmıyor – geri alınacak bir şey yok.", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "Makine şifreli klasörü kullanmıyor – yedek, şifrelenmemiş klasördür. Silinmiyor.", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "Makinenin rclone.conf dosyasında '%@' uzak deposu yok – çalıştırın: cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "Makine bu uygulamanın şablonunda değil veya %@ kullanmıyor (önce cloudmachine-agent vm-update): %@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "Makine artık %@ kullanıyor, ancak %@ güncellenemedi (%@) – başka bir şey vm-setup çalıştırmadan önce düzeltin.", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "Makine artık %@ kullanıyor. %@ korunuyor. Otomatik yedeklemeleri yeniden açın: sudo tmutil enable", + "The password must have at least 12 characters.": + "Parola en az 12 karakter olmalıdır.", + "The passwords differ - nothing was changed.": + "Parolalar farklı – hiçbir şey değiştirilmedi.", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "Ters yönde geçiş: değişenleri şifrelenmemiş klasöre geri çözer ve onu yeniden kullanır.", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "Şifreli klasöre yükleme bitmedi (veya rclone yanıt vermedi) – hiçbir şey silinmiyor.", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine yedekleme yapıyor. Otomatik yedeklemeleri kapatın (sudo tmutil disable), durdurun (tmutil stopbackup), ardından bunu yeniden çalıştırın.", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "Time Machine durdurulmuşken: son eşitleme, cryptcheck, ardından makine şifreli klasörü kullanır. Şifrelenmemiş olan kalır.", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "Şifreleme parolası ve tuz olmadan şifreli yedek okunamaz. İkisini de bu Mac'in dışında saklayın (parola yöneticisi, kâğıt), ardından --secrets-saved-elsewhere ile onaylayın.", + "rclone obscure failed.": + "rclone obscure başarısız oldu.", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil yanıt vermedi – Time Machine'in durumu bilinmezken geçiş yapılmıyor.", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-zh-Hans.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-zh-Hans.swift new file mode 100644 index 0000000..a20ea24 --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-zh-Hans.swift @@ -0,0 +1,121 @@ +// Simplified Chinese translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let zhHansEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ 及其元数据位于 Google Drive 的回收站中(保留 30 天,仍计入配额)。", + "'%@' is already in %@ - not replacing its password.": + "'%@' 已在 %@ 中 - 不会替换其密码。", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' 已在虚拟机的 rclone.conf 中 - 不会替换其密码。", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ 中有此密钥无法读取的文件夹:虚拟机中的密码或盐不同,或是之前尝试留下的残余。请比较虚拟机的 rclone.conf 与 Mac 上的;残余需要手动从 %@ 中删除。", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "Mac 的 rclone.conf 中 '%@' 不完整或不存在 - 请运行:cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ 需要自己的 rclone.conf - 请将 RCLONE_CONFIG 设为一份副本,而不是 %@。它在钥匙串中的条目是服务 %@。", + "Could not read the '%@' section on the Mac or in the machine.": + "无法在 Mac 或虚拟机上读取 '%@' 部分。", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "虚拟机的 '%@'(密码、盐或文件夹)与 Mac 上的不同 - 副本将只能用虚拟机的密钥读取。不会移动任何内容。", + "A file was restored from the encrypted backup.": + "已从加密备份中恢复了一个文件。", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "已在 Mac 和虚拟机上添加 '%@'(%@);密码和盐在钥匙串中(服务 %@)。\n盐:%@\n请立即将密码和这个盐都保存在这台 Mac 之外 - 缺少任何一个都无法读取加密备份。下一步:cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "在 Mac 和虚拟机上添加加密的远程存储(会询问密码)。不移动数据。", + "Again: ": + "再输入一次:", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "在后台将未加密的备份复制到加密文件夹;备份照常进行。", + "Copy: %@": + "复制: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "不设置 CLOUDMACHINE_VM_REMOTE 的 CLOUDMACHINE_VM_INSTANCE=%@ 会使用正在使用的备份文件夹 - 请两者都设置。", + "Could not add the remote to %@: %@": + "无法将远程存储添加到 %@:%@", + "Could not add the remote to the machine's rclone.conf.": + "无法将远程存储添加到虚拟机的 rclone.conf。", + "Could not read %@.": + "无法读取 %@。", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "无法读取 Time Machine 上次完成备份的时间(完全磁盘访问权限?)- 不删除。", + "Could not start the copy: %@": + "无法开始复制:%@", + "Could not write %@.": + "无法写入 %@。", + "Deleting the plain folder failed: %@": + "删除未加密文件夹失败:%@", + "Encrypted folder: %@": + "加密文件夹: %@", + "Encryption password: ": + "加密密码:", + "Machine uses: %@": + "虚拟机使用: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "将 Google Drive 上的备份移到 rclone 加密之下:setup、copy、switch,然后 delete-plain。", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "将未加密文件夹移到 Google Drive 的回收站 - 仅在一次备份已完成到加密文件夹之后。", + "Moving the machine failed; it went back to %@: %@": + "切换虚拟机失败;它已回到 %@:%@", + "No answer from rclone in the machine - the upload queue is unknown.": + "虚拟机中的 rclone 没有响应 - 上传队列未知。", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "尚无备份完成到加密文件夹(最近一次 %@,切换于 %@)- 不删除。", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "尚未全部上传到 Google Drive(%@ MB 未发送,%@ 个上传中,%@ 个失败)。请等待上传完成:cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "请先从加密备份中恢复一个文件(Time Machine,浏览),然后用 --restore-checked 确认。", + "Shows which folder the machine uses and the copy.": + "显示虚拟机使用哪个文件夹以及复制状态。", + "Switched: %@": + "切换于: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "复制已在运行 - 查看:cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "复制正在虚拟机中运行(%@,日志 %@)。在此期间备份照常进行。检查:cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "复制到加密文件夹未能顺利完成(%@)- 请运行:cloudmachine-agent vm-encrypt copy,并等待其完成。", + "The encryption password and salt are kept outside this Mac.": + "加密密码和盐保存在这台 Mac 之外。", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "缓冲区守护程序让 Time Machine 处于暂停状态,并会在切换期间恢复它。请等到它恢复且上传完成。", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "自上次备份以来,映像的元数据尚未复制到加密文件夹 - 不删除。", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "最后一次同步或 cryptcheck 失败 - 虚拟机未切换:%@", + "The machine already uses the encrypted folder - nothing to switch.": + "虚拟机已在使用加密文件夹 - 无需切换。", + "The machine already uses the encrypted folder.": + "虚拟机已在使用加密文件夹。", + "The machine does not use the encrypted folder - nothing to roll back.": + "虚拟机未使用加密文件夹 - 无可回滚。", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "虚拟机未使用加密文件夹 - 未加密文件夹就是备份。不删除它。", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "虚拟机的 rclone.conf 中没有远程存储 '%@' - 请运行:cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "虚拟机不在此应用的模板上,或未使用 %@(请先运行 cloudmachine-agent vm-update):%@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "虚拟机现在使用 %@,但无法更新 %@(%@)- 请在其他任何程序运行 vm-setup 之前修正。", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "虚拟机现在使用 %@。%@ 予以保留。请重新打开自动备份:sudo tmutil enable", + "The password must have at least 12 characters.": + "密码至少需要 12 个字符。", + "The passwords differ - nothing was changed.": + "两次密码不同 - 未做任何更改。", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "反向切换:把已更改的内容解密回未加密文件夹,并重新使用它。", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "上传到加密文件夹尚未完成(或 rclone 没有响应)- 不删除。", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine 正在备份。请关闭自动备份(sudo tmutil disable),停止它(tmutil stopbackup),然后再次运行此命令。", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "在 Time Machine 停止时:最后一次同步、cryptcheck,然后虚拟机使用加密文件夹。未加密文件夹保留。", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "没有加密密码和盐就无法读取加密备份。请将两者都保存在这台 Mac 之外(密码管理器、纸张),然后用 --secrets-saved-elsewhere 确认。", + "rclone obscure failed.": + "rclone obscure 失败。", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil 没有响应 - 在 Time Machine 状态未知时不进行切换。", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-zh-Hant.swift b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-zh-Hant.swift new file mode 100644 index 0000000..216b40b --- /dev/null +++ b/mac-app/Sources/CloudMachineCore/L10nTranslations+Encryption-zh-Hant.swift @@ -0,0 +1,121 @@ +// Traditional Chinese translations of vm-encrypt (DriveEncryption), keyed by the English text. +extension L10nTranslations { + static let zhHantEncryption: [String: String] = [ + "%@ and its metadata are in Google Drive's bin (kept 30 days, still counted in the quota).": + "%@ 及其後設資料位於 Google Drive 的垃圾桶中(保留 30 天,仍計入配額)。", + "'%@' is already in %@ - not replacing its password.": + "'%@' 已在 %@ 中 - 不會取代其密碼。", + "'%@' is already in the machine's rclone.conf - not replacing its password.": + "'%@' 已在虛擬機器的 rclone.conf 中 - 不會取代其密碼。", + "%@ holds folders this key cannot read: another password or salt in the machine, or leftovers of an earlier try. Compare the machine's rclone.conf with the Mac's; leftovers have to be removed from %@ by hand.": + "%@ 中有此金鑰無法讀取的檔案夾:虛擬機器中的密碼或鹽不同,或是先前嘗試留下的殘餘。請比較虛擬機器的 rclone.conf 與 Mac 上的;殘餘需要手動從 %@ 中刪除。", + "'%@' is incomplete or missing in the Mac's rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "Mac 的 rclone.conf 中 '%@' 不完整或不存在 - 請執行:cloudmachine-agent vm-encrypt setup", + "CLOUDMACHINE_VM_INSTANCE=%@ needs its own rclone.conf - set RCLONE_CONFIG to a copy, not %@. Its Keychain entry is service %@.": + "CLOUDMACHINE_VM_INSTANCE=%@ 需要自己的 rclone.conf - 請將 RCLONE_CONFIG 設為一份副本,而不是 %@。它在鑰匙圈中的項目是服務 %@。", + "Could not read the '%@' section on the Mac or in the machine.": + "無法在 Mac 或虛擬機器上讀取 '%@' 區段。", + "The machine's '%@' (password, salt or folder) differs from the Mac's - the copy would be readable only with the machine's key. Not moving anything.": + "虛擬機器的 '%@'(密碼、鹽或檔案夾)與 Mac 上的不同 - 副本將只能用虛擬機器的金鑰讀取。不會移動任何內容。", + "A file was restored from the encrypted backup.": + "已從加密備份中回復了一個檔案。", + "Added '%@' (%@) on the Mac and in the machine; password and salt are in the Keychain (service %@).\nSALT: %@\nKeep the password AND this salt outside this Mac now - without both the encrypted backup cannot be read. Next: cloudmachine-agent vm-encrypt copy": + "已在 Mac 和虛擬機器上加入 '%@'(%@);密碼和鹽在鑰匙圈中(服務 %@)。\n鹽:%@\n請立即將密碼和這個鹽都保存在這台 Mac 之外 - 缺少任何一個都無法讀取加密備份。下一步:cloudmachine-agent vm-encrypt copy", + "Adds the encrypted remote on the Mac and in the machine (asks for the password). Moves no data.": + "在 Mac 和虛擬機器上加入加密的遠端儲存(會詢問密碼)。不移動資料。", + "Again: ": + "再輸入一次:", + "Copies the plain backup into the encrypted folder, in the background; backups go on.": + "在背景將未加密的備份拷貝到加密檔案夾;備份照常進行。", + "Copy: %@": + "拷貝: %@", + "CLOUDMACHINE_VM_INSTANCE=%@ without CLOUDMACHINE_VM_REMOTE would use the live backup's folder - set both.": + "未設定 CLOUDMACHINE_VM_REMOTE 的 CLOUDMACHINE_VM_INSTANCE=%@ 會使用正在使用的備份檔案夾 - 請兩者都設定。", + "Could not add the remote to %@: %@": + "無法將遠端儲存加入 %@:%@", + "Could not add the remote to the machine's rclone.conf.": + "無法將遠端儲存加入虛擬機器的 rclone.conf。", + "Could not read %@.": + "無法讀取 %@。", + "Could not read when Time Machine last finished a backup (Full Disk Access?) - not deleting.": + "無法讀取 Time Machine 上次完成備份的時間(「完整磁碟取用權限」?)- 不刪除。", + "Could not start the copy: %@": + "無法開始拷貝:%@", + "Could not write %@.": + "無法寫入 %@。", + "Deleting the plain folder failed: %@": + "刪除未加密檔案夾失敗:%@", + "Encrypted folder: %@": + "加密檔案夾: %@", + "Encryption password: ": + "加密密碼:", + "Machine uses: %@": + "虛擬機器使用: %@", + "Moves the backup on Google Drive under rclone encryption: setup, copy, switch, then delete-plain.": + "將 Google Drive 上的備份移到 rclone 加密之下:setup、copy、switch,然後 delete-plain。", + "Moves the plain folder to Google Drive's bin - only after a backup has finished into the encrypted one.": + "將未加密檔案夾移到 Google Drive 的垃圾桶 - 僅在一次備份已完成到加密檔案夾之後。", + "Moving the machine failed; it went back to %@: %@": + "切換虛擬機器失敗;它已回到 %@:%@", + "No answer from rclone in the machine - the upload queue is unknown.": + "虛擬機器中的 rclone 沒有回應 - 上傳佇列未知。", + "No backup has finished into the encrypted folder yet (last one %@, switched %@) - not deleting.": + "尚無備份完成到加密檔案夾(最近一次 %@,切換於 %@)- 不刪除。", + "Not everything is on Google Drive yet (%@ MB unsent, %@ uploading, %@ failed). Wait until the upload is done: cloudmachine-agent vm-status": + "尚未全部上傳到 Google Drive(%@ MB 未傳送,%@ 個上傳中,%@ 個失敗)。請等待上傳完成:cloudmachine-agent vm-status", + "Restore a file from the encrypted backup first (Time Machine, Browse), then confirm with --restore-checked.": + "請先從加密備份中回復一個檔案(Time Machine,瀏覽),然後用 --restore-checked 確認。", + "Shows which folder the machine uses and the copy.": + "顯示虛擬機器使用哪個檔案夾以及拷貝狀態。", + "Switched: %@": + "切換於: %@", + "The copy is already running - see: cloudmachine-agent vm-encrypt status": + "拷貝已在執行 - 查看:cloudmachine-agent vm-encrypt status", + "The copy runs in the machine (%@, log %@). Backups go on meanwhile. Check: cloudmachine-agent vm-encrypt status": + "拷貝正在虛擬機器中執行(%@,記錄 %@)。在此期間備份照常進行。檢查:cloudmachine-agent vm-encrypt status", + "The copy to the encrypted folder has not finished well (%@) - run: cloudmachine-agent vm-encrypt copy, and wait for it.": + "拷貝到加密檔案夾未能順利完成(%@)- 請執行:cloudmachine-agent vm-encrypt copy,並等待其完成。", + "The encryption password and salt are kept outside this Mac.": + "加密密碼和鹽保存在這台 Mac 之外。", + "The guard holds Time Machine paused and would resume it during the switch. Wait until it resumes and the upload is done.": + "緩衝區守護程式讓 Time Machine 處於暫停狀態,並會在切換期間繼續它。請等到它繼續且上傳完成。", + "The image metadata has not been copied to the encrypted folder since the last backup - not deleting.": + "自上次備份以來,映像檔的後設資料尚未拷貝到加密檔案夾 - 不刪除。", + "The last sync or cryptcheck failed - the machine was NOT moved: %@": + "最後一次同步或 cryptcheck 失敗 - 虛擬機器未切換:%@", + "The machine already uses the encrypted folder - nothing to switch.": + "虛擬機器已在使用加密檔案夾 - 無需切換。", + "The machine already uses the encrypted folder.": + "虛擬機器已在使用加密檔案夾。", + "The machine does not use the encrypted folder - nothing to roll back.": + "虛擬機器未使用加密檔案夾 - 無可還原。", + "The machine does not use the encrypted folder - the plain folder is the backup. Not deleting it.": + "虛擬機器未使用加密檔案夾 - 未加密檔案夾就是備份。不刪除它。", + "The machine has no '%@' remote in its rclone.conf - run: cloudmachine-agent vm-encrypt setup": + "虛擬機器的 rclone.conf 中沒有遠端儲存 '%@' - 請執行:cloudmachine-agent vm-encrypt setup", + "The machine is not on this app's template or does not use %@ (cloudmachine-agent vm-update first): %@": + "虛擬機器不在此 App 的範本上,或未使用 %@(請先執行 cloudmachine-agent vm-update):%@", + "The machine now uses %@, but %@ could not be updated (%@) - fix it before anything else runs vm-setup.": + "虛擬機器現在使用 %@,但無法更新 %@(%@)- 請在其他任何程式執行 vm-setup 之前修正。", + "The machine now uses %@. %@ is kept. Turn automatic backups back on: sudo tmutil enable": + "虛擬機器現在使用 %@。%@ 予以保留。請重新開啟自動備份:sudo tmutil enable", + "The password must have at least 12 characters.": + "密碼至少需要 12 個字元。", + "The passwords differ - nothing was changed.": + "兩次密碼不同 - 未做任何更改。", + "The switch the other way: decrypts what changed back into the plain folder and uses it again.": + "反向切換:把已更改的內容解密回未加密檔案夾,並重新使用它。", + "The upload to the encrypted folder is not finished (or rclone did not answer) - not deleting.": + "上傳到加密檔案夾尚未完成(或 rclone 沒有回應)- 不刪除。", + "Time Machine is backing up. Turn automatic backups off (sudo tmutil disable), stop it (tmutil stopbackup), then run this again.": + "Time Machine 正在備份。請關閉自動備份(sudo tmutil disable),停止它(tmutil stopbackup),然後再次執行此指令。", + "With Time Machine stopped: last sync, cryptcheck, then the machine uses the encrypted folder. The plain one stays.": + "在 Time Machine 停止時:最後一次同步、cryptcheck,然後虛擬機器使用加密檔案夾。未加密檔案夾保留。", + "Without the encryption password and salt the encrypted backup cannot be read. Keep both outside this Mac (a password manager, paper), then confirm with --secrets-saved-elsewhere.": + "沒有加密密碼和鹽就無法讀取加密備份。請將兩者都保存在這台 Mac 之外(密碼管理器、紙張),然後用 --secrets-saved-elsewhere 確認。", + "rclone obscure failed.": + "rclone obscure 失敗。", + "tmutil did not answer - not switching while Time Machine's state is unknown.": + "tmutil 沒有回應 - 在 Time Machine 狀態未知時不進行切換。", + ] +} diff --git a/mac-app/Sources/CloudMachineCore/RemoteConfigurer.swift b/mac-app/Sources/CloudMachineCore/RemoteConfigurer.swift index 3c843c8..e5f9f1a 100644 --- a/mac-app/Sources/CloudMachineCore/RemoteConfigurer.swift +++ b/mac-app/Sources/CloudMachineCore/RemoteConfigurer.swift @@ -377,7 +377,11 @@ public enum RemoteConfigurer { env["RCLONE_DRIVE_CLIENT_ID"] = clientID env["RCLONE_DRIVE_CLIENT_SECRET"] = clientSecret } - let path = VMLayout.remote.split(separator: ":", maxSplits: 1).last.map(String.init) ?? "" + // `:drive:` cannot decrypt, so with encryption the question is whether + // the encrypted folder has anything in it - its names are not readable. + let encrypted = DriveEncryption.isEnabled + let folder = encrypted ? DriveEncryption.cryptRoot : VMLayout.remote + let path = folder.split(separator: ":", maxSplits: 1).last.map(String.init) ?? "" guard let result = try? await ProcessRunner.run( CMTooling.managedRclonePath.path, ["lsf", "--dirs-only", ":drive:\(path)"], env: env, @@ -386,17 +390,20 @@ public enum RemoteConfigurer { return .unknown(L10n.tr("rclone did not answer")) } return classifyRemoteListing( - succeeded: result.succeeded, stdout: result.stdout, stderr: result.stderr) + succeeded: result.succeeded, stdout: result.stdout, stderr: result.stderr, + anyFolderCounts: encrypted) } - /// `lsf --dirs-only` of the folder -> whether the backup's `bands/` is there. + /// `lsf --dirs-only` of the folder -> whether the backup's `bands/` is there + /// (with `anyFolderCounts`, for an encrypted folder: any folder at all). /// A missing folder is an answer ("absent"), any other error is not. - static func classifyRemoteListing(succeeded: Bool, stdout: String, stderr: String) - -> RemotePresence - { + static func classifyRemoteListing( + succeeded: Bool, stdout: String, stderr: String, anyFolderCounts: Bool = false + ) -> RemotePresence { if succeeded { let present = stdout.split(whereSeparator: \.isNewline).contains { - $0.trimmingCharacters(in: .whitespaces) == "bands/" + let name = $0.trimmingCharacters(in: .whitespaces) + return anyFolderCounts ? name.hasSuffix("/") : name == "bands/" } return present ? .present : .absent } diff --git a/mac-app/Sources/CloudMachineCore/VMLayout.swift b/mac-app/Sources/CloudMachineCore/VMLayout.swift index fb5219f..0daf412 100644 --- a/mac-app/Sources/CloudMachineCore/VMLayout.swift +++ b/mac-app/Sources/CloudMachineCore/VMLayout.swift @@ -65,10 +65,24 @@ public enum VMLayout { /// `gdrive:CloudMachine/-tm`. The `-tm` suffix keeps it apart from /// `CloudMachine/`, where the retired disk-image layout kept its /// image - the two never share bands. - /// `CLOUDMACHINE_VM_REMOTE` replaces it whole (see `instanceName`). + /// Once `vm-encrypt switch` has moved the machine, it is the encrypted + /// `gdrive-crypt:tm` (`DriveEncryption`). public static var remote: String { - override("CLOUDMACHINE_VM_REMOTE") - ?? "\(DriveFolder.remoteName):\(DriveFolder.remoteRoot)/\(DriveFolder.name)-tm" + remote(plain: plainRemote, encrypted: DriveEncryption.isEnabled) + } + + static func remote(plain: String, encrypted: Bool) -> String { + encrypted ? DriveEncryption.encryptedRemote : plain + } + + /// The unencrypted folder, whichever one the machine uses now. + /// `CLOUDMACHINE_VM_REMOTE` replaces it whole (see `instanceName`). + public static var plainRemote: String { + plainRemote(override: override("CLOUDMACHINE_VM_REMOTE"), folder: DriveFolder.name) + } + + static func plainRemote(override: String?, folder: String) -> String { + override ?? "\(DriveFolder.remoteName):\(DriveFolder.remoteRoot)/\(folder)-tm" } /// `gdrive:CloudMachine/-tm-meta`: the image's files outside @@ -119,7 +133,12 @@ public enum VMLayout { if let custom = ProcessInfo.processInfo.environment["RCLONE_CONFIG"], !custom.isEmpty { return URL(fileURLWithPath: custom) } - return FileManager.default.homeDirectoryForCurrentUser + return defaultHostRcloneConfig + } + + /// `~/.config/rclone/rclone.conf`, the file `RCLONE_CONFIG` replaces. + public static var defaultHostRcloneConfig: URL { + FileManager.default.homeDirectoryForCurrentUser .appendingPathComponent(".config/rclone/rclone.conf") } diff --git a/mac-app/Tests/CloudMachineAppTests/DriveEncryptionTests.swift b/mac-app/Tests/CloudMachineAppTests/DriveEncryptionTests.swift new file mode 100644 index 0000000..fa2f1d4 --- /dev/null +++ b/mac-app/Tests/CloudMachineAppTests/DriveEncryptionTests.swift @@ -0,0 +1,396 @@ +import XCTest + +@testable import CloudMachineCore + +/// `vm-encrypt`: the folder the machine uses, the scripts it runs in the +/// machine, and the gates in front of every step that could lose the backup. +final class DriveEncryptionTests: XCTestCase { + + // MARK: - Which folder + + /// An installation that never ran `vm-encrypt` keeps its folder: a release + /// with this code must not move a live machine anywhere. + func testWithoutEncryptionTheFolderIsUnchanged() { + let plain = VMLayout.plainRemote(override: nil, folder: "mac-studio") + XCTAssertEqual(plain, "gdrive:CloudMachine/mac-studio-tm") + XCTAssertEqual(VMLayout.remote(plain: plain, encrypted: false), plain) + XCTAssertEqual(VMLayout.remote(plain: plain, encrypted: true), "gdrive-crypt:tm") + XCTAssertEqual( + VMLayout.plainRemote(override: "gdrive:test-tm", folder: "mac-studio"), "gdrive:test-tm") + XCTAssertEqual(DriveEncryption.cryptRoot(plain: plain), "gdrive:CloudMachine/mac-studio-crypt") + XCTAssertEqual(DriveEncryption.cryptRoot(plain: "gdrive:test-tm"), "gdrive:test-crypt") + } + + /// A throwaway machine never shares the live marker or the live folder: + /// `delete-plain` there must not reach the real backup. + func testThrowawayMachineIsIsolated() { + XCTAssertEqual(DriveEncryption.markerName(instance: "cmsmb"), "drive-encryption") + XCTAssertEqual(DriveEncryption.markerName(instance: "cmtest"), "drive-encryption-cmtest") + let mac = "/Users/x/.config/rclone/rclone.conf" + func refusal(_ instance: String, _ remote: String?, _ config: String?) -> String? { + DriveEncryption.throwawayRefusal( + instance: instance, remoteOverride: remote, rcloneConfig: config, defaultConfig: mac) + } + XCTAssertNil(refusal("cmsmb", nil, nil)) + XCTAssertNil(refusal("cmsmb", nil, mac)) + XCTAssertNotNil(refusal("cmtest", nil, "/tmp/cmtest/rclone.conf")) + XCTAssertNil(refusal("cmtest", "gdrive:test-tm", "/tmp/cmtest/rclone.conf")) + } + + /// Decision 04.11: a throwaway machine never writes the Mac's rclone.conf + /// or the live Keychain entry. Without its own RCLONE_CONFIG every step + /// refuses and says what to set; its Keychain entry is its own by name. + func testThrowawayMachineNeedsItsOwnConfigAndKeychainEntry() { + let mac = "/Users/x/.config/rclone/rclone.conf" + func refusal(_ config: String?) -> String? { + DriveEncryption.throwawayRefusal( + instance: "cmtest", remoteOverride: "gdrive:test-tm", rcloneConfig: config, + defaultConfig: mac) + } + for config in [nil, "", mac, "/Users/x/.config/rclone/../rclone/rclone.conf"] { + let text = refusal(config) + XCTAssertNotNil(text, "\(String(describing: config))") + XCTAssertTrue(text?.contains("RCLONE_CONFIG") == true) + } + XCTAssertNil(refusal("/tmp/cmtest/rclone.conf")) + + XCTAssertEqual(DriveEncryption.keychainService(instance: "cmsmb"), "cloudmachine-drive-crypt") + XCTAssertEqual( + DriveEncryption.keychainService(instance: "cmtest"), "cloudmachine-drive-crypt-cmtest") + } + + /// Only a complete marker moves the machine; a half-written file is none. + func testMarker() { + XCTAssertEqual( + DriveEncryption.marker(text: "rclone-crypt 1793000000\n"), + Date(timeIntervalSince1970: 1_793_000_000)) + XCTAssertNil(DriveEncryption.marker(text: "rclone-crypt")) + XCTAssertNil(DriveEncryption.marker(text: "")) + XCTAssertNil(DriveEncryption.marker(text: "yes 1793000000")) + let date = Date(timeIntervalSince1970: 1_793_000_000) + XCTAssertEqual( + DriveEncryption.marker(text: DriveEncryption.markerText(switchedAt: date)), date) + } + + func testConfigSection() { + let section = DriveEncryption.configSection( + cryptRoot: "gdrive:CloudMachine/mac-studio-crypt", obscuredPassword: "OBP", + obscuredSalt: "OBS") + XCTAssertTrue(section.contains("\n[gdrive-crypt]\ntype = crypt\n")) + XCTAssertTrue(section.contains("remote = gdrive:CloudMachine/mac-studio-crypt\n")) + XCTAssertTrue(section.contains("password = OBP\npassword2 = OBS\n")) + XCTAssertTrue(section.contains("filename_encryption = standard\n")) + XCTAssertTrue(section.contains("directory_name_encryption = true\n")) + XCTAssertTrue(DriveEncryption.hasSection("[gdrive]\ntype = drive\n" + section)) + XCTAssertFalse(DriveEncryption.hasSection("[gdrive]\ntype = drive\n")) + } + + /// With encryption the `:drive:` check cannot read names: any folder in the + /// encrypted root is the backup. + func testEncryptedFolderListing() { + XCTAssertEqual( + RemoteConfigurer.classifyRemoteListing( + succeeded: true, stdout: "q1b2c3d4/\n", stderr: "", anyFolderCounts: true), .present) + XCTAssertEqual( + RemoteConfigurer.classifyRemoteListing( + succeeded: true, stdout: "", stderr: "", anyFolderCounts: true), .absent) + XCTAssertEqual( + RemoteConfigurer.classifyRemoteListing(succeeded: true, stdout: "q1b2c3d4/\n", stderr: ""), + .absent) + } + + // MARK: - The background copy + + func testCopyState() { + func state(_ load: String, _ active: String, _ sub: String, _ result: String) + -> DriveEncryption.CopyState + { + DriveEncryption.copyState( + showOutput: "LoadState=\(load)\nActiveState=\(active)\nSubState=\(sub)\nResult=\(result)\n") + } + XCTAssertEqual(state("not-found", "inactive", "dead", "success"), .notRun) + XCTAssertEqual(state("loaded", "active", "running", "success"), .running) + XCTAssertEqual(state("loaded", "active", "exited", "success"), .done) + XCTAssertEqual(state("loaded", "failed", "failed", "exit-code"), .failed("exit-code")) + XCTAssertEqual(DriveEncryption.copyState(showOutput: ""), .unknown) + } + + func testCopyKeepsAFinishedUnitVisible() { + // The rclone lines are quoted once more inside `bash -c '...'`. + let script = DriveEncryption.copyScript(.init(plain: "gdrive:CloudMachine/m-tm")) + .replacingOccurrences(of: #"'\''"#, with: "'") + XCTAssertTrue(script.contains("-p RemainAfterExit=yes")) + XCTAssertTrue(script.contains("copy 'gdrive:CloudMachine/m-tm' 'gdrive-crypt:tm'")) + XCTAssertTrue(script.contains("copy 'gdrive:CloudMachine/m-tm-meta' 'gdrive-crypt:tm-meta'")) + XCTAssertTrue(script.contains("--drive-stop-on-upload-limit")) + XCTAssertFalse(script.contains(" sync ")) + } + + // MARK: - Switch + + private let remotes = DriveEncryption.Remotes(plain: "gdrive:CloudMachine/m-tm") + + func testFinalSyncChecksEveryFileEitherWay() { + let forward = DriveEncryption.finalSyncScript(remotes, direction: .encrypt) + XCTAssertTrue(forward.hasPrefix("set -euo pipefail\n")) + XCTAssertTrue(forward.contains("sync 'gdrive:CloudMachine/m-tm' 'gdrive-crypt:tm'")) + XCTAssertTrue(forward.contains("cryptcheck 'gdrive:CloudMachine/m-tm' 'gdrive-crypt:tm'")) + XCTAssertTrue( + forward.contains("cryptcheck 'gdrive:CloudMachine/m-tm-meta' 'gdrive-crypt:tm-meta'")) + let back = DriveEncryption.finalSyncScript(remotes, direction: .decrypt) + XCTAssertTrue(back.contains("sync 'gdrive-crypt:tm' 'gdrive:CloudMachine/m-tm'")) + XCTAssertTrue(back.contains("cryptcheck 'gdrive:CloudMachine/m-tm' 'gdrive-crypt:tm'")) + } + + /// A wrong password in the machine's rclone.conf: `gdrive-crypt:tm` then + /// encrypts to another folder name, `sync` finds "nothing" there, uploads + /// the whole backup again under the wrong key and `cryptcheck` agrees with + /// it (container test, 03.11: switch succeeded, 182 files in the folder). + /// Listing the crypt root with `--crypt-strict-names` fails on the folders + /// the right key wrote, before anything is copied. + func testSyncAndCopyRefuseAnotherKey() { + let check = "lsf --dirs-only --crypt-strict-names 'gdrive-crypt:'" + for direction in [DriveEncryption.Direction.encrypt, .decrypt] { + let script = DriveEncryption.finalSyncScript(remotes, direction: direction) + let lines = script.split(separator: "\n").map(String.init) + let listed = lines.firstIndex { $0.contains(check) } ?? Int.max + let synced = lines.firstIndex { $0.contains(" sync ") } ?? -1 + XCTAssertLessThan(listed, synced, "\(direction)") + // The copy has run, so both folders must be readable with this key - + // checked before anything is synced. + for folder in ["grep -qx 'tm/'", "grep -qx 'tm-meta/'"] { + let grepped = lines.firstIndex { $0.contains(folder) } ?? Int.max + XCTAssertLessThan(grepped, synced, "\(direction) \(folder)") + } + } + // The copy starts from an empty folder, but a resumed one must not + // continue under another key. + let copy = DriveEncryption.copyScript(.init(plain: "gdrive:CloudMachine/m-tm")) + .replacingOccurrences(of: #"'\''"#, with: "'") + let listed = copy.range(of: check)?.lowerBound + let copied = copy.range(of: "copy 'gdrive:CloudMachine/m-tm'")?.lowerBound + XCTAssertNotNil(listed) + XCTAssertNotNil(copied) + if let listed, let copied { XCTAssertLessThan(listed, copied) } + // Before the first copy the folder does not exist and `lsf` fails on it + // (the container test's first try of this check: every copy failed). + let made = copy.range(of: "mkdir 'gdrive-crypt:'")?.lowerBound + XCTAssertNotNil(made) + if let made, let listed { XCTAssertLessThan(made, listed) } + } + + /// A nested encrypted folder: the listing of the crypt root shows only its + /// first component, so that is what is looked for. + func testEncryptedFolderChecks() { + XCTAssertEqual( + DriveEncryption.encryptedFolderChecks(.init(plain: "gdrive:m-tm")), ["tm/", "tm-meta/"]) + XCTAssertEqual( + DriveEncryption.encryptedFolderChecks( + .init(plain: "gdrive:m-tm", encrypted: "gdrive-crypt:a/tm")), ["a/"]) + } + + /// The listing check cannot see a key that was wrong from the first copy + /// on: everything is then consistently under that key. What can see it is + /// the Mac: `setup` writes the same section to both files, so the machine's + /// password, salt and folder must equal the Mac's, character for character. + func testMachineKeyMustEqualTheMacs() { + let mac = """ + [gdrive] + type = drive + + [gdrive-crypt] + type = crypt + remote = gdrive:CloudMachine/m-crypt + password = AAA + password2 = BBB + filename_encryption = standard + + [other] + password = ZZZ + """ + let keys = DriveEncryption.cryptKeys(config: mac) + XCTAssertEqual( + keys, + ["remote": "gdrive:CloudMachine/m-crypt", "password": "AAA", "password2": "BBB"]) + // The machine's half, as `sed -n '/^\[gdrive-crypt\]/,/^\[/p'` prints it. + let same = [ + "[gdrive-crypt]", "type = crypt", "remote = gdrive:CloudMachine/m-crypt", + "password = AAA", "password2 = BBB", "[other]", "", + ].joined(separator: "\n") + XCTAssertNil(DriveEncryption.keyRefusal(mac: mac, machine: same)) + for (from, to) in [("AAA", "XXX"), ("BBB", "XXX")] { + XCTAssertNotNil( + DriveEncryption.keyRefusal( + mac: mac, machine: same.replacingOccurrences(of: from, with: to)), from) + } + XCTAssertNotNil( + DriveEncryption.keyRefusal( + mac: mac, machine: same.replacingOccurrences(of: "m-crypt", with: "x-crypt"))) + // Not read, or no section on one side: refused like a difference. + XCTAssertNotNil(DriveEncryption.keyRefusal(mac: nil, machine: same)) + XCTAssertNotNil(DriveEncryption.keyRefusal(mac: mac, machine: nil)) + XCTAssertNotNil(DriveEncryption.keyRefusal(mac: "[gdrive]\n", machine: "[gdrive]\n")) + } + + /// A failed listing of foreign-key folders says what to do, not only + /// rclone's last lines. + func testForeignFoldersAreExplained() { + let out = + "ERROR : error listing: there were 2 undecryptable name errors." + + " first error: 0b3t: undecryptable dir name detected" + let hint = DriveEncryption.syncFailureHint(out, cryptRoot: "gdrive:CloudMachine/m-crypt") + XCTAssertNotNil(hint) + XCTAssertTrue(hint?.contains("gdrive:CloudMachine/m-crypt") == true) + XCTAssertNil(DriveEncryption.syncFailureHint("directory not found", cryptRoot: "x")) + } + + /// Every guest script parses (`bash -n`); the container test ran them. + func testScriptsParse() throws { + let plain = "gdrive:CloudMachine/m-tm" + let r = DriveEncryption.Remotes(plain: plain) + let scripts = [ + DriveEncryption.copyScript(r), DriveEncryption.copyStateScript, + DriveEncryption.finalSyncScript(r, direction: .encrypt), + DriveEncryption.finalSyncScript(r, direction: .decrypt), + DriveEncryption.repointScript(oldRemote: plain, newRemote: r.encrypted, stamp: "S"), + DriveEncryption.repointScript(oldRemote: r.encrypted, newRemote: plain, stamp: "S"), + DriveEncryption.deletePlainScript(plain: plain), + ] + for script in scripts { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/bin/bash") + process.arguments = ["-n", "-c", script] + try process.run() + process.waitUntilExit() + XCTAssertEqual(process.terminationStatus, 0, script) + } + } + + func testCachePaths() { + XCTAssertEqual( + DriveEncryption.cachePaths(remote: "gdrive:CloudMachine/m-tm"), + [ + "/var/cache/rclone/vfs/gdrive/CloudMachine/m-tm", + "/var/cache/rclone/vfs/gdrive/CloudMachine/m-tm".replacingOccurrences( + of: "/vfs/", with: "/vfsMeta/"), + ]) + XCTAssertEqual( + DriveEncryption.cachePaths(remote: "gdrive-crypt:tm").first, + "/var/cache/rclone/vfs/gdrive-crypt/tm") + } + + /// The order is the safety: the queue is checked empty before anything + /// changes, the way back is armed before the units stop, and only the old + /// remote's buffer is deleted. + func testRepointOrder() { + let script = DriveEncryption.repointScript( + oldRemote: "gdrive:CloudMachine/m-tm", newRemote: "gdrive-crypt:tm", stamp: "S") + let lines = script.split(separator: "\n").map(String.init) + func index(_ text: String) -> Int { + lines.firstIndex { $0.contains(text) } ?? Int.max + } + XCTAssertEqual(lines.first, "set -euo pipefail") + // Starts only from the folder it was asked to leave. + XCTAssertLessThan( + index(#"grep -qx 'CM_REMOTE="gdrive:CloudMachine/m-tm"'"#), index("systemctl stop smbd")) + XCTAssertLessThan(index("systemctl cat rclone-tm"), index("systemctl stop smbd")) + XCTAssertLessThan(index("unsent=$("), index("cp -p /etc/cloudmachine/vm.env")) + XCTAssertLessThan(index("trap '"), index("systemctl stop tm-bands-bind")) + XCTAssertLessThan(index("systemctl stop tm-bands-bind"), index("rm -rf")) + XCTAssertTrue(script.contains(#"CM_REMOTE="gdrive-crypt:tm""#)) + XCTAssertTrue(script.contains("rm -rf '/var/cache/rclone/vfs/gdrive/CloudMachine/m-tm'")) + XCTAssertFalse(script.contains("rm -rf '/var/cache/rclone/vfs/gdrive-crypt")) + XCTAssertLessThan(index("test \"$after\" -eq \"$before\""), index("trap - ERR")) + // `move` stopped Samba before the last sync. A failure before the way + // back is armed (no bands, a preflight miss) must not leave it stopped: + // container test, 03.11, `before=0` left smbd inactive. + XCTAssertLessThan(index("trap 'systemctl start smbd' ERR"), index("systemctl cat rclone-tm")) + XCTAssertLessThan(index("trap 'systemctl start smbd' ERR"), index("test \"$before\" -gt 0")) + XCTAssertLessThan(index("trap 'systemctl start smbd' ERR"), index("trap 'echo \"FAILED")) + } + + private func goodSwitch(_ direction: DriveEncryption.Direction = .encrypt) + -> DriveEncryption.SwitchFacts + { + DriveEncryption.SwitchFacts( + direction: direction, enabled: direction == .decrypt, cryptRemoteInMachine: true, + timeMachineRunning: false, + reading: .init( + buffer: .init(bytesUsed: 100 << 30, uploadsQueued: 0, uploadsInProgress: 0, unsentBytes: 0), + freeBytes: 50 << 30), + copyState: .done, guardHoldsPause: false) + } + + func testSwitchGate() { + XCTAssertNil(DriveEncryption.switchRefusal(goodSwitch())) + var facts = goodSwitch() + facts.enabled = true + XCTAssertNotNil(DriveEncryption.switchRefusal(facts)) + for change: (inout DriveEncryption.SwitchFacts) -> Void in [ + { $0.cryptRemoteInMachine = nil }, { $0.cryptRemoteInMachine = false }, + { $0.timeMachineRunning = nil }, { $0.timeMachineRunning = true }, + { $0.reading = nil }, { $0.reading?.buffer.unsentBytes = nil }, + { $0.reading?.buffer.unsentBytes = 1 }, { $0.reading?.buffer.uploadsInProgress = 1 }, + { $0.reading?.buffer.erroredFiles = 1 }, { $0.guardHoldsPause = true }, + { $0.copyState = .running }, { $0.copyState = .notRun }, + { $0.copyState = .failed("exit-code") }, { $0.copyState = .unknown }, + ] { + var bad = goodSwitch() + change(&bad) + XCTAssertNotNil(DriveEncryption.switchRefusal(bad), "\(bad)") + } + } + + /// Going back decrypts in the last sync itself; the forward copy's state + /// does not matter, being on the encrypted folder does. + func testRollbackGate() { + var facts = goodSwitch(.decrypt) + facts.copyState = .notRun + XCTAssertNil(DriveEncryption.switchRefusal(facts)) + facts.enabled = false + XCTAssertNotNil(DriveEncryption.switchRefusal(facts)) + } + + // MARK: - Deleting the plain folder + + private let switched = Date(timeIntervalSince1970: 1_793_000_000) + + private func goodDelete() -> DriveEncryption.DeleteFacts { + DriveEncryption.DeleteFacts( + switchedAt: switched, lastBackup: switched.addingTimeInterval(3600), + lastMetaSync: switched.addingTimeInterval(4000), + reading: .init( + buffer: .init(bytesUsed: 1, uploadsQueued: 0, uploadsInProgress: 0, unsentBytes: 0), + freeBytes: 1), + secretsSavedElsewhere: true, restoreChecked: true) + } + + func testDeleteGate() { + XCTAssertNil(DriveEncryption.deleteRefusal(goodDelete())) + for change: (inout DriveEncryption.DeleteFacts) -> Void in [ + { $0.switchedAt = nil }, { $0.secretsSavedElsewhere = false }, { $0.restoreChecked = false }, + { $0.lastBackup = nil }, { $0.lastBackup = self.switched }, + { $0.lastMetaSync = nil }, { $0.lastMetaSync = self.switched.addingTimeInterval(60) }, + { $0.reading = nil }, { $0.reading?.buffer.unsentBytes = 5 }, + { $0.reading?.buffer.erroredFiles = 1 }, + ] { + var bad = goodDelete() + change(&bad) + XCTAssertNotNil(DriveEncryption.deleteRefusal(bad), "\(bad)") + } + } + + func testDeleteGoesToTheBinAndTouchesOnlyThePlainFolder() { + let script = DriveEncryption.deletePlainScript(plain: "gdrive:CloudMachine/m-tm") + XCTAssertTrue(script.contains("purge --drive-use-trash=true 'gdrive:CloudMachine/m-tm'")) + XCTAssertTrue(script.contains("purge --drive-use-trash=true 'gdrive:CloudMachine/m-tm-meta'")) + XCTAssertFalse(script.contains("gdrive-crypt")) + XCTAssertFalse(script.contains("use-trash=false")) + } + + func testMetaSyncDate() { + XCTAssertEqual( + DriveEncryption.metaSyncDate("1793000000\n"), Date(timeIntervalSince1970: 1_793_000_000)) + XCTAssertNil(DriveEncryption.metaSyncDate("stat: cannot stat")) + } +}